mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 10:40:10 +01:00 
			
		
		
		
	Compare commits
	
		
			172 Commits
		
	
	
		
			customized
			...
			customized
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						9101ca1afc
	
				 | 
					
					
						|||
| 
						
						
							
						
						7e493e0188
	
				 | 
					
					
						|||
| 
						
						
							
						
						78b5540636
	
				 | 
					
					
						|||
| 
						
						
							
						
						97ba6ae997
	
				 | 
					
					
						|||
| 
						
						
							
						
						9ac1a14604
	
				 | 
					
					
						|||
| 
						
						
							
						
						3c20feba8d
	
				 | 
					
					
						|||
| 
						
						
							
						
						d23475818a
	
				 | 
					
					
						|||
| 
						
						
							
						
						7f5c9b56ef
	
				 | 
					
					
						|||
| 
						
						
							
						
						c3f5cb4866
	
				 | 
					
					
						|||
| 
						
						
							
						
						7a25fbfd31
	
				 | 
					
					
						|||
| 
						
						
							
						
						444880b153
	
				 | 
					
					
						|||
| 
						
						
							
						
						0ae06e46da
	
				 | 
					
					
						|||
| 
						
						
							
						
						98e085e636
	
				 | 
					
					
						|||
| 
						
						
							
						
						67c60ae8cc
	
				 | 
					
					
						|||
| 
						
						
							
						
						ffd2433da3
	
				 | 
					
					
						|||
| 
						
						
							
						
						cfe219250c
	
				 | 
					
					
						|||
| 
						
						
							
						
						68bdae1afc
	
				 | 
					
					
						|||
| 
						
						
							
						
						77859e03e7
	
				 | 
					
					
						|||
| 
						
						
							
						
						cfd05be288
	
				 | 
					
					
						|||
| 
						 | 
					c0419d6018 | ||
| 
						 | 
					ea98e50f65 | ||
| 
						 | 
					168174e383 | ||
| 
						 | 
					53cd4e1b88 | ||
| 
						 | 
					27e3561bb8 | ||
| 
						 | 
					9bb9cb13e3 | ||
| 
						 | 
					16455f7241 | ||
| 
						 | 
					82225aa519 | ||
| 
						 | 
					5f2baefc6c | ||
| 
						 | 
					cedcf39723 | ||
| 
						 | 
					4925d9aada | ||
| 
						 | 
					f3e6df32d0 | ||
| 
						 | 
					5aaa8752af | ||
| 
						 | 
					a1d214316c | ||
| 
						 | 
					8a1e3eb066 | ||
| 
						 | 
					75a417773f | ||
| 
						 | 
					b3b3ee4f21 | ||
| 
						 | 
					07b1db4b28 | ||
| 
						 | 
					dc775a0f22 | ||
| 
						 | 
					10228f953e | ||
| 
						 | 
					afceecadbe | ||
| 
						 | 
					b2a4e59571 | ||
| 
						 | 
					b0b944bbf3 | ||
| 
						 | 
					89a3d74b93 | ||
| 
						 | 
					f4eef04750 | ||
| 
						 | 
					e62c86b99f | ||
| 
						 | 
					82bd792da5 | ||
| 
						 | 
					a58c9065e6 | ||
| 
						 | 
					e8bf984b76 | ||
| 
						 | 
					23e1a3499f | ||
| 
						 | 
					6b4e4bacd7 | ||
| 
						 | 
					a84c04ca08 | ||
| 
						 | 
					e67c71e440 | ||
| 
						 | 
					5078ff9c7a | ||
| 
						 | 
					647510de5d | ||
| 
						 | 
					84e11e4236 | ||
| 
						 | 
					9538714af1 | ||
| 
						 | 
					ffd832d990 | ||
| 
						 | 
					8de2b8976b | ||
| 
						 | 
					a6aa26b5d9 | ||
| 
						 | 
					2505651c68 | ||
| 
						 | 
					e67c7b23ff | ||
| 
						 | 
					453cca3b0c | ||
| 
						 | 
					6cee04a4be | ||
| 
						 | 
					ae8b9b4773 | ||
| 
						 | 
					e748b7b265 | ||
| 
						 | 
					c2401ec013 | ||
| 
						 | 
					8073d7ecd0 | ||
| 
						 | 
					64f7859ba7 | ||
| 
						 | 
					f1b94d7026 | ||
| 
						 | 
					79653b6048 | ||
| 
						 | 
					b0e6b72281 | ||
| 
						 | 
					e6220e5e53 | ||
| 
						 | 
					3c064845b1 | ||
| 
						 | 
					736cb219ca | ||
| 
						 | 
					fb30e4e387 | ||
| 
						 | 
					74550ffa16 | ||
| 
						 | 
					d0a0672282 | ||
| 
						 | 
					16e92ddf60 | ||
| 
						 | 
					4d8e68d800 | ||
| 
						 | 
					bbebfaf32a | ||
| 
						 | 
					7e56331e47 | ||
| 
						 | 
					750db8e71c | ||
| 
						 | 
					4255ef68a3 | ||
| 
						 | 
					3313464214 | ||
| 
						 | 
					683ba32a15 | ||
| 
						 | 
					90a60155e5 | ||
| 
						 | 
					b25d06ed9e | ||
| 
						 | 
					706ae3dd91 | ||
| 
						 | 
					9b15ed8181 | ||
| 
						 | 
					f355bef36b | ||
| 
						 | 
					4391e69c48 | ||
| 
						 | 
					0710d80391 | ||
| 
						 | 
					cf41a3a76c | ||
| 
						 | 
					31b2cd872f | ||
| 
						 | 
					2b6945cbb2 | ||
| 
						 | 
					ae5f43918f | ||
| 
						 | 
					6b6bc2752e | ||
| 
						 | 
					4556adae3c | ||
| 
						 | 
					1b0886041b | ||
| 
						 | 
					16e18f3ca7 | ||
| 
						 | 
					ee0d67fbbb | ||
| 
						 | 
					450527f172 | ||
| 
						 | 
					135518ee39 | ||
| 
						 | 
					58715ecb5f | ||
| 
						 | 
					75e26b101d | ||
| 
						 | 
					6421a6face | ||
| 
						 | 
					948520f90a | ||
| 
						 | 
					0765118ce2 | ||
| 
						 | 
					efd4c7b617 | ||
| 
						 | 
					c5346fbece | ||
| 
						 | 
					fe8e8ccc3e | ||
| 
						 | 
					eae111bc2c | ||
| 
						 | 
					6a6c1dc6b4 | ||
| 
						 | 
					86bbb282ab | ||
| 
						 | 
					28aa156cb7 | ||
| 
						 | 
					a7814e69de | ||
| 
						 | 
					1452c116cf | ||
| 
						 | 
					23dfc4b339 | ||
| 
						 | 
					931d4be972 | ||
| 
						 | 
					7dceda587b | ||
| 
						 | 
					52a969074d | ||
| 
						 | 
					e7b87d31cf | ||
| 
						 | 
					5eb0fae08f | ||
| 
						 | 
					798d805a0f | ||
| 
						 | 
					0d4ba06e57 | ||
| 
						 | 
					4913b13a2d | ||
| 
						 | 
					b0bab992db | ||
| 
						 | 
					af5f4227b7 | ||
| 
						 | 
					fa6a694ea4 | ||
| 
						 | 
					1da7ffc052 | ||
| 
						 | 
					c673f5818c | ||
| 
						 | 
					ec78a87644 | ||
| 
						 | 
					69d14ddcf5 | ||
| 
						 | 
					f62819df00 | ||
| 
						 | 
					39a85b6bc2 | ||
| 
						 | 
					f76ae3e867 | ||
| 
						 | 
					3651e5f2f0 | ||
| 
						 | 
					89e016ef6c | ||
| 
						 | 
					e4996f4c4d | ||
| 
						 | 
					c44ed58142 | ||
| 
						 | 
					0091af2a41 | ||
| 
						 | 
					d1eea68719 | ||
| 
						 | 
					133aff7fd8 | ||
| 
						 | 
					efde94db7a | ||
| 
						 | 
					6ec072b34e | ||
| 
						 | 
					4027a21514 | ||
| 
						 | 
					3665b1ab00 | ||
| 
						 | 
					cf6b292f0c | ||
| 
						 | 
					507e4173d3 | ||
| 
						 | 
					abc3575d3e | ||
| 
						 | 
					2c0ff587e3 | ||
| 
						 | 
					26c87535d6 | ||
| 
						 | 
					6ac8e672be | ||
| 
						 | 
					04ee2dd1e7 | ||
| 
						 | 
					3106a98aee | ||
| 
						 | 
					73769a3472 | ||
| 
						 | 
					085e253d77 | ||
| 
						 | 
					b2af8f153e | ||
| 
						 | 
					37fb41fca8 | ||
| 
						 | 
					e2b05ab639 | ||
| 
						 | 
					354fd8fef0 | ||
| 
						 | 
					9b97867be1 | ||
| 
						 | 
					06685d1721 | ||
| 
						 | 
					ae4b88a06b | ||
| 
						 | 
					c83ecc46ed | ||
| 
						 | 
					c32050a208 | ||
| 
						 | 
					4a8c7227e6 | ||
| 
						 | 
					55e61a7094 | ||
| 
						 | 
					60977d05b6 | ||
| 
						 | 
					601747f720 | ||
| 
						 | 
					0c91bc3207 | ||
| 
						 | 
					f5cd2c173f | 
							
								
								
									
										6
									
								
								.github/workflows/closeYoutrackOnCommit.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/closeYoutrackOnCommit.yml
									
									
									
									
										vendored
									
									
								
							@@ -20,10 +20,10 @@ jobs:
 | 
			
		||||
          fetch-depth: 300
 | 
			
		||||
      - name: Get tags
 | 
			
		||||
        run: git fetch --tags origin
 | 
			
		||||
      - name: Set up JDK 11
 | 
			
		||||
      - name: Set up JDK 17
 | 
			
		||||
        uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: '11'
 | 
			
		||||
          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
 | 
			
		||||
@@ -34,7 +34,7 @@ jobs:
 | 
			
		||||
          echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-close-youtrack)" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
      - name: Update YouTrack
 | 
			
		||||
        run: ./gradlew updateYoutrackOnCommit
 | 
			
		||||
        run: ./gradlew --no-configuration-cache updateYoutrackOnCommit
 | 
			
		||||
        env:
 | 
			
		||||
          SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
 | 
			
		||||
          YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/integrationsTest.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/integrationsTest.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,10 +18,10 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 300
 | 
			
		||||
      - name: Set up JDK 11
 | 
			
		||||
      - name: Set up JDK 17
 | 
			
		||||
        uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: '11'
 | 
			
		||||
          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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/kover.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/kover.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,10 +18,10 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 300
 | 
			
		||||
      - name: Set up JDK 11
 | 
			
		||||
      - name: Set up JDK 17
 | 
			
		||||
        uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: '11'
 | 
			
		||||
          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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/mergePr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/mergePr.yml
									
									
									
									
										vendored
									
									
								
							@@ -20,17 +20,17 @@ jobs:
 | 
			
		||||
          fetch-depth: 50
 | 
			
		||||
          # See end of file updateChangeslog.yml for explanation of this secret
 | 
			
		||||
          ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
 | 
			
		||||
      - name: Set up JDK 11
 | 
			
		||||
      - name: Set up JDK 17
 | 
			
		||||
        uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: '11'
 | 
			
		||||
          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 authors
 | 
			
		||||
        id: update_authors
 | 
			
		||||
        run: ./gradlew updateMergedPr -PprId=${{ github.event.number }}
 | 
			
		||||
        run: ./gradlew --no-configuration-cache updateMergedPr -PprId=${{ github.event.number }}
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/updateAuthors.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/updateAuthors.yml
									
									
									
									
										vendored
									
									
								
							@@ -25,10 +25,10 @@ jobs:
 | 
			
		||||
          ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
 | 
			
		||||
      - name: Get tags
 | 
			
		||||
        run: git fetch --tags origin
 | 
			
		||||
      - name: Set up JDK 11
 | 
			
		||||
      - name: Set up JDK 17
 | 
			
		||||
        uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: '11'
 | 
			
		||||
          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
 | 
			
		||||
@@ -40,7 +40,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Update authors
 | 
			
		||||
        id: update_authors
 | 
			
		||||
        run: ./gradlew updateAuthors --stacktrace
 | 
			
		||||
        run: ./gradlew --no-configuration-cache updateAuthors --stacktrace
 | 
			
		||||
        env:
 | 
			
		||||
          SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
 | 
			
		||||
          GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/updateChangelog.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/updateChangelog.yml
									
									
									
									
										vendored
									
									
								
							@@ -22,10 +22,10 @@ jobs:
 | 
			
		||||
          ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
 | 
			
		||||
      - name: Get tags
 | 
			
		||||
        run: git fetch --tags origin
 | 
			
		||||
      - name: Set up JDK 11
 | 
			
		||||
      - name: Set up JDK 17
 | 
			
		||||
        uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: '11'
 | 
			
		||||
          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
 | 
			
		||||
@@ -36,7 +36,7 @@ jobs:
 | 
			
		||||
          echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-changelog)" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
      - name: Update changelog
 | 
			
		||||
        run: ./gradlew updateChangelog
 | 
			
		||||
        run: ./gradlew --no-configuration-cache updateChangelog
 | 
			
		||||
        env:
 | 
			
		||||
          SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
 | 
			
		||||
 | 
			
		||||
@@ -60,4 +60,4 @@ jobs:
 | 
			
		||||
#   dependabot updates. See mergeDependatobPR.yml file.
 | 
			
		||||
# However, it turned out that GitHub accepts pushes from the actions as a PR and requires checks, that are always
 | 
			
		||||
#   false for pushing from actions.
 | 
			
		||||
# This secret is created to implement the workaround described in https://stackoverflow.com/a/76135647/3124227
 | 
			
		||||
# This secret is created to implement the workaround described in https://stackoverflow.com/a/76135647/3124227
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/updateFormatting.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/updateFormatting.yml
									
									
									
									
										vendored
									
									
								
							@@ -20,10 +20,10 @@ jobs:
 | 
			
		||||
          fetch-depth: 50
 | 
			
		||||
          # See end of file updateChangeslog.yml for explanation of this secret
 | 
			
		||||
          ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
 | 
			
		||||
      - name: Set up JDK 11
 | 
			
		||||
      - name: Set up JDK 17
 | 
			
		||||
        uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: '11'
 | 
			
		||||
          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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -10,6 +10,8 @@
 | 
			
		||||
!/.idea/runConfigurations
 | 
			
		||||
!/.idea/codeStyles
 | 
			
		||||
!/.idea/vcs.xml
 | 
			
		||||
!/.idea/misc.xml
 | 
			
		||||
!/.idea/.name
 | 
			
		||||
 | 
			
		||||
**/build/
 | 
			
		||||
**/out/
 | 
			
		||||
@@ -22,7 +24,7 @@
 | 
			
		||||
.teamcity/*.iml
 | 
			
		||||
 | 
			
		||||
# Generated by gradle task "generateGrammarSource"
 | 
			
		||||
src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
 | 
			
		||||
vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated
 | 
			
		||||
vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
 | 
			
		||||
# Generated JSONs for lazy classloading
 | 
			
		||||
/vim-engine/src/main/resources/ksp-generated
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.idea/.name
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.idea/.name
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
IdeaVim
 | 
			
		||||
							
								
								
									
										22
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="EntryPointsManager">
 | 
			
		||||
    <list size="3">
 | 
			
		||||
      <item index="0" class="java.lang.String" itemvalue="com.intellij.vim.annotations.CommandOrMotion" />
 | 
			
		||||
      <item index="1" class="java.lang.String" itemvalue="com.intellij.vim.annotations.ExCommand" />
 | 
			
		||||
      <item index="2" class="java.lang.String" itemvalue="com.intellij.vim.annotations.VimscriptFunction" />
 | 
			
		||||
    </list>
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="ExternalStorageConfigurationManager" enabled="true" />
 | 
			
		||||
  <component name="FrameworkDetectionExcludesConfiguration">
 | 
			
		||||
    <file type="web" url="file://$PROJECT_DIR$" />
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="MavenProjectsManager">
 | 
			
		||||
    <option name="originalFiles">
 | 
			
		||||
      <list>
 | 
			
		||||
        <option value="$PROJECT_DIR$/.teamcity/pom.xml" />
 | 
			
		||||
      </list>
 | 
			
		||||
    </option>
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK" />
 | 
			
		||||
</project>
 | 
			
		||||
							
								
								
									
										25
									
								
								.idea/runConfigurations/Start_IJ_with_IdeaVim__Split_Mode_.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.idea/runConfigurations/Start_IJ_with_IdeaVim__Split_Mode_.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="Start IJ with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle">
 | 
			
		||||
    <log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
 | 
			
		||||
    <ExternalSystemSettings>
 | 
			
		||||
      <option name="executionName" />
 | 
			
		||||
      <option name="externalProjectPath" value="$PROJECT_DIR$" />
 | 
			
		||||
      <option name="externalSystemIdString" value="GRADLE" />
 | 
			
		||||
      <option name="scriptParameters" value="" />
 | 
			
		||||
      <option name="taskDescriptions">
 | 
			
		||||
        <list />
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="taskNames">
 | 
			
		||||
        <list>
 | 
			
		||||
          <option value="runIdeSplitMode" />
 | 
			
		||||
        </list>
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="vmOptions" value="" />
 | 
			
		||||
    </ExternalSystemSettings>
 | 
			
		||||
    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
 | 
			
		||||
    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
 | 
			
		||||
    <DebugAllEnabled>false</DebugAllEnabled>
 | 
			
		||||
    <RunAsTest>false</RunAsTest>
 | 
			
		||||
    <method v="2" />
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										14
									
								
								.teamcity/_Self/buildTypes/ReleasePlugin.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.teamcity/_Self/buildTypes/ReleasePlugin.kt
									
									
									
									
										vendored
									
									
								
							@@ -19,8 +19,6 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.BuildFailureOnMetric
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.failOnMetricChange
 | 
			
		||||
 | 
			
		||||
object ReleaseMajor : ReleasePlugin("major")
 | 
			
		||||
object ReleaseMinor : ReleasePlugin("minor")
 | 
			
		||||
@@ -158,16 +156,4 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
 | 
			
		||||
      teamcitySshKey = "IdeaVim ssh keys"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  failureConditions {
 | 
			
		||||
    failOnMetricChange {
 | 
			
		||||
      metric = BuildFailureOnMetric.MetricType.ARTIFACT_SIZE
 | 
			
		||||
      threshold = 5
 | 
			
		||||
      units = BuildFailureOnMetric.MetricUnit.PERCENTS
 | 
			
		||||
      comparison = BuildFailureOnMetric.MetricComparison.DIFF
 | 
			
		||||
      compareTo = build {
 | 
			
		||||
        buildRule = lastSuccessful()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								AUTHORS.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								AUTHORS.md
									
									
									
									
									
								
							@@ -511,6 +511,18 @@ Contributors:
 | 
			
		||||
  [![icon][github]](https://github.com/Aisper)
 | 
			
		||||
   
 | 
			
		||||
  Egor Nikolaevsky
 | 
			
		||||
* [![icon][mail]](mailto:77796630+throwaway69420-69420@users.noreply.github.com)
 | 
			
		||||
  [![icon][github]](https://github.com/kun-codes)
 | 
			
		||||
   
 | 
			
		||||
  Bishwa Saha, 
 | 
			
		||||
* [![icon][mail]](mailto:alexfu@fastmail.com)
 | 
			
		||||
  [![icon][github]](https://github.com/alexfu)
 | 
			
		||||
   
 | 
			
		||||
  Alex Fu
 | 
			
		||||
* [![icon][mail]](mailto:jakepeters199@hotmail.com)
 | 
			
		||||
  [![icon][github]](https://github.com/LazyScaper)
 | 
			
		||||
   
 | 
			
		||||
  Jake
 | 
			
		||||
 | 
			
		||||
Previous contributors:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
IdeaVim project is licensed under MIT license except the following parts of it:
 | 
			
		||||
 | 
			
		||||
* File [RegExp.kt](src/main/java/com/maddyhome/idea/vim/regexp/RegExp.kt) is licensed under Vim License.  
 | 
			
		||||
* File [ScrollViewHelper.kt](com/maddyhome/idea/vim/helper/ScrollViewHelper.kt) is licensed under Vim License.
 | 
			
		||||
* File [Tutor.kt](src/main/java/com/maddyhome/idea/vim/ui/Tutor.kt) is licensed under Vim License.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.24-1.0.20")
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.22")
 | 
			
		||||
  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")
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
 | 
			
		||||
import com.intellij.vim.processors.VimscriptFunctionProcessor
 | 
			
		||||
 | 
			
		||||
public class VimscriptFunctionProcessorProvider : SymbolProcessorProvider {
 | 
			
		||||
class VimscriptFunctionProcessorProvider : SymbolProcessorProvider {
 | 
			
		||||
  override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
 | 
			
		||||
    return VimscriptFunctionProcessor(environment)
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										145
									
								
								build.gradle.kts
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								build.gradle.kts
									
									
									
									
									
								
							@@ -48,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.9.0.202403050737-r")
 | 
			
		||||
    classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.0.202406032230-r")
 | 
			
		||||
    classpath("org.kohsuke:github-api:1.305")
 | 
			
		||||
 | 
			
		||||
    classpath("io.ktor:ktor-client-core:2.3.11")
 | 
			
		||||
    classpath("io.ktor:ktor-client-core:2.3.12")
 | 
			
		||||
    classpath("io.ktor:ktor-client-cio:2.3.10")
 | 
			
		||||
    classpath("io.ktor:ktor-client-auth:2.3.11")
 | 
			
		||||
    classpath("io.ktor:ktor-client-auth:2.3.12")
 | 
			
		||||
    classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
 | 
			
		||||
    classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
 | 
			
		||||
    classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
 | 
			
		||||
 | 
			
		||||
    // This comes from the changelog plugin
 | 
			
		||||
//        classpath("org.jetbrains:markdown:0.3.1")
 | 
			
		||||
@@ -63,7 +63,6 @@ buildscript {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
  antlr
 | 
			
		||||
  java
 | 
			
		||||
  kotlin("jvm") version "1.9.22"
 | 
			
		||||
  application
 | 
			
		||||
@@ -73,26 +72,11 @@ plugins {
 | 
			
		||||
  id("org.jetbrains.changelog") version "2.2.0"
 | 
			
		||||
 | 
			
		||||
  id("org.jetbrains.kotlinx.kover") version "0.6.1"
 | 
			
		||||
  id("com.dorongold.task-tree") version "3.0.0"
 | 
			
		||||
  id("com.dorongold.task-tree") version "4.0.0"
 | 
			
		||||
 | 
			
		||||
  id("com.google.devtools.ksp") version "1.9.22-1.0.17"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ksp {
 | 
			
		||||
  arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
 | 
			
		||||
  arg("vimscript_functions_file", "intellij_vimscript_functions.json")
 | 
			
		||||
  arg("ex_commands_file", "intellij_ex_commands.json")
 | 
			
		||||
  arg("commands_file", "intellij_commands.json")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
afterEvaluate {
 | 
			
		||||
//  tasks.named("kspKotlin").configure { dependsOn("clean") }
 | 
			
		||||
  tasks.named("kspKotlin").configure { dependsOn("generateGrammarSource") }
 | 
			
		||||
  tasks.named("kspTestFixturesKotlin").configure { enabled = false }
 | 
			
		||||
  tasks.named("kspTestFixturesKotlin").configure { enabled = false }
 | 
			
		||||
  tasks.named("kspTestKotlin").configure { enabled = false }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Import variables from gradle.properties file
 | 
			
		||||
val javaVersion: String by project
 | 
			
		||||
val kotlinVersion: String by project
 | 
			
		||||
@@ -100,8 +84,8 @@ val ideaVersion: String by project
 | 
			
		||||
val ideaType: String by project
 | 
			
		||||
val downloadIdeaSources: String by project
 | 
			
		||||
val instrumentPluginCode: String by project
 | 
			
		||||
val antlrVersion: String by project
 | 
			
		||||
val remoteRobotVersion: String by project
 | 
			
		||||
val splitModeVersion: String by project
 | 
			
		||||
 | 
			
		||||
val publishChannels: String by project
 | 
			
		||||
val publishToken: String by project
 | 
			
		||||
@@ -109,6 +93,8 @@ val publishToken: String by project
 | 
			
		||||
val slackUrl: String by project
 | 
			
		||||
val youtrackToken: String by project
 | 
			
		||||
 | 
			
		||||
val releaseType: String? by project
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
  mavenCentral()
 | 
			
		||||
  maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
 | 
			
		||||
@@ -117,12 +103,10 @@ repositories {
 | 
			
		||||
dependencies {
 | 
			
		||||
  api(project(":vim-engine"))
 | 
			
		||||
  ksp(project(":annotation-processors"))
 | 
			
		||||
  implementation(project(":annotation-processors"))
 | 
			
		||||
  compileOnly(project(":annotation-processors"))
 | 
			
		||||
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
 | 
			
		||||
  compileOnly("org.jetbrains:annotations:24.1.0")
 | 
			
		||||
  runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
 | 
			
		||||
  antlr("org.antlr:antlr4:$antlrVersion")
 | 
			
		||||
 | 
			
		||||
  // --------- Test dependencies ----------
 | 
			
		||||
 | 
			
		||||
@@ -143,12 +127,12 @@ dependencies {
 | 
			
		||||
  // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
 | 
			
		||||
  testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.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")
 | 
			
		||||
  testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
 | 
			
		||||
  testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
 | 
			
		||||
  testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
configurations {
 | 
			
		||||
@@ -159,6 +143,8 @@ configurations {
 | 
			
		||||
 | 
			
		||||
tasks {
 | 
			
		||||
  test {
 | 
			
		||||
    useJUnitPlatform()
 | 
			
		||||
 | 
			
		||||
    // Set teamcity env variable locally to run additional tests for leaks.
 | 
			
		||||
    // By default, this test runs on TC only, but this test doesn't take a lot of time,
 | 
			
		||||
    //   so we can turn it on for local development
 | 
			
		||||
@@ -171,6 +157,9 @@ tasks {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  compileJava {
 | 
			
		||||
    // CodeQL can't resolve the 'by project' property, so we need to give it a hint. This is the minimum version we need
 | 
			
		||||
    // so doesn't have to match exactly
 | 
			
		||||
    // Hint for the CodeQL autobuilder: sourceCompatibility = 17
 | 
			
		||||
    sourceCompatibility = javaVersion
 | 
			
		||||
    targetCompatibility = javaVersion
 | 
			
		||||
 | 
			
		||||
@@ -196,6 +185,10 @@ tasks {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  runIde {
 | 
			
		||||
    systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  downloadRobotServerPlugin {
 | 
			
		||||
    version.set(remoteRobotVersion)
 | 
			
		||||
  }
 | 
			
		||||
@@ -206,11 +199,33 @@ 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)
 | 
			
		||||
  // Add plugin open API sources to the plugin ZIP
 | 
			
		||||
  val createOpenApiSourceJar by registering(Jar::class) {
 | 
			
		||||
    // Java sources
 | 
			
		||||
    from(sourceSets.main.get().java) {
 | 
			
		||||
      include("**/com/maddyhome/idea/vim/**/*.java")
 | 
			
		||||
    }
 | 
			
		||||
    from(project(":vim-engine").sourceSets.main.get().java) {
 | 
			
		||||
      include("**/com/maddyhome/idea/vim/**/*.java")
 | 
			
		||||
    }
 | 
			
		||||
    // Kotlin sources
 | 
			
		||||
    from(kotlin.sourceSets.main.get().kotlin) {
 | 
			
		||||
      include("**/com/maddyhome/idea/vim/**/*.kt")
 | 
			
		||||
    }
 | 
			
		||||
    from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
 | 
			
		||||
      include("**/com/maddyhome/idea/vim/**/*.kt")
 | 
			
		||||
    }
 | 
			
		||||
    destinationDirectory.set(layout.buildDirectory.dir("libs"))
 | 
			
		||||
    archiveClassifier.set("src")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  buildPlugin {
 | 
			
		||||
    dependsOn(createOpenApiSourceJar)
 | 
			
		||||
    from(createOpenApiSourceJar) { into("lib/src") }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -221,7 +236,6 @@ java {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
kotlin {
 | 
			
		||||
  explicitApi()
 | 
			
		||||
  jvmToolchain {
 | 
			
		||||
    languageVersion.set(JavaLanguageVersion.of(javaVersion))
 | 
			
		||||
  }
 | 
			
		||||
@@ -266,48 +280,6 @@ tasks {
 | 
			
		||||
    teamCityOutputFormat.set(true)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  generateGrammarSource {
 | 
			
		||||
    maxHeapSize = "128m"
 | 
			
		||||
    arguments.addAll(listOf("-package", "com.maddyhome.idea.vim.vimscript.parser.generated", "-visitor"))
 | 
			
		||||
    outputDirectory = file("src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  named("compileKotlin") {
 | 
			
		||||
    dependsOn("generateGrammarSource")
 | 
			
		||||
  }
 | 
			
		||||
  named("compileTestKotlin") {
 | 
			
		||||
    dependsOn("generateTestGrammarSource")
 | 
			
		||||
  }
 | 
			
		||||
  named("compileTestFixturesKotlin") {
 | 
			
		||||
    dependsOn("generateTestFixturesGrammarSource")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Add plugin open API sources to the plugin ZIP
 | 
			
		||||
  val createOpenApiSourceJar by registering(Jar::class) {
 | 
			
		||||
    dependsOn("generateGrammarSource")
 | 
			
		||||
    // Java sources
 | 
			
		||||
    from(sourceSets.main.get().java) {
 | 
			
		||||
      include("**/com/maddyhome/idea/vim/**/*.java")
 | 
			
		||||
    }
 | 
			
		||||
    from(project(":vim-engine").sourceSets.main.get().java) {
 | 
			
		||||
      include("**/com/maddyhome/idea/vim/**/*.java")
 | 
			
		||||
    }
 | 
			
		||||
    // Kotlin sources
 | 
			
		||||
    from(kotlin.sourceSets.main.get().kotlin) {
 | 
			
		||||
      include("**/com/maddyhome/idea/vim/**/*.kt")
 | 
			
		||||
    }
 | 
			
		||||
    from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
 | 
			
		||||
      include("**/com/maddyhome/idea/vim/**/*.kt")
 | 
			
		||||
    }
 | 
			
		||||
    destinationDirectory.set(layout.buildDirectory.dir("libs"))
 | 
			
		||||
    archiveClassifier.set("src")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  buildPlugin {
 | 
			
		||||
    dependsOn(createOpenApiSourceJar)
 | 
			
		||||
    from(createOpenApiSourceJar) { into("lib/src") }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  patchPluginXml {
 | 
			
		||||
    // Don't forget to update plugin.xml
 | 
			
		||||
    sinceBuild.set("241.15989.150")
 | 
			
		||||
@@ -318,14 +290,21 @@ tasks {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Tests
 | 
			
		||||
 | 
			
		||||
tasks {
 | 
			
		||||
  test {
 | 
			
		||||
    useJUnitPlatform()
 | 
			
		||||
  }
 | 
			
		||||
ksp {
 | 
			
		||||
  arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
 | 
			
		||||
  arg("vimscript_functions_file", "intellij_vimscript_functions.json")
 | 
			
		||||
  arg("ex_commands_file", "intellij_ex_commands.json")
 | 
			
		||||
  arg("commands_file", "intellij_commands.json")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
afterEvaluate {
 | 
			
		||||
//  tasks.named("kspKotlin").configure { dependsOn("clean") }
 | 
			
		||||
  tasks.named("kspTestFixturesKotlin").configure { enabled = false }
 | 
			
		||||
  tasks.named("kspTestFixturesKotlin").configure { enabled = false }
 | 
			
		||||
  tasks.named("kspTestKotlin").configure { enabled = false }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// --- Changelog
 | 
			
		||||
 | 
			
		||||
changelog {
 | 
			
		||||
@@ -451,6 +430,8 @@ val fixVersionsElementType = "VersionBundleElement"
 | 
			
		||||
tasks.register("releaseActions") {
 | 
			
		||||
  group = "other"
 | 
			
		||||
  doLast {
 | 
			
		||||
    if (releaseType == "patch") return@doLast
 | 
			
		||||
 | 
			
		||||
    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")
 | 
			
		||||
 
 | 
			
		||||
@@ -129,8 +129,26 @@ Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-
 | 
			
		||||
      </details>
 | 
			
		||||
   
 | 
			
		||||
### Instructions
 | 
			
		||||
   
 | 
			
		||||
https://github.com/terryma/vim-multiple-cursors/blob/master/doc/multiple_cursors.txt
 | 
			
		||||
 | 
			
		||||
At the moment, the default key binds for this plugin do not get mapped correctly in IdeaVim (see [VIM-2178](https://youtrack.jetbrains.com/issue/VIM-2178)). To enable the default key binds, add the following to your `.ideavimrc` file...
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
" Remap multiple-cursors shortcuts to match terryma/vim-multiple-cursors
 | 
			
		||||
nmap <C-n> <Plug>NextWholeOccurrence
 | 
			
		||||
xmap <C-n> <Plug>NextWholeOccurrence
 | 
			
		||||
nmap g<C-n> <Plug>NextOccurrence
 | 
			
		||||
xmap g<C-n> <Plug>NextOccurrence
 | 
			
		||||
xmap <C-x> <Plug>SkipOccurrence
 | 
			
		||||
xmap <C-p> <Plug>RemoveOccurrence
 | 
			
		||||
 | 
			
		||||
" Note that the default <A-n> and g<A-n> shortcuts don't work on Mac due to dead keys.
 | 
			
		||||
" <A-n> is used to enter accented text e.g. ñ
 | 
			
		||||
" Feel free to pick your own mappings that are not affected. I like to use <leader>
 | 
			
		||||
nmap <leader><C-n> <Plug>AllWholeOccurrences
 | 
			
		||||
xmap <leader><C-n> <Plug>AllWholeOccurrences
 | 
			
		||||
nmap <leader>g<C-n> <Plug>AllOccurrences
 | 
			
		||||
xmap <leader>g<C-n> <Plug>AllOccurrences
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,30 +40,33 @@ Plug 'nerdtree'
 | 
			
		||||
- `:NERDTreeFind`
 | 
			
		||||
- `:NERDTreeRefreshRoot`
 | 
			
		||||
 | 
			
		||||
| Key     |  Description                                            |  Map Setting                   |
 | 
			
		||||
|---------|---------------------------------------------------------|--------------------------------|
 | 
			
		||||
| `o`     |  Open files, directories and bookmarks                  | `g:NERDTreeMapActivateNode`    |
 | 
			
		||||
| `go`    |  Open selected file, but leave cursor in the NERDTree   | `g:NERDTreeMapPreview`         |
 | 
			
		||||
| `t`     |  Open selected node/bookmark in a new tab               | `g:NERDTreeMapOpenInTab`       |
 | 
			
		||||
| `T`     |  Same as 't' but keep the focus on the current tab      | `g:NERDTreeMapOpenInTabSilent` |
 | 
			
		||||
| `i`     |  Open selected file in a split window                   | `g:NERDTreeMapOpenSplit`       |
 | 
			
		||||
| `gi`    |  Same as i, but leave the cursor on the NERDTree        | `g:NERDTreeMapPreviewSplit`    |
 | 
			
		||||
| `s`     |  Open selected file in a new vsplit                     | `g:NERDTreeMapOpenVSplit`      |
 | 
			
		||||
| `gs`    |  Same as s, but leave the cursor on the NERDTree        | `g:NERDTreeMapPreviewVSplit`   |
 | 
			
		||||
| `O`     |  Recursively open the selected directory                | `g:NERDTreeMapOpenRecursively` |
 | 
			
		||||
| `x`     |  Close the current nodes parent                         | `g:NERDTreeMapCloseDir`        |
 | 
			
		||||
| `X`     |  Recursively close all children of the current node     | `g:NERDTreeMapCloseChildren`   |
 | 
			
		||||
| `P`     |  Jump to the root node                                  | `g:NERDTreeMapJumpRoot`        |
 | 
			
		||||
| `p`     |  Jump to current nodes parent                           | `g:NERDTreeMapJumpParent`      | 
 | 
			
		||||
| `K`     |  Jump up inside directories at the current tree depth   | `g:NERDTreeMapJumpFirstChild`  |
 | 
			
		||||
| `J`     |  Jump down inside directories at the current tree depth | `g:NERDTreeMapJumpLastChild`   |
 | 
			
		||||
| `<C-J>` |  Jump down to next sibling of the current directory     | `g:NERDTreeMapJumpNextSibling` |
 | 
			
		||||
| `<C-K>` |  Jump up to previous sibling of the current directory   | `g:NERDTreeMapJumpPrevSibling` |
 | 
			
		||||
| `r`     |  Recursively refresh the current directory              | `g:NERDTreeMapRefresh`         |
 | 
			
		||||
| `R`     |  Recursively refresh the current root                   | `g:NERDTreeMapRefreshRoot`     |
 | 
			
		||||
| `m`     |  Display the NERDTree menu                              | `g:NERDTreeMapMenu`            |
 | 
			
		||||
| `q`     |  Close the NERDTree window                              | `g:NERDTreeMapQuit`            |
 | 
			
		||||
| `A`     |  Zoom (maximize/minimize) the NERDTree window           | `g:NERDTreeMapToggleZoom`      |
 | 
			
		||||
| Key     | Description                                            | Map Setting                    |
 | 
			
		||||
|---------|--------------------------------------------------------|--------------------------------|
 | 
			
		||||
| `o`     | Open files, directories and bookmarks                  | `g:NERDTreeMapActivateNode`    |
 | 
			
		||||
| `go`    | Open selected file, but leave cursor in the NERDTree   | `g:NERDTreeMapPreview`         |
 | 
			
		||||
| `t`     | Open selected node/bookmark in a new tab               | `g:NERDTreeMapOpenInTab`       |
 | 
			
		||||
| `T`     | Same as 't' but keep the focus on the current tab      | `g:NERDTreeMapOpenInTabSilent` |
 | 
			
		||||
| `i`     | Open selected file in a split window                   | `g:NERDTreeMapOpenSplit`       |
 | 
			
		||||
| `gi`    | Same as i, but leave the cursor on the NERDTree        | `g:NERDTreeMapPreviewSplit`    |
 | 
			
		||||
| `s`     | Open selected file in a new vsplit                     | `g:NERDTreeMapOpenVSplit`      |
 | 
			
		||||
| `gs`    | Same as s, but leave the cursor on the NERDTree        | `g:NERDTreeMapPreviewVSplit`   |
 | 
			
		||||
| `O`     | Recursively open the selected directory                | `g:NERDTreeMapOpenRecursively` |
 | 
			
		||||
| `x`     | Close the current nodes parent                         | `g:NERDTreeMapCloseDir`        |
 | 
			
		||||
| `X`     | Recursively close all children of the current node     | `g:NERDTreeMapCloseChildren`   |
 | 
			
		||||
| `P`     | Jump to the root node                                  | `g:NERDTreeMapJumpRoot`        |
 | 
			
		||||
| `p`     | Jump to current nodes parent                           | `g:NERDTreeMapJumpParent`      | 
 | 
			
		||||
| `K`     | Jump up inside directories at the current tree depth   | `g:NERDTreeMapJumpFirstChild`  |
 | 
			
		||||
| `J`     | Jump down inside directories at the current tree depth | `g:NERDTreeMapJumpLastChild`   |
 | 
			
		||||
| `<C-J>` | Jump down to next sibling of the current directory     | `g:NERDTreeMapJumpNextSibling` |
 | 
			
		||||
| `<C-K>` | Jump up to previous sibling of the current directory   | `g:NERDTreeMapJumpPrevSibling` |
 | 
			
		||||
| `r`     | Recursively refresh the current directory              | `g:NERDTreeMapRefresh`         |
 | 
			
		||||
| `R`     | Recursively refresh the current root                   | `g:NERDTreeMapRefreshRoot`     |
 | 
			
		||||
| `m`     | Display the NERDTree menu                              | `g:NERDTreeMapMenu`            |
 | 
			
		||||
| `q`     | Close the NERDTree window                              | `g:NERDTreeMapQuit`            |
 | 
			
		||||
| `A`     | Zoom (maximize/minimize) the NERDTree window           | `g:NERDTreeMapToggleZoom`      |
 | 
			
		||||
| `d`     | Delete file or directory                               | `g:NERDTreeMapDelete`          |
 | 
			
		||||
| `n`     | Create File                                            | `g:NERDTreeMapNewFile`         |
 | 
			
		||||
| `N`     | Create Directory                                       | `g:NERDTreeMapNewDir`          |
 | 
			
		||||
 | 
			
		||||
### Troubleshooting
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,18 +8,30 @@
 | 
			
		||||
 | 
			
		||||
# suppress inspection "UnusedProperty" for whole file
 | 
			
		||||
 | 
			
		||||
#ideaVersion=LATEST-EAP-SNAPSHOT
 | 
			
		||||
# ideaVersion is the version of the IDE that will be added as a compile-time dependency. The format can be either
 | 
			
		||||
# product version (e.g. 2024.1, 2024.1.1) or build (e.g. 241.15989.150, 241-EAP-SNAPSHOT). The dependency will be
 | 
			
		||||
# resolved against the configured repositories, which by default includes Maven releases and snapshots, the CDN used to
 | 
			
		||||
# download consumer releases, the plugin marketplace and so on.
 | 
			
		||||
# You can find an example list of all CDN based versions for IDEA Community here:
 | 
			
		||||
# https://data.services.jetbrains.com/products?code=IC
 | 
			
		||||
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
 | 
			
		||||
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
 | 
			
		||||
ideaVersion=2024.1.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-35
 | 
			
		||||
version=chylex-37
 | 
			
		||||
javaVersion=17
 | 
			
		||||
remoteRobotVersion=0.11.22
 | 
			
		||||
antlrVersion=4.10.1
 | 
			
		||||
 | 
			
		||||
kotlin.incremental.useClasspathSnapshot=false
 | 
			
		||||
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2
 | 
			
		||||
# Running IdeaVim in split mode requires 242. Update this version once 242 has been released, and remove it completely
 | 
			
		||||
# when IdeaVim targets 242, at which point runIdeSplitMode will run correctly with the target version.
 | 
			
		||||
# See also runIdeSplitMode
 | 
			
		||||
splitModeVersion=242-EAP-SNAPSHOT
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Please don't forget to update kotlin version in buildscript section
 | 
			
		||||
# Also update kotlinxSerializationVersion version
 | 
			
		||||
@@ -36,6 +48,7 @@ youtrackToken=
 | 
			
		||||
 | 
			
		||||
# Gradle settings
 | 
			
		||||
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
 | 
			
		||||
org.gradle.caching=true
 | 
			
		||||
 | 
			
		||||
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary
 | 
			
		||||
kotlin.stdlib.default.dependency=false
 | 
			
		||||
 
 | 
			
		||||
@@ -22,15 +22,15 @@ repositories {
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
 | 
			
		||||
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.11")
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.12")
 | 
			
		||||
  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.11")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.11")
 | 
			
		||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.12")
 | 
			
		||||
  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.9.0.202403050737-r")
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.0.202406032230-r")
 | 
			
		||||
  implementation("com.vdurmont:semver4j:3.1.0")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
// Set repository for snapshot versions of gradle plugin
 | 
			
		||||
pluginManagement {
 | 
			
		||||
  repositories {
 | 
			
		||||
    maven {
 | 
			
		||||
      url 'https://oss.sonatype.org/content/repositories/snapshots/'
 | 
			
		||||
    }
 | 
			
		||||
    gradlePluginPortal()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rootProject.name = 'IdeaVIM'
 | 
			
		||||
include 'vim-engine'
 | 
			
		||||
include 'scripts'
 | 
			
		||||
include 'annotation-processors'
 | 
			
		||||
include 'tests:java-tests'
 | 
			
		||||
include 'tests:property-tests'
 | 
			
		||||
include 'tests:long-running-tests'
 | 
			
		||||
include 'tests:ui-ij-tests'
 | 
			
		||||
include 'tests:ui-py-tests'
 | 
			
		||||
include 'tests:ui-fixtures'
 | 
			
		||||
							
								
								
									
										21
									
								
								settings.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								settings.gradle.kts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
// Set repository for snapshot versions of gradle plugin
 | 
			
		||||
pluginManagement {
 | 
			
		||||
  repositories {
 | 
			
		||||
    maven {
 | 
			
		||||
      url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
 | 
			
		||||
    }
 | 
			
		||||
    gradlePluginPortal()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rootProject.name = "IdeaVIM"
 | 
			
		||||
 | 
			
		||||
include("vim-engine")
 | 
			
		||||
include("scripts")
 | 
			
		||||
include("annotation-processors")
 | 
			
		||||
include("tests:java-tests")
 | 
			
		||||
include("tests:property-tests")
 | 
			
		||||
include("tests:long-running-tests")
 | 
			
		||||
include("tests:ui-ij-tests")
 | 
			
		||||
include("tests:ui-py-tests")
 | 
			
		||||
include("tests:ui-fixtures")
 | 
			
		||||
@@ -14,36 +14,36 @@ import com.maddyhome.idea.vim.key.MappingOwner
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
public object RegisterActions {
 | 
			
		||||
object RegisterActions {
 | 
			
		||||
  /**
 | 
			
		||||
   * Register all the key/action mappings for the plugin.
 | 
			
		||||
   */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun registerActions() {
 | 
			
		||||
  fun registerActions() {
 | 
			
		||||
    registerVimCommandActions()
 | 
			
		||||
    registerShortcutsWithoutActions()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun findAction(id: String): EditorActionHandlerBase? {
 | 
			
		||||
    val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
 | 
			
		||||
      ?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
 | 
			
		||||
  fun findAction(id: String): EditorActionHandlerBase? {
 | 
			
		||||
    val commandBean = IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id }
 | 
			
		||||
      ?: EngineCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
 | 
			
		||||
    return commandBean.instance
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun findActionOrDie(id: String): EditorActionHandlerBase {
 | 
			
		||||
  fun findActionOrDie(id: String): EditorActionHandlerBase {
 | 
			
		||||
    return findAction(id) ?: throw RuntimeException("Action $id is not registered")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun unregisterActions() {
 | 
			
		||||
  fun unregisterActions() {
 | 
			
		||||
    val keyGroup = VimPlugin.getKeyIfCreated()
 | 
			
		||||
    keyGroup?.unregisterCommandActions()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun registerVimCommandActions() {
 | 
			
		||||
    val parser = VimPlugin.getKey()
 | 
			
		||||
    EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
 | 
			
		||||
    IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
 | 
			
		||||
    EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun registerShortcutsWithoutActions() {
 | 
			
		||||
 
 | 
			
		||||
@@ -37,8 +37,8 @@ import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MacKeyRepeat;
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimListenerManager;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimInjector;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
 | 
			
		||||
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.services.VariableService;
 | 
			
		||||
import com.maddyhome.idea.vim.yank.YankGroupBase;
 | 
			
		||||
import org.jdom.Element;
 | 
			
		||||
@@ -46,6 +46,7 @@ import org.jetbrains.annotations.Nls;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
 | 
			
		||||
import static com.maddyhome.idea.vim.group.EditorGroup.EDITOR_STORE_ELEMENT;
 | 
			
		||||
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
 | 
			
		||||
import static com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc;
 | 
			
		||||
@@ -123,12 +124,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
    return (FileGroup)VimInjectorKt.getInjector().getFile();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static @NotNull SearchGroup getSearch() {
 | 
			
		||||
    return ApplicationManager.getApplication().getService(SearchGroup.class);
 | 
			
		||||
  public static @NotNull IjVimSearchGroup getSearch() {
 | 
			
		||||
    return ApplicationManager.getApplication().getService(IjVimSearchGroup.class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static @Nullable SearchGroup getSearchIfCreated() {
 | 
			
		||||
    return ApplicationManager.getApplication().getServiceIfCreated(SearchGroup.class);
 | 
			
		||||
  public static @Nullable IjVimSearchGroup getSearchIfCreated() {
 | 
			
		||||
    return ApplicationManager.getApplication().getServiceIfCreated(IjVimSearchGroup.class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static @NotNull ProcessGroup getProcess() {
 | 
			
		||||
@@ -283,11 +284,11 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
 | 
			
		||||
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
 | 
			
		||||
      try {
 | 
			
		||||
        VimInjectorKt.injector.getOptionGroup().startInitVimRc();
 | 
			
		||||
        injector.getOptionGroup().startInitVimRc();
 | 
			
		||||
        executeIdeaVimRc(editor);
 | 
			
		||||
      }
 | 
			
		||||
      finally {
 | 
			
		||||
        VimInjectorKt.injector.getOptionGroup().endInitVimRc();
 | 
			
		||||
        injector.getOptionGroup().endInitVimRc();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -345,14 +346,14 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void turnOffPlugin(boolean unsubscribe) {
 | 
			
		||||
    SearchGroup searchGroup = getSearchIfCreated();
 | 
			
		||||
    IjVimSearchGroup searchGroup = getSearchIfCreated();
 | 
			
		||||
    if (searchGroup != null) {
 | 
			
		||||
      searchGroup.turnOff();
 | 
			
		||||
    }
 | 
			
		||||
    if (unsubscribe) {
 | 
			
		||||
      VimListenerManager.INSTANCE.turnOff();
 | 
			
		||||
    }
 | 
			
		||||
    ExEntryPanel.fullReset();
 | 
			
		||||
    injector.getCommandLine().fullReset();
 | 
			
		||||
 | 
			
		||||
    // Unregister vim actions in command mode
 | 
			
		||||
    RegisterActions.unregisterActions();
 | 
			
		||||
 
 | 
			
		||||
@@ -12,13 +12,13 @@ import com.intellij.openapi.Disposable
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.maddyhome.idea.vim.group.EditorHolderService
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
			
		||||
 | 
			
		||||
@Service(Service.Level.PROJECT)
 | 
			
		||||
internal class VimProjectService(val project: Project) : Disposable {
 | 
			
		||||
  override fun dispose() {
 | 
			
		||||
    // Not sure if this is a best solution
 | 
			
		||||
    EditorHolderService.getInstance().editor = null
 | 
			
		||||
    ExEntryPanel.getInstance().editor = null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
 | 
			
		||||
 *   way to get ideavim keys for this plugin. See VIM-3085
 | 
			
		||||
 */
 | 
			
		||||
public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
 | 
			
		||||
class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
 | 
			
		||||
  private val handler = KeyHandler.getInstance()
 | 
			
		||||
  private val traceTime = injector.globalOptions().ideatracetime
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,6 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.action
 | 
			
		||||
 | 
			
		||||
public object IntellijCommandProvider : CommandProvider {
 | 
			
		||||
object IntellijCommandProvider : CommandProvider {
 | 
			
		||||
  override val commandListFileName: String = "intellij_commands.json"
 | 
			
		||||
}
 | 
			
		||||
@@ -26,7 +26,6 @@ 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.group.EditorHolderService
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.handler.enableOctopus
 | 
			
		||||
@@ -45,6 +44,7 @@ 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.ui.ex.ExEntryPanel
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExTextField
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
 | 
			
		||||
import java.awt.event.InputEvent
 | 
			
		||||
@@ -60,7 +60,7 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
 | 
			
		||||
 *   way to get ideavim keys for this plugin. See VIM-3085
 | 
			
		||||
 */
 | 
			
		||||
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
  private val traceTime: Boolean
 | 
			
		||||
    get() {
 | 
			
		||||
      // Make sure the injector is initialized
 | 
			
		||||
@@ -257,7 +257,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
 | 
			
		||||
  private fun getEditor(e: AnActionEvent): Editor? {
 | 
			
		||||
    return e.getData(PlatformDataKeys.EDITOR)
 | 
			
		||||
      ?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
 | 
			
		||||
        EditorHolderService.getInstance().editor
 | 
			
		||||
        ExEntryPanel.getInstance().editor
 | 
			
		||||
      } else {
 | 
			
		||||
        null
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inRepeatMode
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
 | 
			
		||||
@@ -102,7 +102,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
 | 
			
		||||
  override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
 | 
			
		||||
    val argument = cmd.argument ?: return false
 | 
			
		||||
    if (!editor.vimStateMachine.isDotRepeatInProgress) {
 | 
			
		||||
    if (!editor.inRepeatMode) {
 | 
			
		||||
      argumentCaptured = argument
 | 
			
		||||
    }
 | 
			
		||||
    val range = getMotionRange(editor, context, argument, operatorArguments)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["."], modes = [Mode.NORMAL])
 | 
			
		||||
@@ -25,7 +24,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_WRITABLE
 | 
			
		||||
 | 
			
		||||
  override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
 | 
			
		||||
    val state = editor.vimStateMachine
 | 
			
		||||
    val state = injector.vimState
 | 
			
		||||
    val lastCommand = VimRepeater.lastChangeCommand
 | 
			
		||||
 | 
			
		||||
    if (lastCommand == null && Extension.lastExtensionHandler == null) return false
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
 | 
			
		||||
public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
 | 
			
		||||
class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
  override fun runAsMulticaret(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
 | 
			
		||||
public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
 | 
			
		||||
class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
 | 
			
		||||
  override fun execute(
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL])
 | 
			
		||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
			
		||||
class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
 | 
			
		||||
  override fun executeForAllCarets(
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
@CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL])
 | 
			
		||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
			
		||||
class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
 | 
			
		||||
  override fun executeForAllCarets(
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,18 @@ internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELET
 | 
			
		||||
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN) {
 | 
			
		||||
  override val type: Command.Type = Command.Type.MOTION
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
 | 
			
		||||
 | 
			
		||||
  override fun execute(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
    cmd: Command,
 | 
			
		||||
    operatorArguments: OperatorArguments
 | 
			
		||||
  ): Boolean {
 | 
			
		||||
    val undo = injector.undo
 | 
			
		||||
    val nanoTime = System.nanoTime()
 | 
			
		||||
    editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
 | 
			
		||||
    return super.execute(editor, context, cmd, operatorArguments)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
 | 
			
		||||
@@ -48,6 +60,18 @@ internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB) {
 | 
			
		||||
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP) {
 | 
			
		||||
  override val type: Command.Type = Command.Type.MOTION
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
 | 
			
		||||
 | 
			
		||||
  override fun execute(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
    cmd: Command,
 | 
			
		||||
    operatorArguments: OperatorArguments
 | 
			
		||||
  ): Boolean {
 | 
			
		||||
    val undo = injector.undo
 | 
			
		||||
    val nanoTime = System.nanoTime()
 | 
			
		||||
    editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
 | 
			
		||||
    return super.execute(editor, context, cmd, operatorArguments)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["K"], modes = [Mode.NORMAL])
 | 
			
		||||
 
 | 
			
		||||
@@ -1,72 +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.command
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * COMPATIBILITY-LAYER: Additional class
 | 
			
		||||
 * Please see: https://jb.gg/zo8n0r
 | 
			
		||||
 */
 | 
			
		||||
public class CommandState(private val machine: VimStateMachine) {
 | 
			
		||||
 | 
			
		||||
  public val isOperatorPending: Boolean
 | 
			
		||||
    get() = machine.isOperatorPending(machine.mode)
 | 
			
		||||
 | 
			
		||||
  public val mode: Mode
 | 
			
		||||
    get() {
 | 
			
		||||
      val myMode = machine.mode
 | 
			
		||||
      return when (myMode) {
 | 
			
		||||
        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
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  public val commandBuilder: CommandBuilder
 | 
			
		||||
    get() = machine.commandBuilder
 | 
			
		||||
 | 
			
		||||
  public val mappingState: MappingState
 | 
			
		||||
    get() = machine.mappingState
 | 
			
		||||
 | 
			
		||||
  public enum class Mode {
 | 
			
		||||
    // Basic modes
 | 
			
		||||
    COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
 | 
			
		||||
 | 
			
		||||
    // Additional modes
 | 
			
		||||
    OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public enum class SubMode {
 | 
			
		||||
    NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    public fun getInstance(editor: Editor): CommandState {
 | 
			
		||||
      return CommandState(editor.vim.vimStateMachine)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal val CommandState.SubMode.engine: SelectionType
 | 
			
		||||
  get() = when (this) {
 | 
			
		||||
    CommandState.SubMode.NONE -> error("Unexpected value")
 | 
			
		||||
    CommandState.SubMode.VISUAL_CHARACTER -> SelectionType.CHARACTER_WISE
 | 
			
		||||
    CommandState.SubMode.VISUAL_LINE -> SelectionType.LINE_WISE
 | 
			
		||||
    CommandState.SubMode.VISUAL_BLOCK -> SelectionType.BLOCK_WISE
 | 
			
		||||
  }
 | 
			
		||||
@@ -12,18 +12,18 @@ import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.LogicalPosition
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
 | 
			
		||||
public class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) {
 | 
			
		||||
  public fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column
 | 
			
		||||
class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) {
 | 
			
		||||
  fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
    public fun fromOffset(editor: Editor, offset: Int): CharacterPosition {
 | 
			
		||||
  companion object {
 | 
			
		||||
    fun fromOffset(editor: Editor, offset: Int): CharacterPosition {
 | 
			
		||||
      // logical position "expands" tabs
 | 
			
		||||
      val logicalPosition = editor.offsetToLogicalPosition(offset)
 | 
			
		||||
      val lineStartOffset = editor.vim.getLineStartOffset(logicalPosition.line)
 | 
			
		||||
      return CharacterPosition(logicalPosition.line, offset - lineStartOffset)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public fun atCaret(editor: Editor): CharacterPosition {
 | 
			
		||||
    fun atCaret(editor: Editor): CharacterPosition {
 | 
			
		||||
      return fromOffset(editor, editor.caretModel.offset)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,62 +9,93 @@ package com.maddyhome.idea.vim.ex
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimExOutputPanel
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimOutputPanel
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimExOutput
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ExOutputPanel
 | 
			
		||||
import java.lang.ref.WeakReference
 | 
			
		||||
 | 
			
		||||
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
 | 
			
		||||
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
 | 
			
		||||
class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanel {
 | 
			
		||||
  private var isActiveInTestMode = false
 | 
			
		||||
 | 
			
		||||
  override val isActive: Boolean
 | 
			
		||||
  val editor get() = myEditor.get()
 | 
			
		||||
 | 
			
		||||
  val isActive: Boolean
 | 
			
		||||
    get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
      ExOutputPanel.isPanelActive(myEditor)
 | 
			
		||||
      editor?.let { ExOutputPanel.getNullablePanel(it) }?.myActive ?: false
 | 
			
		||||
    } else {
 | 
			
		||||
      isActiveInTestMode
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  override var text: String? = null
 | 
			
		||||
  override fun addText(text: String, isNewLine: Boolean) {
 | 
			
		||||
    if (this.text.isNotEmpty() && isNewLine) this.text += "\n$text" else this.text += text
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun show() {
 | 
			
		||||
    if (editor == null) return
 | 
			
		||||
    val currentPanel = injector.outputPanel.getCurrentOutputPanel()
 | 
			
		||||
    if (currentPanel != null && currentPanel != this) currentPanel.close()
 | 
			
		||||
 | 
			
		||||
    editor!!.vimExOutput = this
 | 
			
		||||
    val exOutputPanel = ExOutputPanel.getInstance(editor!!)
 | 
			
		||||
    if (!exOutputPanel.myActive) {
 | 
			
		||||
      if (ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
        isActiveInTestMode = true
 | 
			
		||||
      } else {
 | 
			
		||||
        exOutputPanel.activate()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override var text: String = ""
 | 
			
		||||
    get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
      ExOutputPanel.getInstance(myEditor).text
 | 
			
		||||
      editor?.let { ExOutputPanel.getInstance(it).text } ?: ""
 | 
			
		||||
    } else {
 | 
			
		||||
      // ExOutputPanel always returns a non-null string
 | 
			
		||||
      field
 | 
			
		||||
    }
 | 
			
		||||
    set(value) {
 | 
			
		||||
      // ExOutputPanel will strip a trailing newline. We'll do it now so that tests have the same behaviour. We also
 | 
			
		||||
      // never pass null to ExOutputPanel, but we do store it for tests, so we know if we're active or not
 | 
			
		||||
      val newValue = value.removeSuffix("\n")
 | 
			
		||||
      if (!ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
        ExOutputPanel.getInstance(myEditor).setText(value ?: "")
 | 
			
		||||
        editor?.let { ExOutputPanel.getInstance(it).setText(newValue) }
 | 
			
		||||
      } else {
 | 
			
		||||
        field = value
 | 
			
		||||
        isActiveInTestMode = !value.isNullOrEmpty()
 | 
			
		||||
        field = newValue
 | 
			
		||||
        isActiveInTestMode = newValue.isNotEmpty()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  override fun output(text: String) {
 | 
			
		||||
  fun output(text: String) {
 | 
			
		||||
    this.text = text
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun clear() {
 | 
			
		||||
    text = null
 | 
			
		||||
  fun clear() {
 | 
			
		||||
    text = ""
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun close() {
 | 
			
		||||
    if (!ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
      ExOutputPanel.getInstance(myEditor).close()
 | 
			
		||||
      editor?.let { ExOutputPanel.getInstance(it).close() }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      isActiveInTestMode = false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
  companion object {
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    public fun getInstance(editor: Editor): ExOutputModel {
 | 
			
		||||
    fun getInstance(editor: Editor): ExOutputModel {
 | 
			
		||||
      var model = editor.vimExOutput
 | 
			
		||||
      if (model == null) {
 | 
			
		||||
        model = ExOutputModel(editor)
 | 
			
		||||
        model = ExOutputModel(WeakReference(editor))
 | 
			
		||||
        editor.vimExOutput = model
 | 
			
		||||
      }
 | 
			
		||||
      return model
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun tryGetInstance(editor: Editor) = editor.vimExOutput
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ 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
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
@@ -25,8 +24,8 @@ 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.TestInputModel
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inRepeatMode
 | 
			
		||||
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
 | 
			
		||||
@@ -51,13 +50,13 @@ import javax.swing.KeyStroke
 | 
			
		||||
 *
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
public object VimExtensionFacade {
 | 
			
		||||
object VimExtensionFacade {
 | 
			
		||||
 | 
			
		||||
  private val LOG = logger<VimExtensionFacade>()
 | 
			
		||||
 | 
			
		||||
  /** The 'map' command for mapping keys to handlers defined in extensions. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun putExtensionHandlerMapping(
 | 
			
		||||
  fun putExtensionHandlerMapping(
 | 
			
		||||
    modes: Set<MappingMode>,
 | 
			
		||||
    fromKeys: List<KeyStroke>,
 | 
			
		||||
    pluginOwner: MappingOwner,
 | 
			
		||||
@@ -68,13 +67,15 @@ public object VimExtensionFacade {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * COMPATIBILITY-LAYER: Additional method
 | 
			
		||||
   * Please see: https://jb.gg/zo8n0r
 | 
			
		||||
   */
 | 
			
		||||
  /** The 'map' command for mapping keys to handlers defined in extensions. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun putExtensionHandlerMapping(
 | 
			
		||||
  @Deprecated(
 | 
			
		||||
    "Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
 | 
			
		||||
    ReplaceWith(
 | 
			
		||||
      "VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
 | 
			
		||||
      "com.maddyhome.idea.vim.VimPlugin"
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  fun putExtensionHandlerMapping(
 | 
			
		||||
    modes: Set<MappingMode>,
 | 
			
		||||
    fromKeys: List<KeyStroke>,
 | 
			
		||||
    pluginOwner: MappingOwner,
 | 
			
		||||
@@ -86,7 +87,7 @@ public object VimExtensionFacade {
 | 
			
		||||
 | 
			
		||||
  /** The 'map' command for mapping keys to other keys. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun putKeyMapping(
 | 
			
		||||
  fun putKeyMapping(
 | 
			
		||||
    modes: Set<MappingMode>,
 | 
			
		||||
    fromKeys: List<KeyStroke>,
 | 
			
		||||
    pluginOwner: MappingOwner,
 | 
			
		||||
@@ -98,7 +99,7 @@ public object VimExtensionFacade {
 | 
			
		||||
 | 
			
		||||
  /** The 'map' command for mapping keys to other keys if there is no other mapping to these keys */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun putKeyMappingIfMissing(
 | 
			
		||||
  fun putKeyMappingIfMissing(
 | 
			
		||||
    modes: Set<MappingMode>,
 | 
			
		||||
    fromKeys: List<KeyStroke>,
 | 
			
		||||
    pluginOwner: MappingOwner,
 | 
			
		||||
@@ -112,7 +113,7 @@ public object VimExtensionFacade {
 | 
			
		||||
  /**
 | 
			
		||||
   * Equivalent to calling 'command' to set up a user-defined command or alias
 | 
			
		||||
   */
 | 
			
		||||
  public fun addCommand(
 | 
			
		||||
  fun addCommand(
 | 
			
		||||
    name: String,
 | 
			
		||||
    handler: CommandAliasHandler,
 | 
			
		||||
  ) {
 | 
			
		||||
@@ -123,7 +124,7 @@ public object VimExtensionFacade {
 | 
			
		||||
   * Equivalent to calling 'command' to set up a user-defined command or alias
 | 
			
		||||
   */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun addCommand(
 | 
			
		||||
  fun addCommand(
 | 
			
		||||
    name: String,
 | 
			
		||||
    minimumNumberOfArguments: Int,
 | 
			
		||||
    maximumNumberOfArguments: Int,
 | 
			
		||||
@@ -141,7 +142,7 @@ public object VimExtensionFacade {
 | 
			
		||||
   * leaves the editor in the insert mode if it's been activated.
 | 
			
		||||
   */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
 | 
			
		||||
  fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
 | 
			
		||||
    val context = injector.executionContextManager.getEditorExecutionContext(editor.vim)
 | 
			
		||||
    val keyHandler = KeyHandler.getInstance()
 | 
			
		||||
    keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
 | 
			
		||||
@@ -149,8 +150,8 @@ public object VimExtensionFacade {
 | 
			
		||||
 | 
			
		||||
  /** Returns a single key stroke from the user input similar to 'getchar()'. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun inputKeyStroke(editor: Editor): KeyStroke {
 | 
			
		||||
    if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
 | 
			
		||||
  fun inputKeyStroke(editor: Editor): KeyStroke {
 | 
			
		||||
    if (editor.vim.inRepeatMode) {
 | 
			
		||||
      val input = Extension.consumeKeystroke()
 | 
			
		||||
      LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
 | 
			
		||||
      return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
 | 
			
		||||
@@ -181,43 +182,43 @@ public object VimExtensionFacade {
 | 
			
		||||
 | 
			
		||||
  /** Returns a string typed in the input box similar to 'input()'. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
 | 
			
		||||
  fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
 | 
			
		||||
    return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Get the current contents of the given register similar to 'getreg()'. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun getRegister(register: Char): List<KeyStroke>? {
 | 
			
		||||
  fun getRegister(register: Char): List<KeyStroke>? {
 | 
			
		||||
    val reg = VimPlugin.getRegister().getRegister(register) ?: return null
 | 
			
		||||
    return reg.keys
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
 | 
			
		||||
    val reg = caret.registerStorage.getRegister(register) ?: return null
 | 
			
		||||
  fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
 | 
			
		||||
    val reg = injector.registerGroup.getRegister(register) ?: return null
 | 
			
		||||
    return reg.keys
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Set the current contents of the given register */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun setRegister(register: Char, keys: List<KeyStroke?>?) {
 | 
			
		||||
  fun setRegister(register: Char, keys: List<KeyStroke?>?) {
 | 
			
		||||
    VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Set the current contents of the given register */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
 | 
			
		||||
    caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
 | 
			
		||||
  fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
 | 
			
		||||
    injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Set the current contents of the given register */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
 | 
			
		||||
  fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
 | 
			
		||||
    VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun exportScriptFunction(
 | 
			
		||||
  fun exportScriptFunction(
 | 
			
		||||
    scope: Scope?,
 | 
			
		||||
    name: String,
 | 
			
		||||
    args: List<String>,
 | 
			
		||||
@@ -253,7 +254,7 @@ public object VimExtensionFacade {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) {
 | 
			
		||||
fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) {
 | 
			
		||||
  exportScriptFunction(null, name, listOf("type"), emptyList(), false, noneOfEnum()) {
 | 
			
		||||
    editor, context, args ->
 | 
			
		||||
 | 
			
		||||
@@ -274,6 +275,6 @@ public fun VimExtensionFacade.exportOperatorFunction(name: String, function: Ope
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun interface ScriptFunction {
 | 
			
		||||
  public fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
 | 
			
		||||
}
 | 
			
		||||
fun interface ScriptFunction {
 | 
			
		||||
  fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,12 @@ import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
 * COMPATIBILITY-LAYER: Created a class, renamed original class
 | 
			
		||||
 * Please see: https://jb.gg/zo8n0r
 | 
			
		||||
 */
 | 
			
		||||
public interface VimExtensionHandler : ExtensionHandler {
 | 
			
		||||
interface VimExtensionHandler : ExtensionHandler {
 | 
			
		||||
  override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
    execute(editor.ij, context.ij)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun execute(editor: Editor, context: DataContext)
 | 
			
		||||
  fun execute(editor: Editor, context: DataContext)
 | 
			
		||||
 | 
			
		||||
  public abstract class WithCallback : ExtensionHandler.WithCallback(), VimExtensionHandler
 | 
			
		||||
  abstract class WithCallback : ExtensionHandler.WithCallback(), VimExtensionHandler
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.argtextobj;
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Document;
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.api.*;
 | 
			
		||||
import com.maddyhome.idea.vim.command.*;
 | 
			
		||||
@@ -23,7 +24,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.state.KeyHandlerState;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
 | 
			
		||||
import org.jetbrains.annotations.Nls;
 | 
			
		||||
@@ -244,19 +245,18 @@ public class VimArgTextObjExtension implements VimExtension {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
 | 
			
		||||
 | 
			
		||||
      IjVimEditor vimEditor = (IjVimEditor) editor;
 | 
			
		||||
      @NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
 | 
			
		||||
      int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
 | 
			
		||||
      @NotNull KeyHandler keyHandler = KeyHandler.getInstance();
 | 
			
		||||
      @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
 | 
			
		||||
      int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
 | 
			
		||||
 | 
			
		||||
      final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
 | 
			
		||||
      //noinspection DuplicatedCode
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending(editor.getMode())) {
 | 
			
		||||
      if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
 | 
			
		||||
        editor.nativeCarets().forEach((VimCaret caret) -> {
 | 
			
		||||
          final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
 | 
			
		||||
          if (range != null) {
 | 
			
		||||
            try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
 | 
			
		||||
              if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
 | 
			
		||||
              if (editor.getMode() instanceof Mode.VISUAL) {
 | 
			
		||||
                com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true);
 | 
			
		||||
              } else {
 | 
			
		||||
                InlayHelperKt.moveToInlayAwareOffset(((IjVimCaret)caret).getCaret(), range.getStartOffset());
 | 
			
		||||
@@ -265,7 +265,7 @@ public class VimArgTextObjExtension implements VimExtension {
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
 | 
			
		||||
        keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
 | 
			
		||||
                                                                                         textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class))));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import com.intellij.psi.PsiElement
 | 
			
		||||
import com.intellij.psi.PsiFile
 | 
			
		||||
import com.intellij.psi.PsiWhiteSpace
 | 
			
		||||
import com.intellij.psi.util.PsiTreeUtil
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
@@ -45,7 +46,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
 | 
			
		||||
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
 | 
			
		||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
@@ -64,7 +64,7 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
      selectionType: SelectionType,
 | 
			
		||||
      resetCaret: Boolean = true,
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
      val mode = editor.vimStateMachine.mode
 | 
			
		||||
      val mode = editor.mode
 | 
			
		||||
      if (mode !is Mode.VISUAL) {
 | 
			
		||||
        editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset)
 | 
			
		||||
      }
 | 
			
		||||
@@ -183,10 +183,10 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
    override val isRepeatable = true
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      val commandState = editor.vimStateMachine
 | 
			
		||||
 | 
			
		||||
      val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
 | 
			
		||||
      commandState.commandBuilder.completeCommandPart(Argument(command))
 | 
			
		||||
 | 
			
		||||
      val keyState = KeyHandler.getInstance().keyHandlerState
 | 
			
		||||
      keyState.commandBuilder.completeCommandPart(Argument(command))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import com.intellij.openapi.vfs.VirtualFile
 | 
			
		||||
import com.intellij.psi.PsiComment
 | 
			
		||||
import com.intellij.psi.PsiElement
 | 
			
		||||
import com.intellij.psi.util.PsiTreeUtil
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
@@ -40,7 +41,6 @@ import com.maddyhome.idea.vim.handler.toMotionOrError
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.PsiHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
@@ -91,22 +91,23 @@ internal class Matchit : VimExtension {
 | 
			
		||||
  private class MatchitHandler(private val reverse: Boolean) : ExtensionHandler {
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      val commandState = editor.vimStateMachine
 | 
			
		||||
      val count = commandState.commandBuilder.count
 | 
			
		||||
      val keyHandler = KeyHandler.getInstance()
 | 
			
		||||
      val keyState = keyHandler.keyHandlerState
 | 
			
		||||
      val count = keyState.commandBuilder.count
 | 
			
		||||
 | 
			
		||||
      // Reset the command count so it doesn't transfer onto subsequent commands.
 | 
			
		||||
      editor.vimStateMachine.commandBuilder.resetCount()
 | 
			
		||||
      keyState.commandBuilder.resetCount()
 | 
			
		||||
 | 
			
		||||
      // 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(editor.mode)
 | 
			
		||||
      val isInOpPending = keyHandler.isOperatorPending(editor.mode, keyState)
 | 
			
		||||
 | 
			
		||||
      if (isInOpPending) {
 | 
			
		||||
        val matchitAction = MatchitAction()
 | 
			
		||||
        matchitAction.reverse = reverse
 | 
			
		||||
        matchitAction.isInOpPending = true
 | 
			
		||||
 | 
			
		||||
        commandState.commandBuilder.completeCommandPart(
 | 
			
		||||
        keyState.commandBuilder.completeCommandPart(
 | 
			
		||||
          Argument(
 | 
			
		||||
            Command(
 | 
			
		||||
              count,
 | 
			
		||||
 
 | 
			
		||||
@@ -30,11 +30,11 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.vimSetSelection
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchOptions
 | 
			
		||||
import com.maddyhome.idea.vim.helper.endOffsetInclusive
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import com.maddyhome.idea.vim.helper.exitVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.findWordUnderCursor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
 | 
			
		||||
import com.maddyhome.idea.vim.helper.userData
 | 
			
		||||
@@ -235,7 +235,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
 | 
			
		||||
      val text = if (editor.inVisualMode) {
 | 
			
		||||
        primaryCaret.selectedText ?: return
 | 
			
		||||
      } else {
 | 
			
		||||
        val range = SearchHelper.findWordUnderCursor(editor, primaryCaret) ?: return
 | 
			
		||||
        val range = findWordUnderCursor(editor, primaryCaret) ?: return
 | 
			
		||||
        if (range.startOffset > primaryCaret.offset) return
 | 
			
		||||
        IjVimEditor(editor).getText(range)
 | 
			
		||||
      }
 | 
			
		||||
@@ -300,7 +300,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? {
 | 
			
		||||
    val range = SearchHelper.findWordUnderCursor(editor, caret) ?: return null
 | 
			
		||||
    val range = findWordUnderCursor(editor, caret) ?: return null
 | 
			
		||||
    if (range.startOffset > caret.offset) return null
 | 
			
		||||
 | 
			
		||||
    enterVisualMode(editor.vim)
 | 
			
		||||
@@ -327,6 +327,6 @@ internal class VimMultipleCursorsExtension : VimExtension {
 | 
			
		||||
 | 
			
		||||
  private fun makePattern(text: String, whole: Boolean): String {
 | 
			
		||||
    // Pattern is "very nomagic" (ignore regex chars) and "force case sensitive". This is vim-multiple-cursors behaviour
 | 
			
		||||
    return "\\V\\C" + SearchHelper.makeSearchPattern(text, whole)
 | 
			
		||||
    return "\\V\\C" + if (whole) "\\<$text\\>" else text
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -478,6 +478,9 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
      NerdAction.ToIj("SynchronizeCurrentFile"),
 | 
			
		||||
    )
 | 
			
		||||
    registerCommand("NERDTreeMapToggleHidden", "I", NerdAction.ToIj("ProjectView.ShowExcludedFiles"))
 | 
			
		||||
    registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile"))
 | 
			
		||||
    registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir"))
 | 
			
		||||
    registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete"))
 | 
			
		||||
    registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
 | 
			
		||||
    registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
 | 
			
		||||
    registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.extension.replacewithregister
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
@@ -28,7 +29,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
 | 
			
		||||
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.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
@@ -144,7 +144,7 @@ internal class ReplaceWithRegister : VimExtension {
 | 
			
		||||
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
 | 
			
		||||
  val savedRegister = registerGroup.getRegister(lastRegisterChar) ?: return
 | 
			
		||||
 | 
			
		||||
  var usedType = savedRegister.type
 | 
			
		||||
  var usedText = savedRegister.text
 | 
			
		||||
@@ -166,17 +166,18 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
 | 
			
		||||
    putToLine = -1,
 | 
			
		||||
  )
 | 
			
		||||
  val vimEditor = editor.vim
 | 
			
		||||
  val keyHandler = KeyHandler.getInstance()
 | 
			
		||||
  ClipboardOptionHelper.IdeaputDisabler().use {
 | 
			
		||||
    VimPlugin.getPut().putText(
 | 
			
		||||
      vimEditor,
 | 
			
		||||
      context.vim,
 | 
			
		||||
      putData,
 | 
			
		||||
      operatorArguments = OperatorArguments(
 | 
			
		||||
        editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,
 | 
			
		||||
        keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState),
 | 
			
		||||
        0,
 | 
			
		||||
        editor.vim.mode,
 | 
			
		||||
      ),
 | 
			
		||||
      saveToRegister = false
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.textobjentire;
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Caret;
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler;
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext;
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor;
 | 
			
		||||
@@ -23,7 +24,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.state.KeyHandlerState;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
@@ -133,17 +134,18 @@ public class VimTextObjEntireExtension implements VimExtension {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
 | 
			
		||||
      @NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(editor);
 | 
			
		||||
      int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
 | 
			
		||||
      @NotNull KeyHandler keyHandler = KeyHandler.getInstance();
 | 
			
		||||
      @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
 | 
			
		||||
      int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
 | 
			
		||||
 | 
			
		||||
      final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
 | 
			
		||||
      //noinspection DuplicatedCode
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending(editor.getMode())) {
 | 
			
		||||
      if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
 | 
			
		||||
        ((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
 | 
			
		||||
          final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
 | 
			
		||||
          if (range != null) {
 | 
			
		||||
            try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
 | 
			
		||||
              if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
 | 
			
		||||
              if (editor.getMode() instanceof Mode.VISUAL) {
 | 
			
		||||
                com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(new IjVimCaret(caret), range.getStartOffset(), range.getEndOffset() - 1, true);
 | 
			
		||||
              } else {
 | 
			
		||||
                InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
 | 
			
		||||
@@ -153,7 +155,7 @@ public class VimTextObjEntireExtension implements VimExtension {
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
 | 
			
		||||
        keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
 | 
			
		||||
                                                                                         textObjectHandler, Command.Type.MOTION,
 | 
			
		||||
                                                                                         EnumSet.noneOf(CommandFlags.class))));
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.textobjindent;
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Caret;
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler;
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext;
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor;
 | 
			
		||||
@@ -24,7 +25,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.state.KeyHandlerState;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
@@ -263,17 +264,18 @@ public class VimIndentObject implements VimExtension {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
 | 
			
		||||
      IjVimEditor vimEditor = (IjVimEditor)editor;
 | 
			
		||||
      @NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
 | 
			
		||||
      int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
 | 
			
		||||
      @NotNull KeyHandler keyHandler = KeyHandler.getInstance();
 | 
			
		||||
      @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
 | 
			
		||||
      int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
 | 
			
		||||
 | 
			
		||||
      final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
 | 
			
		||||
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending(editor.getMode())) {
 | 
			
		||||
      if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
 | 
			
		||||
        ((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
 | 
			
		||||
          final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
 | 
			
		||||
          if (range != null) {
 | 
			
		||||
            try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
 | 
			
		||||
              if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
 | 
			
		||||
              if (editor.getMode() instanceof Mode.VISUAL) {
 | 
			
		||||
                EngineVisualGroupKt.vimSetSelection(new IjVimCaret(caret), range.getStartOffset(), range.getEndOffset() - 1, true);
 | 
			
		||||
              } else {
 | 
			
		||||
                InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
 | 
			
		||||
@@ -283,7 +285,7 @@ public class VimIndentObject implements VimExtension {
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
 | 
			
		||||
        keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
 | 
			
		||||
                                                                                         textObjectHandler, Command.Type.MOTION,
 | 
			
		||||
                                                                                         EnumSet.noneOf(CommandFlags.class))));
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -57,8 +57,9 @@ import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.NumberType
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.endOffsetInclusive
 | 
			
		||||
import com.maddyhome.idea.vim.helper.findNumberUnderCursor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.findNumbersInRange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inInsertMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
@@ -83,7 +84,7 @@ import kotlin.math.max
 | 
			
		||||
/**
 | 
			
		||||
 * Provides all the insert/replace related functionality
 | 
			
		||||
 */
 | 
			
		||||
public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
  private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
 | 
			
		||||
  private val listener: EditorMouseListener = object : EditorMouseListener {
 | 
			
		||||
    override fun mouseClicked(event: EditorMouseEvent) {
 | 
			
		||||
@@ -94,7 +95,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun editorCreated(editor: Editor?, disposable: Disposable) {
 | 
			
		||||
  fun editorCreated(editor: Editor?, disposable: Disposable) {
 | 
			
		||||
    EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -102,6 +103,9 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
    val ijContext = context.ij
 | 
			
		||||
    val doc = vimEditor.editor.document
 | 
			
		||||
    val undo = injector.undo
 | 
			
		||||
    val nanoTime = System.nanoTime()
 | 
			
		||||
    vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
 | 
			
		||||
    CommandProcessor.getInstance().executeCommand(
 | 
			
		||||
      editor.project, {
 | 
			
		||||
        ApplicationManager.getApplication()
 | 
			
		||||
@@ -650,7 +654,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    val alpha = nf.contains("alpha")
 | 
			
		||||
    val hex = nf.contains("hex")
 | 
			
		||||
    val octal = nf.contains("octal")
 | 
			
		||||
    val numberRanges = SearchHelper.findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
 | 
			
		||||
    val numberRanges = findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
 | 
			
		||||
    val newNumbers: MutableList<String?> = ArrayList()
 | 
			
		||||
    for (i in numberRanges.indices) {
 | 
			
		||||
      val numberRange = numberRanges[i]
 | 
			
		||||
@@ -673,8 +677,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    val alpha = nf.contains("alpha")
 | 
			
		||||
    val hex = nf.contains("hex")
 | 
			
		||||
    val octal = nf.contains("octal")
 | 
			
		||||
    val range =
 | 
			
		||||
      SearchHelper.findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
 | 
			
		||||
    val range = findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
 | 
			
		||||
    if (range == null) {
 | 
			
		||||
      logger.debug("no number on line")
 | 
			
		||||
      return false
 | 
			
		||||
@@ -783,11 +786,11 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    return number
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun addInsertListener(listener: VimInsertListener) {
 | 
			
		||||
  fun addInsertListener(listener: VimInsertListener) {
 | 
			
		||||
    insertListeners.add(listener)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun removeInsertListener(listener: VimInsertListener) {
 | 
			
		||||
  fun removeInsertListener(listener: VimInsertListener) {
 | 
			
		||||
    insertListeners.remove(listener)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,11 +20,13 @@ import com.intellij.openapi.components.Storage;
 | 
			
		||||
import com.intellij.openapi.editor.*;
 | 
			
		||||
import com.intellij.openapi.editor.event.CaretEvent;
 | 
			
		||||
import com.intellij.openapi.editor.event.CaretListener;
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorEx;
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
 | 
			
		||||
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.ex.ExOutputModel;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CommandStateHelper;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper;
 | 
			
		||||
@@ -38,6 +40,8 @@ import org.jetbrains.annotations.NonNls;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.beans.PropertyChangeEvent;
 | 
			
		||||
import java.beans.PropertyChangeListener;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
@@ -45,6 +49,7 @@ import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
import static com.intellij.openapi.editor.EditorSettings.*;
 | 
			
		||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
 | 
			
		||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.options;
 | 
			
		||||
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -208,43 +213,11 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
 | 
			
		||||
    initLineNumbers(editor);
 | 
			
		||||
 | 
			
		||||
    // We add Vim bindings to all opened editors, even read-only editors. We also add bindings to editors that are used
 | 
			
		||||
    // elsewhere in the IDE, rather than just for editing project files. This includes editors used as part of the UI,
 | 
			
		||||
    // such as the VCS commit message, or used as read-only viewers for text output, such as log files in run
 | 
			
		||||
    // configurations or the Git Console tab. And editors are used for interactive stdin/stdout for console-based run
 | 
			
		||||
    // configurations.
 | 
			
		||||
    // We want to provide an intuitive experience for working with these additional editors, so we automatically switch
 | 
			
		||||
    // to INSERT mode for interactive editors. Recognising these can be a bit tricky.
 | 
			
		||||
    // These additional interactive editors are not file-based, but must have a writable document. However, log output
 | 
			
		||||
    // documents are also writable (the IDE is writing new content as it becomes available) just not user-editable. So
 | 
			
		||||
    // we must also check that the editor is not in read-only "viewer" mode (this includes "rendered" mode, which is
 | 
			
		||||
    // read-only and also hides the caret).
 | 
			
		||||
    // Furthermore, the interactive stdin/stdout console output is hosted in a read-only editor, but it can still be
 | 
			
		||||
    // edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's `isViewer` property and
 | 
			
		||||
    // allows typing if the associated process (if any) is still running. We can get the editor's console view and check
 | 
			
		||||
    // this ourselves, but we have to wait until the editor has finished initialising before it's available in user
 | 
			
		||||
    // data.
 | 
			
		||||
    // 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 context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor));
 | 
			
		||||
      VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
 | 
			
		||||
      KeyHandler.getInstance().reset(new IjVimEditor(editor));
 | 
			
		||||
    };
 | 
			
		||||
    if (!editor.isViewer() &&
 | 
			
		||||
        !EditorHelper.isFileEditor(editor) &&
 | 
			
		||||
        editor.getDocument().isWritable() &&
 | 
			
		||||
        !CommandStateHelper.inInsertMode(editor)) {
 | 
			
		||||
      switchToInsertMode.run();
 | 
			
		||||
    // Listen for changes to the font size, so we can hide the ex text field/output panel
 | 
			
		||||
    if (editor instanceof EditorEx editorEx) {
 | 
			
		||||
      editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE);
 | 
			
		||||
    }
 | 
			
		||||
    ApplicationManager.getApplication().invokeLater(
 | 
			
		||||
      () -> {
 | 
			
		||||
        if (editor.isDisposed()) return;
 | 
			
		||||
        ConsoleViewImpl consoleView = editor.getUserData(ConsoleViewImpl.CONSOLE_VIEW_IN_EDITOR_VIEW);
 | 
			
		||||
        if (consoleView != null && consoleView.isRunning() && !CommandStateHelper.inInsertMode(editor)) {
 | 
			
		||||
          switchToInsertMode.run();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    updateCaretsVisualAttributes(new IjVimEditor(editor));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -253,6 +226,9 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
    UserDataManager.unInitializeEditor(editor);
 | 
			
		||||
    VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
 | 
			
		||||
    CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
 | 
			
		||||
    if (editor instanceof EditorEx editorEx) {
 | 
			
		||||
      editorEx.removePropertyChangeListener(FontSizeChangeListener.INSTANCE);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void notifyIdeaJoin(@Nullable Project project, @NotNull VimEditor editor) {
 | 
			
		||||
@@ -315,7 +291,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
    @Override
 | 
			
		||||
    public Integer convert(@NotNull Editor editor, int lineNumber) {
 | 
			
		||||
      final IjVimEditor ijVimEditor = new IjVimEditor(editor);
 | 
			
		||||
      final boolean number = ijOptions(injector, ijVimEditor).getNumber();
 | 
			
		||||
      final boolean number = options(injector, ijVimEditor).getNumber();
 | 
			
		||||
      final int caretLine = editor.getCaretModel().getLogicalPosition().line;
 | 
			
		||||
 | 
			
		||||
      // lineNumber is 1 based
 | 
			
		||||
@@ -382,4 +358,33 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
      return Stream.empty();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Listens to property changes from the editor to hide ex text field/output panel when the editor's font is zoomed
 | 
			
		||||
   */
 | 
			
		||||
  private static class FontSizeChangeListener implements PropertyChangeListener {
 | 
			
		||||
    public static FontSizeChangeListener INSTANCE = new FontSizeChangeListener();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void propertyChange(PropertyChangeEvent evt) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return;
 | 
			
		||||
      if (evt.getPropertyName().equals(EditorEx.PROP_FONT_SIZE)) {
 | 
			
		||||
        Object source = evt.getSource();
 | 
			
		||||
        if (source instanceof Editor editor) {
 | 
			
		||||
          // The editor is being zoomed, so hide the command line or output panel, if they're being shown. On the one
 | 
			
		||||
          // hand, it's a little rude to cancel a command line for the user, but on the other, the panels obscure the
 | 
			
		||||
          // zoom indicator, so it looks nicer if we hide them.
 | 
			
		||||
          // Note that IDE scale is handled by LafManager.lookAndFeelChanged
 | 
			
		||||
          VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine();
 | 
			
		||||
          if (activeCommandLine != null) {
 | 
			
		||||
            injector.getProcessGroup().cancelExEntry(new IjVimEditor(editor), true, false);
 | 
			
		||||
          }
 | 
			
		||||
          ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor);
 | 
			
		||||
          if (exOutputModel != null) {
 | 
			
		||||
            exOutputModel.close();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2023 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class EditorHolderService {
 | 
			
		||||
  var editor: Editor? = null
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun getInstance(): EditorHolderService = service()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,476 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2023 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.group;
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext;
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys;
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager;
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger;
 | 
			
		||||
import com.intellij.openapi.editor.Document;
 | 
			
		||||
import com.intellij.openapi.editor.Editor;
 | 
			
		||||
import com.intellij.openapi.editor.LogicalPosition;
 | 
			
		||||
import com.intellij.openapi.fileEditor.*;
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.EditorWindow;
 | 
			
		||||
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.common.TextRange;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelperRt;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper;
 | 
			
		||||
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;
 | 
			
		||||
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
 | 
			
		||||
 | 
			
		||||
public class FileGroup extends VimFileBase {
 | 
			
		||||
  public boolean openFile(@NotNull String filename, @NotNull ExecutionContext context) {
 | 
			
		||||
    if (logger.isDebugEnabled()) {
 | 
			
		||||
      logger.debug("openFile(" + filename + ")");
 | 
			
		||||
    }
 | 
			
		||||
    final Project project = PlatformDataKeys.PROJECT.getData(((IjEditorExecutionContext) context).getContext()); // API change - don't merge
 | 
			
		||||
    if (project == null) return false;
 | 
			
		||||
 | 
			
		||||
    VirtualFile found = findFile(filename, project);
 | 
			
		||||
 | 
			
		||||
    if (found != null) {
 | 
			
		||||
      if (logger.isDebugEnabled()) {
 | 
			
		||||
        logger.debug("found file: " + found);
 | 
			
		||||
      }
 | 
			
		||||
      // Can't open a file unless it has a known file type. The next call will return the known type.
 | 
			
		||||
      // If unknown, IDEA will prompt the user to pick a type.
 | 
			
		||||
      FileType type = FileTypeManager.getInstance().getKnownFileTypeOrAssociate(found, project);
 | 
			
		||||
 | 
			
		||||
      //noinspection IfStatementWithIdenticalBranches
 | 
			
		||||
      if (type != null) {
 | 
			
		||||
        FileEditorManager fem = FileEditorManager.getInstance(project);
 | 
			
		||||
        fem.openFile(found, true);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // There was no type and user didn't pick one. Don't open the file
 | 
			
		||||
        // Return true here because we found the file but the user canceled by not picking a type.
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      VimPlugin.showMessage(MessageHelper.message("unable.to.find.0", filename));
 | 
			
		||||
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Nullable VirtualFile findFile(@NotNull String filename, @NotNull Project project) {
 | 
			
		||||
    VirtualFile found;
 | 
			
		||||
    // Vim supports both ~/ and ~\ (tested on Mac and Windows). On Windows, it supports forward- and back-slashes, but
 | 
			
		||||
    // it only supports forward slash on Unix (tested on Mac)
 | 
			
		||||
    // VFS works with both directory separators (tested on Mac and Windows)
 | 
			
		||||
    if (filename.startsWith("~/") || filename.startsWith("~\\")) {
 | 
			
		||||
      String relativePath = filename.substring(2);
 | 
			
		||||
      String dir = System.getProperty("user.home");
 | 
			
		||||
      if (logger.isDebugEnabled()) {
 | 
			
		||||
        logger.debug("home dir file");
 | 
			
		||||
        logger.debug("looking for " + relativePath + " in " + dir);
 | 
			
		||||
      }
 | 
			
		||||
      found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(dir, relativePath));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      found = LocalFileSystem.getInstance().findFileByIoFile(new File(filename));
 | 
			
		||||
 | 
			
		||||
      if (found == null) {
 | 
			
		||||
        found = findByNameInContentRoots(filename, project);
 | 
			
		||||
        if (found == null) {
 | 
			
		||||
          found = findByNameInProject(filename, project);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return found;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Nullable
 | 
			
		||||
  private VirtualFile findByNameInContentRoots(@NotNull String filename, @NotNull Project project) {
 | 
			
		||||
    VirtualFile found = null;
 | 
			
		||||
    ProjectRootManager prm = ProjectRootManager.getInstance(project);
 | 
			
		||||
    VirtualFile[] roots = prm.getContentRoots();
 | 
			
		||||
    for (int i = 0; i < roots.length; i++) {
 | 
			
		||||
      if (logger.isDebugEnabled()) {
 | 
			
		||||
        logger.debug("root[" + i + "] = " + roots[i].getPath());
 | 
			
		||||
      }
 | 
			
		||||
      found = roots[i].findFileByRelativePath(filename);
 | 
			
		||||
      if (found != null) {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return found;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Nullable
 | 
			
		||||
  private static VirtualFile findByNameInProject(@NotNull String filename, @NotNull Project project) {
 | 
			
		||||
    GlobalSearchScope projectScope = ProjectScope.getProjectScope(project);
 | 
			
		||||
    Collection<VirtualFile> names = FilenameIndex.getVirtualFilesByName(filename, projectScope);
 | 
			
		||||
    if (!names.isEmpty()) {
 | 
			
		||||
      return names.stream().findFirst().get();
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Closes the current editor.
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public void closeFile(@NotNull VimEditor editor, @NotNull ExecutionContext context) {
 | 
			
		||||
    final Project project = PlatformDataKeys.PROJECT.getData(((DataContext)context.getContext()));
 | 
			
		||||
    if (project != null) {
 | 
			
		||||
      final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(project);
 | 
			
		||||
      final EditorWindow window = fileEditorManager.getCurrentWindow();
 | 
			
		||||
      final VirtualFile virtualFile = fileEditorManager.getCurrentFile();
 | 
			
		||||
 | 
			
		||||
      if (virtualFile != null && window != null) {
 | 
			
		||||
        // During the work on VIM-2912 I've changed the close function to this one.
 | 
			
		||||
        //   However, the function with manager seems to work weirdly and it causes VIM-2953
 | 
			
		||||
        //window.getManager().closeFile(virtualFile, true, false);
 | 
			
		||||
        window.closeFile(virtualFile);
 | 
			
		||||
 | 
			
		||||
        // Get focus after closing tab
 | 
			
		||||
        window.requestFocus(true);
 | 
			
		||||
        if (!ApplicationManager.getApplication().isUnitTestMode()) {
 | 
			
		||||
          // This thing doesn't have an implementation in test mode
 | 
			
		||||
          EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Closes editor.
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public void closeFile(int number, @NotNull ExecutionContext context) {
 | 
			
		||||
    final Project project = PlatformDataKeys.PROJECT.getData(((IjEditorExecutionContext) context).getContext());
 | 
			
		||||
    if (project == null) return;
 | 
			
		||||
    final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(project);
 | 
			
		||||
    final EditorWindow window = fileEditorManager.getCurrentWindow();
 | 
			
		||||
    VirtualFile[] editors = fileEditorManager.getOpenFiles();
 | 
			
		||||
    if (window != null) {
 | 
			
		||||
      if (number >= 0 && number < editors.length) {
 | 
			
		||||
        fileEditorManager.closeFile(editors[number], window);
 | 
			
		||||
      }
 | 
			
		||||
    } if (!ApplicationManager.getApplication().isUnitTestMode()) {
 | 
			
		||||
      // This thing doesn't have an implementation in test mode
 | 
			
		||||
      EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves specific file in the project.
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public void saveFile(@NotNull ExecutionContext context) {
 | 
			
		||||
    NativeAction action;
 | 
			
		||||
    if (globalIjOptions(injector).getIdeawrite().contains(IjOptionConstants.ideawrite_all)) {
 | 
			
		||||
      action = injector.getNativeActionManager().getSaveAll();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      action = injector.getNativeActionManager().getSaveCurrent();
 | 
			
		||||
    }
 | 
			
		||||
    ExecuteExtensionKt.execute(action, context);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves all files in the project.
 | 
			
		||||
   */
 | 
			
		||||
  public void saveFiles(@NotNull ExecutionContext context) {
 | 
			
		||||
    ExecuteExtensionKt.execute(VimInjectorKt.getInjector().getNativeActionManager().getSaveAll(), context);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Selects then next or previous editor.
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean selectFile(int count, @NotNull ExecutionContext context) {
 | 
			
		||||
    final Project project = PlatformDataKeys.PROJECT.getData(((IjEditorExecutionContext) context).getContext());
 | 
			
		||||
    if (project == null) return false;
 | 
			
		||||
    FileEditorManager fem = FileEditorManager.getInstance(project); // API change - don't merge
 | 
			
		||||
    VirtualFile[] editors = fem.getOpenFiles();
 | 
			
		||||
    if (count == 99) {
 | 
			
		||||
      count = editors.length - 1;
 | 
			
		||||
    }
 | 
			
		||||
    if (count < 0 || count >= editors.length) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fem.openFile(editors[count], true);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Selects then next or previous editor.
 | 
			
		||||
   */
 | 
			
		||||
  public void selectNextFile(int count, @NotNull ExecutionContext context) {
 | 
			
		||||
    Project project = PlatformDataKeys.PROJECT.getData(((IjEditorExecutionContext) context).getContext());
 | 
			
		||||
    if (project == null) return;
 | 
			
		||||
    FileEditorManager fem = FileEditorManager.getInstance(project); // API change - don't merge
 | 
			
		||||
    VirtualFile[] editors = fem.getOpenFiles();
 | 
			
		||||
    VirtualFile current = fem.getSelectedFiles()[0];
 | 
			
		||||
    for (int i = 0; i < editors.length; i++) {
 | 
			
		||||
      if (editors[i].equals(current)) {
 | 
			
		||||
        int pos = (i + (count % editors.length) + editors.length) % editors.length;
 | 
			
		||||
 | 
			
		||||
        fem.openFile(editors[pos], true);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Selects previous editor tab.
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public void selectPreviousTab(@NotNull ExecutionContext context) {
 | 
			
		||||
    Project project = PlatformDataKeys.PROJECT.getData(((DataContext)context.getContext()));
 | 
			
		||||
    if (project == null) return;
 | 
			
		||||
    VirtualFile vf = LastTabService.getInstance(project).getLastTab();
 | 
			
		||||
    if (vf != null && vf.isValid()) {
 | 
			
		||||
      FileEditorManager.getInstance(project).openFile(vf, true);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      VimPlugin.indicateError();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the previous tab.
 | 
			
		||||
   */
 | 
			
		||||
  public @Nullable VirtualFile getPreviousTab(@NotNull DataContext context) {
 | 
			
		||||
    Project project = PlatformDataKeys.PROJECT.getData(context);
 | 
			
		||||
    if (project == null) return null;
 | 
			
		||||
    VirtualFile vf = LastTabService.getInstance(project).getLastTab();
 | 
			
		||||
    if (vf != null && vf.isValid()) {
 | 
			
		||||
      return vf;
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Nullable Editor selectEditor(Project project, @NotNull VirtualFile file) {
 | 
			
		||||
    FileEditorManager fMgr = FileEditorManager.getInstance(project);
 | 
			
		||||
    FileEditor[] feditors = fMgr.openFile(file, true);
 | 
			
		||||
    if (feditors.length > 0) {
 | 
			
		||||
      if (feditors[0] instanceof TextEditor) {
 | 
			
		||||
        Editor editor = ((TextEditor)feditors[0]).getEditor();
 | 
			
		||||
        if (!editor.isDisposed()) {
 | 
			
		||||
          return editor;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void displayLocationInfo(@NotNull VimEditor vimEditor) {
 | 
			
		||||
    Editor editor = ((IjVimEditor)vimEditor).getEditor();
 | 
			
		||||
    StringBuilder msg = new StringBuilder();
 | 
			
		||||
    Document doc = editor.getDocument();
 | 
			
		||||
 | 
			
		||||
    if (!(VimStateMachine.Companion.getInstance(new IjVimEditor(editor)).getMode() instanceof Mode.VISUAL)) {
 | 
			
		||||
      LogicalPosition lp = editor.getCaretModel().getLogicalPosition();
 | 
			
		||||
      int col = editor.getCaretModel().getOffset() - doc.getLineStartOffset(lp.line);
 | 
			
		||||
      int endoff = doc.getLineEndOffset(lp.line);
 | 
			
		||||
      if (endoff < EditorHelperRt.getFileSize(editor) && doc.getCharsSequence().charAt(endoff) == '\n') {
 | 
			
		||||
        endoff--;
 | 
			
		||||
      }
 | 
			
		||||
      int ecol = endoff - doc.getLineStartOffset(lp.line);
 | 
			
		||||
      LogicalPosition elp = editor.offsetToLogicalPosition(endoff);
 | 
			
		||||
 | 
			
		||||
      msg.append("Col ").append(col + 1);
 | 
			
		||||
      if (col != lp.column) {
 | 
			
		||||
        msg.append("-").append(lp.column + 1);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      msg.append(" of ").append(ecol + 1);
 | 
			
		||||
      if (ecol != elp.column) {
 | 
			
		||||
        msg.append("-").append(elp.column + 1);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      int lline = editor.getCaretModel().getLogicalPosition().line;
 | 
			
		||||
      int total = new IjVimEditor(editor).lineCount();
 | 
			
		||||
 | 
			
		||||
      msg.append("; Line ").append(lline + 1).append(" of ").append(total);
 | 
			
		||||
 | 
			
		||||
      SearchHelper.CountPosition cp = SearchHelper.countWords(editor);
 | 
			
		||||
 | 
			
		||||
      msg.append("; Word ").append(cp.getPosition()).append(" of ").append(cp.getCount());
 | 
			
		||||
 | 
			
		||||
      int offset = editor.getCaretModel().getOffset();
 | 
			
		||||
      int size = EditorHelperRt.getFileSize(editor);
 | 
			
		||||
 | 
			
		||||
      msg.append("; Character ").append(offset + 1).append(" of ").append(size);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      msg.append("Selected ");
 | 
			
		||||
 | 
			
		||||
      TextRange vr = new TextRange(editor.getSelectionModel().getBlockSelectionStarts(),
 | 
			
		||||
                                   editor.getSelectionModel().getBlockSelectionEnds());
 | 
			
		||||
      vr.normalize();
 | 
			
		||||
 | 
			
		||||
      int lines;
 | 
			
		||||
      SearchHelper.CountPosition cp = SearchHelper.countWords(editor);
 | 
			
		||||
      int words = cp.getCount();
 | 
			
		||||
      int word = 0;
 | 
			
		||||
      if (vr.isMultiple()) {
 | 
			
		||||
        lines = vr.size();
 | 
			
		||||
        int cols = vr.getMaxLength();
 | 
			
		||||
 | 
			
		||||
        msg.append(cols).append(" Cols; ");
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < vr.size(); i++) {
 | 
			
		||||
          cp = SearchHelper.countWords(editor, vr.getStartOffsets()[i], vr.getEndOffsets()[i] - 1);
 | 
			
		||||
          word += cp.getCount();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        LogicalPosition slp = editor.offsetToLogicalPosition(vr.getStartOffset());
 | 
			
		||||
        LogicalPosition elp = editor.offsetToLogicalPosition(vr.getEndOffset());
 | 
			
		||||
 | 
			
		||||
        lines = elp.line - slp.line + 1;
 | 
			
		||||
 | 
			
		||||
        cp = SearchHelper.countWords(editor, vr.getStartOffset(), vr.getEndOffset() - 1);
 | 
			
		||||
        word = cp.getCount();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      int total = new IjVimEditor(editor).lineCount();
 | 
			
		||||
 | 
			
		||||
      msg.append(lines).append(" of ").append(total).append(" Lines");
 | 
			
		||||
 | 
			
		||||
      msg.append("; ").append(word).append(" of ").append(words).append(" Words");
 | 
			
		||||
 | 
			
		||||
      int chars = vr.getSelectionCount();
 | 
			
		||||
      int size = EditorHelperRt.getFileSize(editor);
 | 
			
		||||
 | 
			
		||||
      msg.append("; ").append(chars).append(" of ").append(size).append(" Characters");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VimPlugin.showMessage(msg.toString());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void displayFileInfo(@NotNull VimEditor vimEditor, boolean fullPath) {
 | 
			
		||||
    Editor editor = ((IjVimEditor)vimEditor).getEditor();
 | 
			
		||||
    StringBuilder msg = new StringBuilder();
 | 
			
		||||
    VirtualFile vf = EditorHelper.getVirtualFile(editor);
 | 
			
		||||
    if (vf != null) {
 | 
			
		||||
      msg.append('"');
 | 
			
		||||
      if (fullPath) {
 | 
			
		||||
        msg.append(vf.getPath());
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        Project project = editor.getProject();
 | 
			
		||||
        if (project != null) {
 | 
			
		||||
          VirtualFile root = ProjectRootManager.getInstance(project).getFileIndex().getContentRootForFile(vf);
 | 
			
		||||
          if (root != null) {
 | 
			
		||||
            msg.append(vf.getPath().substring(root.getPath().length() + 1));
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            msg.append(vf.getPath());
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      msg.append("\" ");
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      msg.append("\"[No File]\" ");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Document doc = editor.getDocument();
 | 
			
		||||
    if (!doc.isWritable()) {
 | 
			
		||||
      msg.append("[RO] ");
 | 
			
		||||
    }
 | 
			
		||||
    else if (FileDocumentManager.getInstance().isDocumentUnsaved(doc)) {
 | 
			
		||||
      msg.append("[+] ");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int lline = editor.getCaretModel().getLogicalPosition().line;
 | 
			
		||||
    int total = new IjVimEditor(editor).lineCount();
 | 
			
		||||
    int pct = (int)((float)lline / (float)total * 100f + 0.5);
 | 
			
		||||
 | 
			
		||||
    msg.append("line ").append(lline + 1).append(" of ").append(total);
 | 
			
		||||
    msg.append(" --").append(pct).append("%-- ");
 | 
			
		||||
 | 
			
		||||
    LogicalPosition lp = editor.getCaretModel().getLogicalPosition();
 | 
			
		||||
    int col = editor.getCaretModel().getOffset() - doc.getLineStartOffset(lline);
 | 
			
		||||
 | 
			
		||||
    msg.append("col ").append(col + 1);
 | 
			
		||||
    if (col != lp.column) {
 | 
			
		||||
      msg.append("-").append(lp.column + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VimPlugin.showMessage(msg.toString());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static final @NotNull Logger logger = Logger.getInstance(FileGroup.class.getName());
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Respond to editor tab selection and remember the last used tab
 | 
			
		||||
   */
 | 
			
		||||
  public static void fileEditorManagerSelectionChangedCallback(@NotNull FileEditorManagerEvent event) {
 | 
			
		||||
    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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										444
									
								
								src/main/java/com/maddyhome/idea/vim/group/FileGroup.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								src/main/java/com/maddyhome/idea/vim/group/FileGroup.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,444 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2023 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
			
		||||
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.EditorsSplitters
 | 
			
		||||
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.psi.search.FilenameIndex
 | 
			
		||||
import com.intellij.psi.search.ProjectScope
 | 
			
		||||
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.VimFileBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.group.LastTabService.Companion.getInstance
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
 | 
			
		||||
import com.maddyhome.idea.vim.helper.countWords
 | 
			
		||||
import com.maddyhome.idea.vim.helper.fileSize
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.execute
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
class FileGroup : VimFileBase() {
 | 
			
		||||
  override fun openFile(filename: String, context: ExecutionContext): Boolean {
 | 
			
		||||
    if (logger.isDebugEnabled) {
 | 
			
		||||
      logger.debug("openFile($filename)")
 | 
			
		||||
    }
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context)
 | 
			
		||||
      ?: return false // API change - don't merge
 | 
			
		||||
 | 
			
		||||
    val found = findFile(filename, project)
 | 
			
		||||
 | 
			
		||||
    if (found != null) {
 | 
			
		||||
      if (logger.isDebugEnabled) {
 | 
			
		||||
        logger.debug("found file: $found")
 | 
			
		||||
      }
 | 
			
		||||
      // Can't open a file unless it has a known file type. The next call will return the known type.
 | 
			
		||||
      // If unknown, IDEA will prompt the user to pick a type.
 | 
			
		||||
      val type = FileTypeManager.getInstance().getKnownFileTypeOrAssociate(found, project)
 | 
			
		||||
 | 
			
		||||
      if (type != null) {
 | 
			
		||||
        val fem = FileEditorManager.getInstance(project)
 | 
			
		||||
        fem.openFile(found, true)
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
      } else {
 | 
			
		||||
        // There was no type and user didn't pick one. Don't open the file
 | 
			
		||||
        // Return true here because we found the file but the user canceled by not picking a type.
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      VimPlugin.showMessage(message("unable.to.find.0", filename))
 | 
			
		||||
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun findFile(filename: String, project: Project): VirtualFile? {
 | 
			
		||||
    var found: VirtualFile?
 | 
			
		||||
    // Vim supports both ~/ and ~\ (tested on Mac and Windows). On Windows, it supports forward- and back-slashes, but
 | 
			
		||||
    // it only supports forward slash on Unix (tested on Mac)
 | 
			
		||||
    // VFS works with both directory separators (tested on Mac and Windows)
 | 
			
		||||
    if (filename.startsWith("~/") || filename.startsWith("~\\")) {
 | 
			
		||||
      val relativePath = filename.substring(2)
 | 
			
		||||
      val dir = System.getProperty("user.home")
 | 
			
		||||
      if (logger.isDebugEnabled) {
 | 
			
		||||
        logger.debug("home dir file")
 | 
			
		||||
        logger.debug("looking for $relativePath in $dir")
 | 
			
		||||
      }
 | 
			
		||||
      found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(dir, relativePath))
 | 
			
		||||
    } else {
 | 
			
		||||
      found = LocalFileSystem.getInstance().findFileByIoFile(File(filename))
 | 
			
		||||
 | 
			
		||||
      if (found == null) {
 | 
			
		||||
        found = findByNameInContentRoots(filename, project)
 | 
			
		||||
        if (found == null) {
 | 
			
		||||
          found = findByNameInProject(filename, project)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return found
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun findByNameInContentRoots(filename: String, project: Project): VirtualFile? {
 | 
			
		||||
    var found: VirtualFile? = null
 | 
			
		||||
    val prm = ProjectRootManager.getInstance(project)
 | 
			
		||||
    val roots = prm.contentRoots
 | 
			
		||||
    for (i in roots.indices) {
 | 
			
		||||
      if (logger.isDebugEnabled) {
 | 
			
		||||
        logger.debug("root[" + i + "] = " + roots[i].path)
 | 
			
		||||
      }
 | 
			
		||||
      found = roots[i].findFileByRelativePath(filename)
 | 
			
		||||
      if (found != null) {
 | 
			
		||||
        break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return found
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Closes the current editor.
 | 
			
		||||
   */
 | 
			
		||||
  override fun closeFile(editor: VimEditor, context: ExecutionContext) {
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData((context.context as DataContext))
 | 
			
		||||
    if (project != null) {
 | 
			
		||||
      val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)
 | 
			
		||||
      val window = fileEditorManager.currentWindow
 | 
			
		||||
      val virtualFile = fileEditorManager.currentFile
 | 
			
		||||
 | 
			
		||||
      if (virtualFile != null && window != null) {
 | 
			
		||||
        // During the work on VIM-2912 I've changed the close function to this one.
 | 
			
		||||
        //   However, the function with manager seems to work weirdly and it causes VIM-2953
 | 
			
		||||
        //window.getManager().closeFile(virtualFile, true, false);
 | 
			
		||||
        window.closeFile(virtualFile)
 | 
			
		||||
 | 
			
		||||
        // Get focus after closing tab
 | 
			
		||||
        window.requestFocus(true)
 | 
			
		||||
        if (!ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
          // This thing doesn't have an implementation in test mode
 | 
			
		||||
          EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Closes editor.
 | 
			
		||||
   */
 | 
			
		||||
  override fun closeFile(number: Int, context: ExecutionContext) {
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return
 | 
			
		||||
    val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)
 | 
			
		||||
    val window = fileEditorManager.currentWindow
 | 
			
		||||
    val editors = fileEditorManager.openFiles
 | 
			
		||||
    if (window != null) {
 | 
			
		||||
      if (number >= 0 && number < editors.size) {
 | 
			
		||||
        fileEditorManager.closeFile(editors[number], window)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
      // This thing doesn't have an implementation in test mode
 | 
			
		||||
      EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves specific file in the project.
 | 
			
		||||
   */
 | 
			
		||||
  override fun saveFile(context: ExecutionContext) {
 | 
			
		||||
    val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) {
 | 
			
		||||
      injector.nativeActionManager.saveAll
 | 
			
		||||
    } else {
 | 
			
		||||
      injector.nativeActionManager.saveCurrent
 | 
			
		||||
    }
 | 
			
		||||
    action.execute(context)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves all files in the project.
 | 
			
		||||
   */
 | 
			
		||||
  override fun saveFiles(context: ExecutionContext) {
 | 
			
		||||
    injector.nativeActionManager.saveAll.execute(context)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Selects then next or previous editor.
 | 
			
		||||
   */
 | 
			
		||||
  override fun selectFile(count: Int, context: ExecutionContext): Boolean {
 | 
			
		||||
    var count = count
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return false
 | 
			
		||||
    val fem = FileEditorManager.getInstance(project) // API change - don't merge
 | 
			
		||||
    val editors = fem.openFiles
 | 
			
		||||
    if (count == 99) {
 | 
			
		||||
      count = editors.size - 1
 | 
			
		||||
    }
 | 
			
		||||
    if (count < 0 || count >= editors.size) {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fem.openFile(editors[count], true)
 | 
			
		||||
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Selects then next or previous editor.
 | 
			
		||||
   */
 | 
			
		||||
  override fun selectNextFile(count: Int, context: ExecutionContext) {
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return
 | 
			
		||||
    val fem = FileEditorManager.getInstance(project) // API change - don't merge
 | 
			
		||||
    val editors = fem.openFiles
 | 
			
		||||
    val current = fem.selectedFiles[0]
 | 
			
		||||
    for (i in editors.indices) {
 | 
			
		||||
      if (editors[i] == current) {
 | 
			
		||||
        val pos = (i + (count % editors.size) + editors.size) % editors.size
 | 
			
		||||
 | 
			
		||||
        fem.openFile(editors[pos], true)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Selects previous editor tab.
 | 
			
		||||
   */
 | 
			
		||||
  override fun selectPreviousTab(context: ExecutionContext) {
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData((context.context as DataContext)) ?: return
 | 
			
		||||
    val vf = getInstance(project).lastTab
 | 
			
		||||
    if (vf != null && vf.isValid) {
 | 
			
		||||
      FileEditorManager.getInstance(project).openFile(vf, true)
 | 
			
		||||
    } else {
 | 
			
		||||
      VimPlugin.indicateError()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the previous tab.
 | 
			
		||||
   */
 | 
			
		||||
  fun getPreviousTab(context: DataContext): VirtualFile? {
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData(context) ?: return null
 | 
			
		||||
    val vf = getInstance(project).lastTab
 | 
			
		||||
    if (vf != null && vf.isValid) {
 | 
			
		||||
      return vf
 | 
			
		||||
    }
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun selectEditor(project: Project, file: VirtualFile): Editor? {
 | 
			
		||||
    val fMgr = FileEditorManager.getInstance(project)
 | 
			
		||||
    val feditors = fMgr.openFile(file, true)
 | 
			
		||||
    if (feditors.size > 0) {
 | 
			
		||||
      if (feditors[0] is TextEditor) {
 | 
			
		||||
        val editor = (feditors[0] as TextEditor).editor
 | 
			
		||||
        if (!editor.isDisposed) {
 | 
			
		||||
          return editor
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun displayLocationInfo(vimEditor: VimEditor) {
 | 
			
		||||
    val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
    val msg = StringBuilder()
 | 
			
		||||
    val doc = editor.document
 | 
			
		||||
 | 
			
		||||
    if (injector.vimState.mode !is VISUAL) {
 | 
			
		||||
      val lp = editor.caretModel.logicalPosition
 | 
			
		||||
      val col = editor.caretModel.offset - doc.getLineStartOffset(lp.line)
 | 
			
		||||
      var endoff = doc.getLineEndOffset(lp.line)
 | 
			
		||||
      if (endoff < editor.fileSize && doc.charsSequence[endoff] == '\n') {
 | 
			
		||||
        endoff--
 | 
			
		||||
      }
 | 
			
		||||
      val ecol = endoff - doc.getLineStartOffset(lp.line)
 | 
			
		||||
      val elp = editor.offsetToLogicalPosition(endoff)
 | 
			
		||||
 | 
			
		||||
      msg.append("Col ").append(col + 1)
 | 
			
		||||
      if (col != lp.column) {
 | 
			
		||||
        msg.append("-").append(lp.column + 1)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      msg.append(" of ").append(ecol + 1)
 | 
			
		||||
      if (ecol != elp.column) {
 | 
			
		||||
        msg.append("-").append(elp.column + 1)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val lline = editor.caretModel.logicalPosition.line
 | 
			
		||||
      val total = IjVimEditor(editor).lineCount()
 | 
			
		||||
 | 
			
		||||
      msg.append("; Line ").append(lline + 1).append(" of ").append(total)
 | 
			
		||||
 | 
			
		||||
      val cp = countWords(vimEditor)
 | 
			
		||||
 | 
			
		||||
      msg.append("; Word ").append(cp.position).append(" of ").append(cp.count)
 | 
			
		||||
 | 
			
		||||
      val offset = editor.caretModel.offset
 | 
			
		||||
      val size = editor.fileSize
 | 
			
		||||
 | 
			
		||||
      msg.append("; Character ").append(offset + 1).append(" of ").append(size)
 | 
			
		||||
    } else {
 | 
			
		||||
      msg.append("Selected ")
 | 
			
		||||
 | 
			
		||||
      val vr = TextRange(
 | 
			
		||||
        editor.selectionModel.blockSelectionStarts,
 | 
			
		||||
        editor.selectionModel.blockSelectionEnds
 | 
			
		||||
      )
 | 
			
		||||
      vr.normalize()
 | 
			
		||||
 | 
			
		||||
      val lines: Int
 | 
			
		||||
      var cp = countWords(vimEditor)
 | 
			
		||||
      val words = cp.count
 | 
			
		||||
      var word = 0
 | 
			
		||||
      if (vr.isMultiple) {
 | 
			
		||||
        lines = vr.size()
 | 
			
		||||
        val cols = vr.maxLength
 | 
			
		||||
 | 
			
		||||
        msg.append(cols).append(" Cols; ")
 | 
			
		||||
 | 
			
		||||
        for (i in 0 until vr.size()) {
 | 
			
		||||
          cp = countWords(vimEditor, vr.startOffsets[i], (vr.endOffsets[i] - 1).toLong())
 | 
			
		||||
          word += cp.count
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        val slp = editor.offsetToLogicalPosition(vr.startOffset)
 | 
			
		||||
        val elp = editor.offsetToLogicalPosition(vr.endOffset)
 | 
			
		||||
 | 
			
		||||
        lines = elp.line - slp.line + 1
 | 
			
		||||
 | 
			
		||||
        cp = countWords(vimEditor, vr.startOffset, (vr.endOffset - 1).toLong())
 | 
			
		||||
        word = cp.count
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val total = IjVimEditor(editor).lineCount()
 | 
			
		||||
 | 
			
		||||
      msg.append(lines).append(" of ").append(total).append(" Lines")
 | 
			
		||||
 | 
			
		||||
      msg.append("; ").append(word).append(" of ").append(words).append(" Words")
 | 
			
		||||
 | 
			
		||||
      val chars = vr.selectionCount
 | 
			
		||||
      val size = editor.fileSize
 | 
			
		||||
 | 
			
		||||
      msg.append("; ").append(chars).append(" of ").append(size).append(" Characters")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VimPlugin.showMessage(msg.toString())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean) {
 | 
			
		||||
    val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
    val msg = StringBuilder()
 | 
			
		||||
    val vf = EditorHelper.getVirtualFile(editor)
 | 
			
		||||
    if (vf != null) {
 | 
			
		||||
      msg.append('"')
 | 
			
		||||
      if (fullPath) {
 | 
			
		||||
        msg.append(vf.path)
 | 
			
		||||
      } else {
 | 
			
		||||
        val project = editor.project
 | 
			
		||||
        if (project != null) {
 | 
			
		||||
          val root = ProjectRootManager.getInstance(project).fileIndex.getContentRootForFile(vf)
 | 
			
		||||
          if (root != null) {
 | 
			
		||||
            msg.append(vf.path.substring(root.path.length + 1))
 | 
			
		||||
          } else {
 | 
			
		||||
            msg.append(vf.path)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      msg.append("\" ")
 | 
			
		||||
    } else {
 | 
			
		||||
      msg.append("\"[No File]\" ")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val doc = editor.document
 | 
			
		||||
    if (!doc.isWritable) {
 | 
			
		||||
      msg.append("[RO] ")
 | 
			
		||||
    } else if (FileDocumentManager.getInstance().isDocumentUnsaved(doc)) {
 | 
			
		||||
      msg.append("[+] ")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val lline = editor.caretModel.logicalPosition.line
 | 
			
		||||
    val total = IjVimEditor(editor).lineCount()
 | 
			
		||||
    val pct = (lline.toFloat() / total.toFloat() * 100f + 0.5).toInt()
 | 
			
		||||
 | 
			
		||||
    msg.append("line ").append(lline + 1).append(" of ").append(total)
 | 
			
		||||
    msg.append(" --").append(pct).append("%-- ")
 | 
			
		||||
 | 
			
		||||
    val lp = editor.caretModel.logicalPosition
 | 
			
		||||
    val col = editor.caretModel.offset - doc.getLineStartOffset(lline)
 | 
			
		||||
 | 
			
		||||
    msg.append("col ").append(col + 1)
 | 
			
		||||
    if (col != lp.column) {
 | 
			
		||||
      msg.append("-").append(lp.column + 1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VimPlugin.showMessage(msg.toString())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun selectEditor(projectId: String, documentPath: String, protocol: String?): VimEditor? {
 | 
			
		||||
    val fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol) ?: return null
 | 
			
		||||
    val virtualFile = fileSystem.findFileByPath(documentPath) ?: return null
 | 
			
		||||
 | 
			
		||||
    val project = Arrays.stream(ProjectManager.getInstance().openProjects)
 | 
			
		||||
      .filter { p: Project? -> injector.file.getProjectId(p!!) == projectId }
 | 
			
		||||
      .findFirst().orElseThrow()
 | 
			
		||||
 | 
			
		||||
    val editor = selectEditor(project, virtualFile) ?: return null
 | 
			
		||||
    return IjVimEditor(editor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getProjectId(project: Any): String {
 | 
			
		||||
    require(project is Project)
 | 
			
		||||
    return project.name + "-" + project.locationHash
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    private fun findByNameInProject(filename: String, project: Project): VirtualFile? {
 | 
			
		||||
      val projectScope = ProjectScope.getProjectScope(project)
 | 
			
		||||
      val names = FilenameIndex.getVirtualFilesByName(filename, projectScope)
 | 
			
		||||
      if (!names.isEmpty()) {
 | 
			
		||||
        return names.stream().findFirst().get()
 | 
			
		||||
      }
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val logger = Logger.getInstance(
 | 
			
		||||
      FileGroup::class.java.name
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Respond to editor tab selection and remember the last used tab
 | 
			
		||||
     */
 | 
			
		||||
    fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
 | 
			
		||||
      if (event.oldFile != null) {
 | 
			
		||||
        getInstance(event.manager.project).lastTab = event.oldFile
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -19,24 +19,22 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
 | 
			
		||||
 * options
 | 
			
		||||
 */
 | 
			
		||||
@Suppress("SpellCheckingInspection")
 | 
			
		||||
public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
 | 
			
		||||
  public var ide: String by optionProperty(IjOptions.ide)
 | 
			
		||||
  public var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
 | 
			
		||||
  public var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
 | 
			
		||||
  public val ideavimsupport: StringListOptionValue by optionProperty(IjOptions.ideavimsupport)
 | 
			
		||||
  public var ideawrite: String by optionProperty(IjOptions.ideawrite)
 | 
			
		||||
  public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
 | 
			
		||||
  public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
 | 
			
		||||
  public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
 | 
			
		||||
open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
 | 
			
		||||
  var ide: String by optionProperty(IjOptions.ide)
 | 
			
		||||
  var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
 | 
			
		||||
  var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
 | 
			
		||||
  val ideavimsupport: StringListOptionValue by optionProperty(IjOptions.ideavimsupport)
 | 
			
		||||
  var ideawrite: String by optionProperty(IjOptions.ideawrite)
 | 
			
		||||
  val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
 | 
			
		||||
  var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
 | 
			
		||||
  var visualdelay: Int by optionProperty(IjOptions.visualdelay)
 | 
			
		||||
 | 
			
		||||
  // 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 useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
 | 
			
		||||
  public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
 | 
			
		||||
  var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
 | 
			
		||||
  var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
 | 
			
		||||
  var oldundo: Boolean by optionProperty(IjOptions.oldundo)
 | 
			
		||||
  var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
 | 
			
		||||
  var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -44,20 +42,19 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
 | 
			
		||||
 *
 | 
			
		||||
 * As a convenience, this class also provides access to the IntelliJ specific global options, via inheritance.
 | 
			
		||||
 */
 | 
			
		||||
public class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOptions(scope) {
 | 
			
		||||
class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOptions(scope) {
 | 
			
		||||
  // Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
 | 
			
		||||
  public var breakindent: Boolean by optionProperty(IjOptions.breakindent)
 | 
			
		||||
  public val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
 | 
			
		||||
  public var cursorline: Boolean by optionProperty(IjOptions.cursorline)
 | 
			
		||||
  public var fileformat: String by optionProperty(IjOptions.fileformat)
 | 
			
		||||
  public var list: Boolean by optionProperty(IjOptions.list)
 | 
			
		||||
  public var number: Boolean by optionProperty(IjOptions.number)
 | 
			
		||||
  public var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
 | 
			
		||||
  public var textwidth: Int by optionProperty(IjOptions.textwidth)
 | 
			
		||||
  public var wrap: Boolean by optionProperty(IjOptions.wrap)
 | 
			
		||||
  var breakindent: Boolean by optionProperty(IjOptions.breakindent)
 | 
			
		||||
  val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
 | 
			
		||||
  var cursorline: Boolean by optionProperty(IjOptions.cursorline)
 | 
			
		||||
  var fileformat: String by optionProperty(IjOptions.fileformat)
 | 
			
		||||
  var list: Boolean by optionProperty(IjOptions.list)
 | 
			
		||||
  var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
 | 
			
		||||
  var textwidth: Int by optionProperty(IjOptions.textwidth)
 | 
			
		||||
  var wrap: Boolean by optionProperty(IjOptions.wrap)
 | 
			
		||||
 | 
			
		||||
  // IntelliJ specific options
 | 
			
		||||
  public var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
 | 
			
		||||
  public var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
 | 
			
		||||
  public var idearefactormode: String by optionProperty(IjOptions.idearefactormode)
 | 
			
		||||
  var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
 | 
			
		||||
  var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
 | 
			
		||||
  var idearefactormode: String by optionProperty(IjOptions.idearefactormode)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,9 +26,9 @@ import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
 | 
			
		||||
 | 
			
		||||
@Suppress("SpellCheckingInspection")
 | 
			
		||||
public object IjOptions {
 | 
			
		||||
object IjOptions {
 | 
			
		||||
 | 
			
		||||
  public fun initialise() {
 | 
			
		||||
  fun initialise() {
 | 
			
		||||
    // Calling this method allows for deterministic initialisation of IjOptions, specifically initialising the
 | 
			
		||||
    // properties and registering the IJ specific options. Once added, they can be safely accessed by name, e.g. by the
 | 
			
		||||
    // implementation of `:set` while executing ~/.ideavimrc
 | 
			
		||||
@@ -39,8 +39,8 @@ public object IjOptions {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
 | 
			
		||||
  public val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
 | 
			
		||||
  public val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") {
 | 
			
		||||
  val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
 | 
			
		||||
  val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") {
 | 
			
		||||
    override fun checkIfValueValid(value: VimDataType, token: String) {
 | 
			
		||||
      super.checkIfValueValid(value, token)
 | 
			
		||||
      if (value != VimString.EMPTY) {
 | 
			
		||||
@@ -55,19 +55,18 @@ public object IjOptions {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  public val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
 | 
			
		||||
  public val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
 | 
			
		||||
  public val number: ToggleOption = addOption(ToggleOption("number", LOCAL_TO_WINDOW, "nu", false))
 | 
			
		||||
  public val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
 | 
			
		||||
  public val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
 | 
			
		||||
  public val wrap: ToggleOption = addOption(ToggleOption("wrap", LOCAL_TO_WINDOW, "wrap", true))
 | 
			
		||||
  val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
 | 
			
		||||
  val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
 | 
			
		||||
  val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
 | 
			
		||||
  val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
 | 
			
		||||
  val wrap: ToggleOption = addOption(ToggleOption("wrap", LOCAL_TO_WINDOW, "wrap", true))
 | 
			
		||||
 | 
			
		||||
  // These options are not explicitly listed as local-noglobal in Vim's help, but are set when a new buffer is edited,
 | 
			
		||||
  // based on the value of 'fileformats' or 'fileencodings'. To prevent unexpected file cnversion, we treat them as
 | 
			
		||||
  // based on the value of 'fileformats' or 'fileencodings'. To prevent unexpected file conversion, we treat them as
 | 
			
		||||
  // local-noglobal. See `:help local-noglobal`, `:help 'fileformats'` and `:help 'fileencodings'`
 | 
			
		||||
  public val bomb: ToggleOption =
 | 
			
		||||
  val bomb: ToggleOption =
 | 
			
		||||
    addOption(ToggleOption("bomb", LOCAL_TO_BUFFER, "bomb", false, isLocalNoGlobal = true))
 | 
			
		||||
  public val fileencoding: StringOption = addOption(
 | 
			
		||||
  val fileencoding: StringOption = addOption(
 | 
			
		||||
    StringOption(
 | 
			
		||||
      "fileencoding",
 | 
			
		||||
      LOCAL_TO_BUFFER,
 | 
			
		||||
@@ -76,7 +75,7 @@ public object IjOptions {
 | 
			
		||||
      isLocalNoGlobal = true
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  public val fileformat: StringOption = addOption(
 | 
			
		||||
  val fileformat: StringOption = addOption(
 | 
			
		||||
    StringOption(
 | 
			
		||||
      "fileformat",
 | 
			
		||||
      LOCAL_TO_BUFFER,
 | 
			
		||||
@@ -88,15 +87,15 @@ public object IjOptions {
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // IntelliJ specific functionality - custom options
 | 
			
		||||
  public val ide: StringOption = addOption(
 | 
			
		||||
  val ide: StringOption = addOption(
 | 
			
		||||
    StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
 | 
			
		||||
  )
 | 
			
		||||
  public val ideacopypreprocess: ToggleOption = addOption(
 | 
			
		||||
  val ideacopypreprocess: ToggleOption = addOption(
 | 
			
		||||
    ToggleOption("ideacopypreprocess", GLOBAL_OR_LOCAL_TO_BUFFER, "ideacopypreprocess", false)
 | 
			
		||||
  )
 | 
			
		||||
  public val ideajoin: ToggleOption = addOption(ToggleOption("ideajoin", GLOBAL_OR_LOCAL_TO_BUFFER, "ideajoin", false))
 | 
			
		||||
  public val ideamarks: ToggleOption = addOption(ToggleOption("ideamarks", GLOBAL, "ideamarks", true))
 | 
			
		||||
  public val idearefactormode: StringOption = addOption(
 | 
			
		||||
  val ideajoin: ToggleOption = addOption(ToggleOption("ideajoin", GLOBAL_OR_LOCAL_TO_BUFFER, "ideajoin", false))
 | 
			
		||||
  val ideamarks: ToggleOption = addOption(ToggleOption("ideamarks", GLOBAL, "ideamarks", true))
 | 
			
		||||
  val idearefactormode: StringOption = addOption(
 | 
			
		||||
    StringOption(
 | 
			
		||||
      "idearefactormode",
 | 
			
		||||
      GLOBAL_OR_LOCAL_TO_BUFFER,
 | 
			
		||||
@@ -105,7 +104,7 @@ public object IjOptions {
 | 
			
		||||
      IjOptionConstants.ideaRefactorModeValues
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  public val ideastatusicon: StringOption = addOption(
 | 
			
		||||
  val ideastatusicon: StringOption = addOption(
 | 
			
		||||
    StringOption(
 | 
			
		||||
      "ideastatusicon",
 | 
			
		||||
      GLOBAL,
 | 
			
		||||
@@ -114,7 +113,7 @@ public object IjOptions {
 | 
			
		||||
      IjOptionConstants.ideaStatusIconValues
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  public val ideavimsupport: StringListOption = addOption(
 | 
			
		||||
  val ideavimsupport: StringListOption = addOption(
 | 
			
		||||
    StringListOption(
 | 
			
		||||
      "ideavimsupport",
 | 
			
		||||
      GLOBAL,
 | 
			
		||||
@@ -123,27 +122,26 @@ public object IjOptions {
 | 
			
		||||
      IjOptionConstants.ideavimsupportValues
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  @JvmField public val ideawrite: StringOption = addOption(
 | 
			
		||||
  @JvmField
 | 
			
		||||
  val ideawrite: StringOption = addOption(
 | 
			
		||||
    StringOption("ideawrite", GLOBAL, "ideawrite", "all", IjOptionConstants.ideaWriteValues)
 | 
			
		||||
  )
 | 
			
		||||
  public val lookupkeys: StringListOption = addOption(
 | 
			
		||||
  val lookupkeys: StringListOption = addOption(
 | 
			
		||||
    StringListOption(
 | 
			
		||||
      "lookupkeys",
 | 
			
		||||
      GLOBAL,
 | 
			
		||||
      "lookupkeys",
 | 
			
		||||
      "<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 visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
 | 
			
		||||
  val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
 | 
			
		||||
  val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
 | 
			
		||||
 | 
			
		||||
  // 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))
 | 
			
		||||
  val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
 | 
			
		||||
  val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
 | 
			
		||||
  val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
 | 
			
		||||
  val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
 | 
			
		||||
  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>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
public class IjVimPsiService: VimPsiService {
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimRedrawService
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
 | 
			
		||||
public class IjVimRedrawService : VimRedrawService {
 | 
			
		||||
class IjVimRedrawService : VimRedrawService {
 | 
			
		||||
  override fun redraw() {
 | 
			
		||||
    // The only thing IntelliJ needs to redraw is the status line. Everything else is handled automatically.
 | 
			
		||||
    redrawStatusLine()
 | 
			
		||||
@@ -25,11 +25,11 @@ public class IjVimRedrawService : VimRedrawService {
 | 
			
		||||
    injector.messages.clearStatusBarMessage()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
  companion object {
 | 
			
		||||
    /**
 | 
			
		||||
     * Simulate Vim's redraw when the current editor changes
 | 
			
		||||
     */
 | 
			
		||||
    public fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
 | 
			
		||||
    fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
 | 
			
		||||
      injector.redrawService.redraw()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -39,7 +39,7 @@ public class IjVimRedrawService : VimRedrawService {
 | 
			
		||||
   *
 | 
			
		||||
   * Only redraw if lines are added/removed.
 | 
			
		||||
   */
 | 
			
		||||
  public object RedrawListener : DocumentListener {
 | 
			
		||||
  object RedrawListener : DocumentListener {
 | 
			
		||||
    override fun documentChanged(event: DocumentEvent) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (event.newFragment.contains("\n") || event.oldFragment.contains("\n")) {
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,6 @@ import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
 | 
			
		||||
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.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.listener.AppCodeTemplates
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
@@ -307,14 +306,14 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
        val editor = fileEditor.editor
 | 
			
		||||
        if (!editor.isDisposed) {
 | 
			
		||||
          editor.vim.let { vimEditor ->
 | 
			
		||||
            when (vimEditor.vimStateMachine.mode) {
 | 
			
		||||
            when (vimEditor.mode) {
 | 
			
		||||
              is Mode.VISUAL -> {
 | 
			
		||||
                vimEditor.exitVisualMode()
 | 
			
		||||
                KeyHandler.getInstance().reset(vimEditor)
 | 
			
		||||
              }
 | 
			
		||||
              is Mode.CMD_LINE -> {
 | 
			
		||||
                injector.processGroup.cancelExEntry(vimEditor, false)
 | 
			
		||||
                ExOutputModel.getInstance(editor).clear()
 | 
			
		||||
                injector.processGroup.cancelExEntry(vimEditor, refocusOwningEditor = false, resetCaret = false)
 | 
			
		||||
                ExOutputModel.tryGetInstance(editor)?.close()
 | 
			
		||||
              }
 | 
			
		||||
              else -> {}
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,10 +12,12 @@ import com.intellij.application.options.CodeStyle
 | 
			
		||||
import com.intellij.codeStyle.AbstractConvertLineSeparatorsAction
 | 
			
		||||
import com.intellij.openapi.Disposable
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.editor.EditorKind
 | 
			
		||||
import com.intellij.openapi.editor.EditorSettings.LineNumerationType
 | 
			
		||||
import com.intellij.openapi.editor.ScrollPositionCalculator
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorEx
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
 | 
			
		||||
import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
 | 
			
		||||
import com.intellij.openapi.fileEditor.TextEditor
 | 
			
		||||
@@ -94,12 +96,12 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup, InternalOpt
 | 
			
		||||
    addOptionValueOverride(IjOptions.fileencoding, FileEncodingOptionMapper())
 | 
			
		||||
    addOptionValueOverride(IjOptions.fileformat, FileFormatOptionMapper())
 | 
			
		||||
    addOptionValueOverride(IjOptions.list, ListOptionMapper(IjOptions.list, this))
 | 
			
		||||
    addOptionValueOverride(IjOptions.number, NumberOptionMapper(IjOptions.number, this))
 | 
			
		||||
    addOptionValueOverride(IjOptions.relativenumber, RelativeNumberOptionMapper(IjOptions.relativenumber, this))
 | 
			
		||||
    addOptionValueOverride(IjOptions.textwidth, TextWidthOptionMapper(IjOptions.textwidth))
 | 
			
		||||
    addOptionValueOverride(IjOptions.wrap, WrapOptionMapper(IjOptions.wrap, this))
 | 
			
		||||
 | 
			
		||||
    // These options are defined and implemented in vim-engine, but IntelliJ has similar features with settings we can map
 | 
			
		||||
    addOptionValueOverride(Options.number, NumberOptionMapper(Options.number, this))
 | 
			
		||||
    addOptionValueOverride(Options.scrolljump, ScrollJumpOptionMapper(Options.scrolljump, this))
 | 
			
		||||
    addOptionValueOverride(Options.sidescroll, SideScrollOptionMapper(Options.sidescroll, this))
 | 
			
		||||
    addOptionValueOverride(Options.scrolloff, ScrollOffOptionMapper(Options.scrolloff, this))
 | 
			
		||||
@@ -926,7 +928,9 @@ private class ScrollJumpOptionMapper(option: NumberOption, internalOptionValueAc
 | 
			
		||||
  override fun getEffectiveExternalValue(editor: VimEditor) = editor.ij.settings.verticalScrollJump.asVimInt()
 | 
			
		||||
 | 
			
		||||
  override fun setLocalExternalValue(editor: VimEditor, value: VimInt) {
 | 
			
		||||
    editor.ij.settings.verticalScrollJump = value.value
 | 
			
		||||
    // Note that Vim supports -1 to -100 as a percentage value. IntelliJ does not have any validation, but does not
 | 
			
		||||
    // handle or expect negative values
 | 
			
		||||
    editor.ij.settings.verticalScrollJump = value.value.coerceAtLeast(0)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun resetLocalExternalValue(editor: VimEditor, defaultValue: VimInt) {
 | 
			
		||||
@@ -975,26 +979,35 @@ private class SideScrollOptionMapper(option: NumberOption, internalOptionValueAc
 | 
			
		||||
 * setting value, and there is no UI to modify the local IntelliJ settings. Once the value has been set in IdeaVim, it
 | 
			
		||||
 * takes precedence over the global, persistent setting until the option is reset with either `:set scrolloff&` or
 | 
			
		||||
 * `:setlocal scrolloff<`.
 | 
			
		||||
 *
 | 
			
		||||
 * Note that when the IdeaVim value is set, we set the IntelliJ local value to 0 rather than sharing the value. This is
 | 
			
		||||
 * to prevent conflicts between IntelliJ and IdeaVim's separate implementations for scrolling. IntelliJ's scrolling
 | 
			
		||||
 * includes virtual space at the bottom of the file, while (Idea)Vim doesn't. Combining this with a non-zero
 | 
			
		||||
 * `'scrolloff'` value can reposition the bottom of the file. E.g., using `G` will position the last line at the bottom
 | 
			
		||||
 * of the file, but then IntelliJ moves it up `'scrolloff'` when the caret is moved.
 | 
			
		||||
 *
 | 
			
		||||
 * With a large value like `999`, IntelliJ will try to move the current line to the centre of the screen, but then
 | 
			
		||||
 * IdeaVim will try to reposition. Normally, this doesn't cause too much of a problem, because setting the scroll
 | 
			
		||||
 * position will cancel any outstanding animations. However, using backspace updates the scroll position with animations
 | 
			
		||||
 * disabled, so the scroll happens immediately, with a visible "twitch" as the editor scrolls for IntelliJ and then back
 | 
			
		||||
 * for IdeaVim.
 | 
			
		||||
 *
 | 
			
		||||
 * We should consider implementing [ScrollPositionCalculator] which would allow IdeaVim to completely take over
 | 
			
		||||
 * scrolling from IntelliJ. This would be a non-trivial change, and it might be better to move the scrolling to
 | 
			
		||||
 * vim-engine so it can also work in Fleet.
 | 
			
		||||
 */
 | 
			
		||||
private class ScrollOffOptionMapper(option: NumberOption, internalOptionValueAccessor: InternalOptionValueAccessor)
 | 
			
		||||
  : GlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(option, internalOptionValueAccessor) {
 | 
			
		||||
private class ScrollOffOptionMapper(
 | 
			
		||||
  scrollOffOption: NumberOption,
 | 
			
		||||
  internalOptionValueAccessor: InternalOptionValueAccessor,
 | 
			
		||||
) : OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(scrollOffOption, internalOptionValueAccessor) {
 | 
			
		||||
 | 
			
		||||
  override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_VERTICAL_SCROLL_OFFSET
 | 
			
		||||
 | 
			
		||||
  // The IntelliJ setting is in practice global. The base implementation relies on this fact
 | 
			
		||||
  override val canUserModifyExternalLocalValue: Boolean = false
 | 
			
		||||
  override fun getExternalGlobalValue() =
 | 
			
		||||
    EditorSettingsExternalizable.getInstance().verticalScrollOffset.asVimInt()
 | 
			
		||||
 | 
			
		||||
  override fun getGlobalExternalValue() = EditorSettingsExternalizable.getInstance().verticalScrollOffset.asVimInt()
 | 
			
		||||
  override fun getEffectiveExternalValue(editor: VimEditor) = editor.ij.settings.verticalScrollOffset.asVimInt()
 | 
			
		||||
 | 
			
		||||
  override fun setLocalExternalValue(editor: VimEditor, value: VimInt) {
 | 
			
		||||
    editor.ij.settings.verticalScrollOffset = value.value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun removeLocalExternalValue(editor: VimEditor) {
 | 
			
		||||
    // Unexpectedly, verticalScrollOffset accepts `-1` as a value to clear any local overrides, and this will reset the
 | 
			
		||||
    // effective value to return the global value
 | 
			
		||||
    editor.ij.settings.verticalScrollOffset = -1
 | 
			
		||||
  override fun suppressExternalLocalValue(editor: VimEditor) {
 | 
			
		||||
    editor.ij.settings.verticalScrollOffset = 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1002,15 +1015,14 @@ private class ScrollOffOptionMapper(option: NumberOption, internalOptionValueAcc
 | 
			
		||||
/**
 | 
			
		||||
 * Map the `'sidescrolloff'` global-local Vim option to the IntelliJ global-local horizontal scroll offset setting
 | 
			
		||||
 *
 | 
			
		||||
 * Ideally, we would implement this in a similar manner to [SideScrollOptionMapper], setting the external local
 | 
			
		||||
 * horizontal scroll offset value when the user explicitly sets the Vim value, so that IntelliJ could also use the
 | 
			
		||||
 * value. Unfortunately, IntelliJ's scrolling calculation logic is based on integer font width maths, which causes
 | 
			
		||||
 * problems with fractional font widths (such as on a Mac when running tests).
 | 
			
		||||
 * IntelliJ supports horizontal scroll offset in a similar manner to Vim. However, the implementation calculates offsets
 | 
			
		||||
 * using integer font sizes, which can lead to minor inaccuracies when compared to the IdeaVim implementation, such as
 | 
			
		||||
 * differences running tests on a Mac.
 | 
			
		||||
 *
 | 
			
		||||
 * For example, given a `'sidescrolloff'` value of `10`, and a fractional font width of `7.8`, IntelliJ will scroll `80`
 | 
			
		||||
 * pixels instead of `78`. This is a very minor difference, but because it overshoots, it means that IdeaVim doesn't
 | 
			
		||||
 * need to scroll, which in turn can cause issues with `'sidescroll'`, because IntelliJ doesn't support `sidescroll=0`,
 | 
			
		||||
 * which would scroll to position the caret in the middle of the display.
 | 
			
		||||
 * need to scroll, which in turn can cause issues with `'sidescroll'` (jump), because IntelliJ doesn't support
 | 
			
		||||
 * `sidescroll=0`, which would scroll to position the caret in the middle of the display.
 | 
			
		||||
 *
 | 
			
		||||
 * It also causes precision problems in the tests. The display is scrolled to a couple of pixels _before_ the leftmost
 | 
			
		||||
 * column, which means the rightmost column ends a couple of pixels _after_ the rightmost edge of the display. The tests
 | 
			
		||||
@@ -1028,78 +1040,98 @@ private class ScrollOffOptionMapper(option: NumberOption, internalOptionValueAcc
 | 
			
		||||
 * vim-engine so it can also work in Fleet.
 | 
			
		||||
 */
 | 
			
		||||
private class SideScrollOffOptionMapper(
 | 
			
		||||
  private val sideScrollOffOption: NumberOption,
 | 
			
		||||
  private val internalOptionValueAccessor: InternalOptionValueAccessor,
 | 
			
		||||
) : GlobalOptionValueOverride<VimInt>, LocalOptionValueOverride<VimInt>, IdeaBackedOptionValueOverride {
 | 
			
		||||
  sideScrollOffOption: NumberOption,
 | 
			
		||||
  internalOptionValueAccessor: InternalOptionValueAccessor,
 | 
			
		||||
) : OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(sideScrollOffOption, internalOptionValueAccessor) {
 | 
			
		||||
 | 
			
		||||
  override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_HORIZONTAL_SCROLL_OFFSET
 | 
			
		||||
 | 
			
		||||
  override fun getGlobalValue(storedValue: OptionValue<VimInt>, editor: VimEditor?): OptionValue<VimInt> {
 | 
			
		||||
  override fun getExternalGlobalValue() =
 | 
			
		||||
    EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt()
 | 
			
		||||
 | 
			
		||||
  override fun suppressExternalLocalValue(editor: VimEditor) {
 | 
			
		||||
    editor.ij.settings.horizontalScrollOffset = 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An abstract base class to map a global-local IDEA setting to a global-local Vim option. The IDEA setting is not
 | 
			
		||||
 * updated to reflect the Vim changes, but is kept at a neutral value.
 | 
			
		||||
 *
 | 
			
		||||
 * This class is used for Vim options that have an IDEA equivalent, but the implementation is handled by IdeaVim, e.g.,
 | 
			
		||||
 * scroll jumps and offsets. The IDEA value is not updated, and kept to a neutral value, so that the IDEA implementation
 | 
			
		||||
 * does not interfere with the IdeaVim implementation.
 | 
			
		||||
 */
 | 
			
		||||
private abstract class OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<T : VimDataType>(
 | 
			
		||||
  private val option: Option<T>,
 | 
			
		||||
  private val internalOptionValueAccessor: InternalOptionValueAccessor,
 | 
			
		||||
) : GlobalOptionValueOverride<T>, LocalOptionValueOverride<T>, IdeaBackedOptionValueOverride {
 | 
			
		||||
 | 
			
		||||
  override fun getGlobalValue(storedValue: OptionValue<T>, editor: VimEditor?): OptionValue<T> {
 | 
			
		||||
    if (storedValue is OptionValue.Default) {
 | 
			
		||||
      return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
 | 
			
		||||
      return OptionValue.Default(getExternalGlobalValue())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If it's not the default value, it's got to be the stored value
 | 
			
		||||
    return storedValue
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setGlobalValue(
 | 
			
		||||
    storedValue: OptionValue<VimInt>,
 | 
			
		||||
    newValue: OptionValue<VimInt>,
 | 
			
		||||
    editor: VimEditor?,
 | 
			
		||||
  ): Boolean {
 | 
			
		||||
    // The user has typed `:setlocal`. Just make sure that the IntelliJ value doesn't interfere with the Vim value
 | 
			
		||||
    injector.editorGroup.getEditors().forEach { it.ij.settings.horizontalScrollOffset = 0 }
 | 
			
		||||
  override fun setGlobalValue(storedValue: OptionValue<T>, newValue: OptionValue<T>, editor: VimEditor?): Boolean {
 | 
			
		||||
    // The user is updating the global Vim value, via `:setglobal`. IdeaVim scrolling will be using this value. Make
 | 
			
		||||
    // sure the IntelliJ values won't interfere
 | 
			
		||||
    // Note that we don't reset the local IntelliJ value for `:set {option}&` or `:set {option}<` because the current
 | 
			
		||||
    // global IntelliJ value might still interfere with IdeaVim's implementation. We continue to suppress the IntelliJ
 | 
			
		||||
    // value.
 | 
			
		||||
    injector.editorGroup.getEditors().forEach { suppressExternalLocalValue(it) }
 | 
			
		||||
    return storedValue.value != newValue.value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getLocalValue(storedValue: OptionValue<VimInt>?, editor: VimEditor): OptionValue<VimInt> {
 | 
			
		||||
  override fun getLocalValue(storedValue: OptionValue<T>?, editor: VimEditor): OptionValue<T> {
 | 
			
		||||
    if (storedValue == null) {
 | 
			
		||||
      // Initialisation. Report the global value of the setting. We ignore the local value because the user doesn't have
 | 
			
		||||
      // a way to set it, and we set it to 0 so that it doesn't affect our scroll calculations (because IntelliJ doesn't
 | 
			
		||||
      // handle sidescroll=0 to mean half a page)
 | 
			
		||||
      return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
 | 
			
		||||
      // a way to set it. If it has been changed (unlikely if stored value hasn't been set yet), then it would be 0
 | 
			
		||||
      return OptionValue.Default(getExternalGlobalValue())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (storedValue is OptionValue.Default && storedValue.value != sideScrollOffOption.unsetValue) {
 | 
			
		||||
      // The local value is set to the default value (as a copy of the global value), so return the global external
 | 
			
		||||
      // value as a default
 | 
			
		||||
      return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
 | 
			
		||||
    if (storedValue is OptionValue.Default && storedValue.value != option.unsetValue) {
 | 
			
		||||
      // The local value has been reset to Default. It's not the Vim default of "unset", but a copy of the global value.
 | 
			
		||||
      // Return the current value of the global external value
 | 
			
		||||
      return OptionValue.Default(getExternalGlobalValue())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Whatever is left is either explicitly set by the user, or option.unsetValue
 | 
			
		||||
    return storedValue
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setLocalValue(
 | 
			
		||||
    storedValue: OptionValue<VimInt>?,
 | 
			
		||||
    newValue: OptionValue<VimInt>,
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
  ): Boolean {
 | 
			
		||||
    // This is setting the Vim local value. We do nothing but reset the local horizontal scroll jump so IntelliJ's
 | 
			
		||||
    // scrolling doesn't affect our scrolling
 | 
			
		||||
    editor.ij.settings.horizontalScrollOffset = 0
 | 
			
		||||
  override fun setLocalValue(storedValue: OptionValue<T>?, newValue: OptionValue<T>, editor: VimEditor): Boolean {
 | 
			
		||||
    // Vim local value is being set. We do nothing but set the local IntelliJ value to 0, so IntelliJ's scrolling
 | 
			
		||||
    // doesn't affect IdeaVim's scrolling
 | 
			
		||||
    suppressExternalLocalValue(editor)
 | 
			
		||||
    return storedValue?.value != newValue.value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun onGlobalIdeaValueChanged(propertyName: String) {
 | 
			
		||||
    if (propertyName == ideaPropertyName) {
 | 
			
		||||
      // Again, just make sure the IntelliJ local value is 0
 | 
			
		||||
      injector.editorGroup.getEditors().forEach { it.ij.settings.horizontalScrollOffset = 0 }
 | 
			
		||||
      // The IntelliJ global value has changed. We want to use this as the Vim global value. Since we control scrolling,
 | 
			
		||||
      // set the local IntelliJ value to 0
 | 
			
		||||
      injector.editorGroup.getEditors().forEach { suppressExternalLocalValue(it) }
 | 
			
		||||
 | 
			
		||||
      // Update the stored Vim global value. This will not override any existing local values
 | 
			
		||||
      // Now update the Vim global value to match the new IntelliJ global value. If the current Vim global value is
 | 
			
		||||
      // Default, then it will already reflect the current global external value. Otherwise, update the Vim global value
 | 
			
		||||
      // to the external global value.
 | 
			
		||||
      val globalScope = OptionAccessScope.GLOBAL(null)
 | 
			
		||||
      val storedValue = internalOptionValueAccessor.getOptionValueInternal(sideScrollOffOption, globalScope)
 | 
			
		||||
      val storedValue = internalOptionValueAccessor.getOptionValueInternal(option, globalScope)
 | 
			
		||||
      if (storedValue !is OptionValue.Default) {
 | 
			
		||||
        val externalGlobalValue = EditorSettingsExternalizable.getInstance().horizontalScrollOffset
 | 
			
		||||
        internalOptionValueAccessor.setOptionValueInternal(
 | 
			
		||||
          sideScrollOffOption,
 | 
			
		||||
          option,
 | 
			
		||||
          globalScope,
 | 
			
		||||
          OptionValue.External(VimInt(externalGlobalValue))
 | 
			
		||||
          OptionValue.External(getExternalGlobalValue())
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected abstract fun getExternalGlobalValue(): T
 | 
			
		||||
  protected abstract fun suppressExternalLocalValue(editor: VimEditor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1198,23 +1230,63 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
 | 
			
		||||
    setIsUseSoftWraps(editor, value.asBoolean())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getGlobalIsUseSoftWraps(editor: VimEditor): Boolean {
 | 
			
		||||
    val settings = EditorSettingsExternalizable.getInstance()
 | 
			
		||||
    if (settings.isUseSoftWraps) {
 | 
			
		||||
      val masks = settings.softWrapFileMasks
 | 
			
		||||
      if (masks.trim() == "*") return true
 | 
			
		||||
  override fun canInitialiseOptionFrom(sourceEditor: VimEditor, targetEditor: VimEditor): Boolean {
 | 
			
		||||
    // IntelliJ's soft-wrap settings are based on editor kind, so there can be different wrap options for consoles,
 | 
			
		||||
    // main editors, etc. This is particularly noticeable in the console when running an application. The main editor
 | 
			
		||||
    // might have the Vim default with line wrap enabled. Initialising the run console will also have a default value,
 | 
			
		||||
    // and won't be updated by the options subsystem. It might have wrap enabled or not. If the editors were the same
 | 
			
		||||
    // kind, the same default value would be used.
 | 
			
		||||
    // However, if the main editor has 'wrap' explicitly set, this value is copied to the console, so the behaviour is
 | 
			
		||||
    // inconsistent. Furthermore, the run console has a soft-wraps toggle button that works at the global level, and
 | 
			
		||||
    // IdeaVim only sets the local value, so the toggle button can be inconsistent too.
 | 
			
		||||
    // By denying copying the main editor value during initialisation, the console gets the default value, and the IDE
 | 
			
		||||
    // decides what it should be. The behaviour is now more consistent.
 | 
			
		||||
    // We're happy to initialise diff editors from main editors, as there isn't a different soft wrap setting there.
 | 
			
		||||
    // Preview tabs might also have different settings, but because they're a type of main editor, it doesn't matter
 | 
			
		||||
    // so much
 | 
			
		||||
    fun editorKindToSoftWrapAppliancesPlace(kind: EditorKind) = when (kind) {
 | 
			
		||||
      EditorKind.UNTYPED,
 | 
			
		||||
      EditorKind.DIFF,
 | 
			
		||||
      EditorKind.MAIN_EDITOR -> SoftWrapAppliancePlaces.MAIN_EDITOR
 | 
			
		||||
      EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE
 | 
			
		||||
      // Treat PREVIEW as a kind of MAIN_EDITOR instead of SWAP.PREVIEW. There are fewer noticeable differences
 | 
			
		||||
      EditorKind.PREVIEW -> SoftWrapAppliancePlaces.MAIN_EDITOR
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      editor.ij.virtualFile?.let { file ->
 | 
			
		||||
        masks.split(";").forEach { mask ->
 | 
			
		||||
          val trimmed = mask.trim()
 | 
			
		||||
          if (trimmed.isNotEmpty() && PatternUtil.fromMask(trimmed).matcher(file.name).matches()) {
 | 
			
		||||
            return true
 | 
			
		||||
    val sourceKind = editorKindToSoftWrapAppliancesPlace(sourceEditor.ij.editorKind)
 | 
			
		||||
    val targetKind = editorKindToSoftWrapAppliancesPlace(targetEditor.ij.editorKind)
 | 
			
		||||
    return sourceKind == targetKind
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  private fun getGlobalIsUseSoftWraps(editor: VimEditor): Boolean {
 | 
			
		||||
    val softWrapAppliancePlace = when (editor.ij.editorKind) {
 | 
			
		||||
      EditorKind.UNTYPED,
 | 
			
		||||
      EditorKind.DIFF,
 | 
			
		||||
      EditorKind.MAIN_EDITOR -> SoftWrapAppliancePlaces.MAIN_EDITOR
 | 
			
		||||
      EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE
 | 
			
		||||
      EditorKind.PREVIEW -> SoftWrapAppliancePlaces.PREVIEW
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val settings = EditorSettingsExternalizable.getInstance()
 | 
			
		||||
    if (softWrapAppliancePlace == SoftWrapAppliancePlaces.MAIN_EDITOR) {
 | 
			
		||||
      if (settings.isUseSoftWraps) {
 | 
			
		||||
        val masks = settings.softWrapFileMasks
 | 
			
		||||
        if (masks.trim() == "*") return true
 | 
			
		||||
 | 
			
		||||
        editor.ij.virtualFile?.let { file ->
 | 
			
		||||
          masks.split(";").forEach { mask ->
 | 
			
		||||
            val trimmed = mask.trim()
 | 
			
		||||
            if (trimmed.isNotEmpty() && PatternUtil.fromMask(trimmed).matcher(file.name).matches()) {
 | 
			
		||||
              return true
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false
 | 
			
		||||
    return settings.isUseSoftWraps(softWrapAppliancePlace)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getEffectiveIsUseSoftWraps(editor: VimEditor) = editor.ij.settings.isUseSoftWraps
 | 
			
		||||
@@ -1241,28 +1313,28 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
public class IjOptionConstants {
 | 
			
		||||
class IjOptionConstants {
 | 
			
		||||
  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
 | 
			
		||||
  public companion object {
 | 
			
		||||
  companion object {
 | 
			
		||||
 | 
			
		||||
    public const val idearefactormode_keep: String = "keep"
 | 
			
		||||
    public const val idearefactormode_select: String = "select"
 | 
			
		||||
    public const val idearefactormode_visual: String = "visual"
 | 
			
		||||
    const val idearefactormode_keep: String = "keep"
 | 
			
		||||
    const val idearefactormode_select: String = "select"
 | 
			
		||||
    const val idearefactormode_visual: String = "visual"
 | 
			
		||||
 | 
			
		||||
    public const val ideastatusicon_enabled: String = "enabled"
 | 
			
		||||
    public const val ideastatusicon_gray: String = "gray"
 | 
			
		||||
    public const val ideastatusicon_disabled: String = "disabled"
 | 
			
		||||
    const val ideastatusicon_enabled: String = "enabled"
 | 
			
		||||
    const val ideastatusicon_gray: String = "gray"
 | 
			
		||||
    const val ideastatusicon_disabled: String = "disabled"
 | 
			
		||||
 | 
			
		||||
    public const val ideavimsupport_dialog: String = "dialog"
 | 
			
		||||
    public const val ideavimsupport_singleline: String = "singleline"
 | 
			
		||||
    public const val ideavimsupport_dialoglegacy: String = "dialoglegacy"
 | 
			
		||||
    const val ideavimsupport_dialog: String = "dialog"
 | 
			
		||||
    const val ideavimsupport_singleline: String = "singleline"
 | 
			
		||||
    const val ideavimsupport_dialoglegacy: String = "dialoglegacy"
 | 
			
		||||
 | 
			
		||||
    public const val ideawrite_all: String = "all"
 | 
			
		||||
    public const val ideawrite_file: String = "file"
 | 
			
		||||
    const val ideawrite_all: String = "all"
 | 
			
		||||
    const val ideawrite_file: String = "file"
 | 
			
		||||
 | 
			
		||||
    public val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
 | 
			
		||||
    public val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
 | 
			
		||||
    public val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
 | 
			
		||||
    public val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
 | 
			
		||||
    val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
 | 
			
		||||
    val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
 | 
			
		||||
    val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
 | 
			
		||||
    val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,111 +18,22 @@ import com.intellij.openapi.progress.ProgressIndicatorProvider
 | 
			
		||||
import com.intellij.openapi.progress.ProgressManager
 | 
			
		||||
import com.intellij.util.execution.ParametersListUtil
 | 
			
		||||
import com.intellij.util.text.CharSequenceReader
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
 | 
			
		||||
import com.maddyhome.idea.vim.KeyProcessResult
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimProcessGroupBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.helper.requestFocus
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.returnTo
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
			
		||||
import java.io.BufferedWriter
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.io.OutputStreamWriter
 | 
			
		||||
import java.io.Reader
 | 
			
		||||
import java.io.Writer
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
public class ProcessGroup : VimProcessGroupBase() {
 | 
			
		||||
  override var lastCommand: String? = null
 | 
			
		||||
    private set
 | 
			
		||||
  override var isCommandProcessing: Boolean = false
 | 
			
		||||
  override var modeBeforeCommandProcessing: Mode? = null
 | 
			
		||||
 | 
			
		||||
  public override fun startExEntry(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
    command: Command,
 | 
			
		||||
    label: String,
 | 
			
		||||
    initialCommandText: String,
 | 
			
		||||
  ) {
 | 
			
		||||
    // Don't allow ex commands in one line editors
 | 
			
		||||
    if (editor.isOneLineMode()) return
 | 
			
		||||
 | 
			
		||||
    val currentMode = editor.vimStateMachine.mode
 | 
			
		||||
    check(currentMode is ReturnableFromCmd) {
 | 
			
		||||
      "Cannot enable cmd mode from current mode $currentMode"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isCommandProcessing = true
 | 
			
		||||
    modeBeforeCommandProcessing = currentMode
 | 
			
		||||
 | 
			
		||||
    // Make sure the Visual selection marks are up to date before we use them.
 | 
			
		||||
    injector.markService.setVisualSelectionMarks(editor)
 | 
			
		||||
 | 
			
		||||
    val rangeText = getRange(editor, command)
 | 
			
		||||
 | 
			
		||||
    // Note that we should remove selection and reset caret offset before we switch back to Normal mode and then enter
 | 
			
		||||
    // Command-line mode. However, some IdeaVim commands can handle multiple carets, including multiple carets with
 | 
			
		||||
    // selection (which might or might not be a block selection). Unfortunately, because we're just entering
 | 
			
		||||
    // Command-line mode, we don't know which command is going to be entered, so we can't remove selection here.
 | 
			
		||||
    // Therefore, we switch to Normal and then Command-line even though we might still have a Visual selection...
 | 
			
		||||
    // On the plus side, it means we still show selection while editing the command line, which Vim also does
 | 
			
		||||
    // (Normal then Command-line is not strictly necessary, but done for completeness and autocmd)
 | 
			
		||||
    // Caret selection is finally handled in Command.execute
 | 
			
		||||
    editor.mode = Mode.NORMAL()
 | 
			
		||||
    editor.mode = Mode.CMD_LINE(currentMode)
 | 
			
		||||
 | 
			
		||||
    injector.commandLine.create(editor, context, ":", rangeText + initialCommandText, 1)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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) {
 | 
			
		||||
      processResultBuilder.addExecutionStep { _, _, _ ->
 | 
			
		||||
        requestFocus(panel.entry)
 | 
			
		||||
        panel.handleKey(stroke)
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
    } else {
 | 
			
		||||
      processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
 | 
			
		||||
        lambdaEditor.mode = Mode.NORMAL()
 | 
			
		||||
        getInstance().reset(lambdaEditor)
 | 
			
		||||
      }
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
 | 
			
		||||
    // If 'cpoptions' contains 'x', then Escape should execute the command line. This is the default for Vi but not Vim.
 | 
			
		||||
    // IdeaVim does not (currently?) support 'cpoptions', so sticks with Vim's default behaviour. Escape cancels.
 | 
			
		||||
    editor.mode = editor.mode.returnTo()
 | 
			
		||||
    getInstance().reset(editor)
 | 
			
		||||
    val panel = ExEntryPanel.getInstance()
 | 
			
		||||
    panel.deactivate(true, resetCaret)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getRange(editor: VimEditor, cmd: Command) = when {
 | 
			
		||||
    editor.inVisualMode -> "'<,'>"
 | 
			
		||||
    cmd.rawCount == 1 -> "."
 | 
			
		||||
    cmd.rawCount > 1 -> ".,.+" + (cmd.count - 1)
 | 
			
		||||
    else -> ""
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
class ProcessGroup : VimProcessGroupBase() {
 | 
			
		||||
  @Throws(ExecutionException::class, ProcessCanceledException::class)
 | 
			
		||||
  public override fun executeCommand(
 | 
			
		||||
  override fun executeCommand(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    command: String,
 | 
			
		||||
    input: CharSequence?,
 | 
			
		||||
@@ -221,7 +132,7 @@ public class ProcessGroup : VimProcessGroupBase() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
  companion object {
 | 
			
		||||
    private val logger = logger<ProcessGroup>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -53,6 +53,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
 | 
			
		||||
        jumpElem.setAttribute("line", jump.line.toString())
 | 
			
		||||
        jumpElem.setAttribute("column", jump.col.toString())
 | 
			
		||||
        jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
 | 
			
		||||
        jumpElem.setAttribute("protocol", StringUtil.notNullize(jump.protocol))
 | 
			
		||||
        projectElement.addContent(jumpElem)
 | 
			
		||||
        if (logger.isDebug()) {
 | 
			
		||||
          logger.debug("saved jump = $jump")
 | 
			
		||||
@@ -73,6 +74,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
 | 
			
		||||
          Integer.parseInt(jumpElement.getAttributeValue("line")),
 | 
			
		||||
          Integer.parseInt(jumpElement.getAttributeValue("column")),
 | 
			
		||||
          jumpElement.getAttributeValue("filename"),
 | 
			
		||||
          jumpElement.getAttributeValue("protocol", "file"),
 | 
			
		||||
        )
 | 
			
		||||
        jumps.add(jump)
 | 
			
		||||
      }
 | 
			
		||||
@@ -94,7 +96,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
 | 
			
		||||
      if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
 | 
			
		||||
      // we do not want jumps that were processed before
 | 
			
		||||
      val jump = buildJump(changePlace) ?: return
 | 
			
		||||
      jumpService.addJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump, true)
 | 
			
		||||
      jumpService.addJump(injector.file.getProjectId(project), jump, true)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -106,7 +108,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
 | 
			
		||||
      if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
 | 
			
		||||
      // we do not want jumps that were processed before
 | 
			
		||||
      val jump = buildJump(changePlace) ?: return
 | 
			
		||||
      jumpService.removeJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump)
 | 
			
		||||
      jumpService.removeJump(injector.file.getProjectId(project), jump)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -120,6 +122,6 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
 | 
			
		||||
 | 
			
		||||
    val path = place.file.path
 | 
			
		||||
 | 
			
		||||
    return Jump(line, col, path)
 | 
			
		||||
    return Jump(line, col, path, place.file.fileSystem.protocol)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -83,6 +83,11 @@ internal class PutGroup : VimPutBase() {
 | 
			
		||||
    val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
    val context = vimContext.context as DataContext
 | 
			
		||||
    val carets: MutableMap<Caret, RangeMarker> = mutableMapOf()
 | 
			
		||||
    if (editor.isInsertMode) {
 | 
			
		||||
      val undo = injector.undo
 | 
			
		||||
      val nanoTime = System.nanoTime()
 | 
			
		||||
      vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
 | 
			
		||||
    }
 | 
			
		||||
    EditorHelper.getOrderedCaretsList(editor).forEach { caret ->
 | 
			
		||||
      val startOffset =
 | 
			
		||||
        prepareDocumentAndGetStartOffsets(
 | 
			
		||||
 
 | 
			
		||||
@@ -53,12 +53,12 @@ import javax.swing.Timer
 | 
			
		||||
 *   no adjustment gets performed and IdeaVim stays in insert mode.
 | 
			
		||||
 */
 | 
			
		||||
// Do not remove until it's used in EasyMotion plugin in tests
 | 
			
		||||
public object VimVisualTimer {
 | 
			
		||||
object VimVisualTimer {
 | 
			
		||||
 | 
			
		||||
  public var swingTimer: Timer? = null
 | 
			
		||||
  public var mode: Mode? = null
 | 
			
		||||
  var swingTimer: Timer? = null
 | 
			
		||||
  var mode: Mode? = null
 | 
			
		||||
 | 
			
		||||
  public inline fun singleTask(currentMode: Mode, crossinline task: (initialMode: Mode?) -> Unit) {
 | 
			
		||||
  inline fun singleTask(currentMode: Mode, crossinline task: (initialMode: Mode?) -> Unit) {
 | 
			
		||||
    swingTimer?.stop()
 | 
			
		||||
 | 
			
		||||
    if (mode == null) mode = currentMode
 | 
			
		||||
@@ -70,7 +70,7 @@ public object VimVisualTimer {
 | 
			
		||||
    swingTimer = timer
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun doNow() {
 | 
			
		||||
  fun doNow() {
 | 
			
		||||
    val swingTimer1 = swingTimer
 | 
			
		||||
    if (swingTimer1 != null) {
 | 
			
		||||
      swingTimer1.stop()
 | 
			
		||||
@@ -80,12 +80,12 @@ public object VimVisualTimer {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun drop() {
 | 
			
		||||
  fun drop() {
 | 
			
		||||
    swingTimer?.stop()
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
 | 
			
		||||
  inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
 | 
			
		||||
    task(mode)
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
    mode = null
 | 
			
		||||
 
 | 
			
		||||
@@ -9,13 +9,9 @@
 | 
			
		||||
package com.maddyhome.idea.vim.group.visual
 | 
			
		||||
 | 
			
		||||
import com.intellij.find.FindManager
 | 
			
		||||
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.command.engine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -31,12 +27,4 @@ internal class VisualMotionGroup : VimVisualMotionGroupBase() {
 | 
			
		||||
 | 
			
		||||
    return super.autodetectVisualSubmode(editor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * COMPATIBILITY-LAYER: Added a method
 | 
			
		||||
   * Please see: https://jb.gg/zo8n0r
 | 
			
		||||
   */
 | 
			
		||||
  fun enterVisualMode(editor: Editor, subMode: CommandState.SubMode? = null): Boolean {
 | 
			
		||||
    return this.enterVisualMode(editor.vim, subMode?.engine)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ internal object GuicursorChangeListener : EffectiveOptionValueChangeListener {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun Editor.guicursorMode(): GuiCursorMode {
 | 
			
		||||
  return GuiCursorMode.fromMode(vim.mode, vim.vimStateMachine.isReplaceCharacter)
 | 
			
		||||
  return GuiCursorMode.fromMode(vim.mode, injector.vimState.isReplaceCharacter)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -144,26 +144,20 @@ private object AttributesCache {
 | 
			
		||||
@TestOnly
 | 
			
		||||
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
 | 
			
		||||
 | 
			
		||||
public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
 | 
			
		||||
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
 | 
			
		||||
  override fun isReplaceCharChanged(editor: VimEditor) {
 | 
			
		||||
    updateCaretsVisual(editor)
 | 
			
		||||
    updateCaretsVisual()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun modeChanged(editor: VimEditor, oldMode: Mode) {
 | 
			
		||||
    updateCaretsVisual(editor)
 | 
			
		||||
    updateCaretsVisual()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun updateCaretsVisual(editor: VimEditor) {
 | 
			
		||||
    if (injector.globalOptions().ideaglobalmode) {
 | 
			
		||||
      updateAllEditorsCaretsVisual()
 | 
			
		||||
    } else {
 | 
			
		||||
      val ijEditor = (editor as IjVimEditor).editor
 | 
			
		||||
      ijEditor.updateCaretsVisualAttributes()
 | 
			
		||||
      ijEditor.updateCaretsVisualPosition()
 | 
			
		||||
    }
 | 
			
		||||
  private fun updateCaretsVisual() {
 | 
			
		||||
    updateAllEditorsCaretsVisual()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun updateAllEditorsCaretsVisual() {
 | 
			
		||||
  fun updateAllEditorsCaretsVisual() {
 | 
			
		||||
    injector.editorGroup.getEditors().forEach { editor ->
 | 
			
		||||
      val ijEditor = (editor as IjVimEditor).editor
 | 
			
		||||
      ijEditor.updateCaretsVisualAttributes()
 | 
			
		||||
 
 | 
			
		||||
@@ -11,13 +11,7 @@
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.api.Options
 | 
			
		||||
import com.maddyhome.idea.vim.api.hasValue
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandState
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@@ -27,57 +21,17 @@ internal val Mode.hasVisualSelection
 | 
			
		||||
    else -> false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * COMPATIBILITY-LAYER: New method
 | 
			
		||||
 * Please see: https://jb.gg/zo8n0r
 | 
			
		||||
 */
 | 
			
		||||
public val Editor.mode: CommandState.Mode
 | 
			
		||||
  get() {
 | 
			
		||||
    val mode = this.vim.vimStateMachine.mode
 | 
			
		||||
    return when (mode) {
 | 
			
		||||
      is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
      Mode.INSERT -> CommandState.Mode.INSERT
 | 
			
		||||
      is Mode.NORMAL -> CommandState.Mode.COMMAND
 | 
			
		||||
      is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
 | 
			
		||||
      Mode.REPLACE -> CommandState.Mode.REPLACE
 | 
			
		||||
      is Mode.SELECT -> CommandState.Mode.SELECT
 | 
			
		||||
      is Mode.VISUAL -> CommandState.Mode.VISUAL
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * COMPATIBILITY-LAYER: New method
 | 
			
		||||
 * Please see: https://jb.gg/zo8n0r
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated("Please migrate to VimEditor.isEndAllowed which can correctly access virtualedit at the right scope",
 | 
			
		||||
  replaceWith = ReplaceWith("VimEditor.isEndAllowed"))
 | 
			
		||||
public val CommandState.Mode.isEndAllowed: Boolean
 | 
			
		||||
  get() {
 | 
			
		||||
    fun possiblyUsesVirtualSpace(): Boolean {
 | 
			
		||||
      // virtualedit is GLOBAL_OR_LOCAL_TO_WINDOW. We should be using EFFECTIVE, but we don't have a valid editor (which
 | 
			
		||||
      // is why this property is deprecated). Fetch the global value, passing in the fallback window to avoid asserts
 | 
			
		||||
      // DO NOT COPY THIS APPROACH - ALWAYS USE A REAL WINDOW FOR NON-GLOBAL OPTIONS!
 | 
			
		||||
      return injector.optionGroup.hasValue(Options.virtualedit, OptionAccessScope.GLOBAL(injector.fallbackWindow), OptionConstants.virtualedit_onemore)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return when (this) {
 | 
			
		||||
      CommandState.Mode.INSERT, CommandState.Mode.VISUAL, CommandState.Mode.SELECT -> true
 | 
			
		||||
      CommandState.Mode.COMMAND, CommandState.Mode.CMD_LINE, CommandState.Mode.REPLACE, CommandState.Mode.OP_PENDING -> possiblyUsesVirtualSpace()
 | 
			
		||||
      CommandState.Mode.INSERT_NORMAL, CommandState.Mode.INSERT_VISUAL, CommandState.Mode.INSERT_SELECT -> possiblyUsesVirtualSpace()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
public val Mode.inNormalMode: Boolean
 | 
			
		||||
val Mode.inNormalMode: Boolean
 | 
			
		||||
  get() = this is Mode.NORMAL
 | 
			
		||||
 | 
			
		||||
@get:JvmName("inInsertMode")
 | 
			
		||||
public val Editor.inInsertMode: Boolean
 | 
			
		||||
val Editor.inInsertMode: Boolean
 | 
			
		||||
  get() = this.vim.mode == Mode.INSERT || this.vim.mode == Mode.REPLACE
 | 
			
		||||
 | 
			
		||||
@get:JvmName("inVisualMode")
 | 
			
		||||
public val Editor.inVisualMode: Boolean
 | 
			
		||||
val Editor.inVisualMode: Boolean
 | 
			
		||||
  get() = this.vim.inVisualMode
 | 
			
		||||
 | 
			
		||||
@get:JvmName("inExMode")
 | 
			
		||||
internal val Editor.inExMode
 | 
			
		||||
  get() = this.vim.vimStateMachine.mode is Mode.CMD_LINE
 | 
			
		||||
  get() = this.vim.mode is Mode.CMD_LINE
 | 
			
		||||
 
 | 
			
		||||
@@ -100,15 +100,6 @@ public class EditorHelper {
 | 
			
		||||
    return EngineEditorHelperKt.normalizeVisualLine(new IjVimEditor(editor), line);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * COMPATIBILITY-LAYER: Created a function
 | 
			
		||||
   * Please see: <a href="https://jb.gg/zo8n0r">doc</a>
 | 
			
		||||
   */
 | 
			
		||||
  public static int getVisualLineCount(final @NotNull Editor editor) {
 | 
			
		||||
    @NotNull final VimEditor editor1 = new IjVimEditor(editor);
 | 
			
		||||
    return EngineEditorHelperKt.getVisualLineCount(editor1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Best efforts to ensure that scroll offset doesn't overlap itself.
 | 
			
		||||
   * <p>
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ import javax.swing.JComponent
 | 
			
		||||
import javax.swing.JTable
 | 
			
		||||
 | 
			
		||||
@Deprecated("Use fileSize from VimEditor")
 | 
			
		||||
public val Editor.fileSize: Int
 | 
			
		||||
val Editor.fileSize: Int
 | 
			
		||||
  get() = document.textLength
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ 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.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimRangeMarker
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimVisualPosition
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
@@ -57,4 +58,20 @@ internal class IjEditorHelper : EngineEditorHelper {
 | 
			
		||||
  override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
 | 
			
		||||
    return EditorUtil.inlayAwareOffsetToVisualPosition(editor.ij, offset).vim
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun createRangeMarker(editor: VimEditor, startOffset: Int, endOffset: Int): VimRangeMarker {
 | 
			
		||||
    val ijRangeMarker = editor.ij.document.createRangeMarker(startOffset, endOffset)
 | 
			
		||||
    return object : VimRangeMarker {
 | 
			
		||||
      override val startOffset: Int
 | 
			
		||||
        get() = ijRangeMarker.startOffset
 | 
			
		||||
      override val endOffset: Int
 | 
			
		||||
        get() = ijRangeMarker.endOffset
 | 
			
		||||
      override val isValid: Boolean
 | 
			
		||||
        get() = ijRangeMarker.isValid
 | 
			
		||||
 | 
			
		||||
      override fun dispose() {
 | 
			
		||||
        ijRangeMarker.dispose()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ import com.maddyhome.idea.vim.state.mode.returnTo
 | 
			
		||||
internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
 | 
			
		||||
  if (!this.vim.inSelectMode) return
 | 
			
		||||
 | 
			
		||||
  val returnTo = this.vim.vimStateMachine.mode.returnTo
 | 
			
		||||
  val returnTo = this.vim.mode.returnTo
 | 
			
		||||
  when (returnTo) {
 | 
			
		||||
    ReturnTo.INSERT -> {
 | 
			
		||||
      this.vim.mode = Mode.INSERT
 | 
			
		||||
@@ -64,7 +64,7 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
 | 
			
		||||
internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
 | 
			
		||||
  if (!this.inSelectMode) return
 | 
			
		||||
 | 
			
		||||
  val returnTo = this.vimStateMachine.mode.returnTo
 | 
			
		||||
  val returnTo = this.mode.returnTo
 | 
			
		||||
  when (returnTo) {
 | 
			
		||||
    ReturnTo.INSERT -> {
 | 
			
		||||
      this.mode = Mode.INSERT
 | 
			
		||||
 
 | 
			
		||||
@@ -180,7 +180,7 @@ internal object ScrollViewHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getScrollJump(editor: VimEditor, height: Int): Int {
 | 
			
		||||
    val flags = VimStateMachine.getInstance(editor).executingCommandFlags
 | 
			
		||||
    val flags = injector.vimState.executingCommandFlags
 | 
			
		||||
    val scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP)
 | 
			
		||||
 | 
			
		||||
    // Default value is 1. Zero is a valid value, but we normalise to 1 - we always want to scroll at least one line
 | 
			
		||||
@@ -203,7 +203,7 @@ internal object ScrollViewHelper {
 | 
			
		||||
    val caretColumn = position.column
 | 
			
		||||
    val halfWidth = getApproximateScreenWidth(editor) / 2
 | 
			
		||||
    val scrollOffset = getNormalizedSideScrollOffset(editor)
 | 
			
		||||
    val flags = VimStateMachine.getInstance(vimEditor).executingCommandFlags
 | 
			
		||||
    val flags = injector.vimState.executingCommandFlags
 | 
			
		||||
    val allowSidescroll = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP)
 | 
			
		||||
    val sidescroll = injector.options(vimEditor).sidescroll
 | 
			
		||||
    val offsetLeft = caretColumn - (currentVisualLeftColumn + scrollOffset)
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -8,152 +8,26 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
 | 
			
		||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.getLineEndOffset
 | 
			
		||||
import com.maddyhome.idea.vim.api.getText
 | 
			
		||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchHelper.findPositionOfFirstCharacter
 | 
			
		||||
 | 
			
		||||
private data class State(val position: Int, val trigger: Char, val inQuote: Boolean?, val lastOpenSingleQuotePos: Int)
 | 
			
		||||
 | 
			
		||||
// bounds are considered inside corresponding quotes
 | 
			
		||||
internal fun checkInString(chars: CharSequence, currentPos: Int, str: Boolean): Boolean {
 | 
			
		||||
  val begin = findPositionOfFirstCharacter(chars, currentPos, setOf('\n'), false, Direction.BACKWARDS)?.second?.plus(1) ?: 0
 | 
			
		||||
  val changes = quoteChanges(chars, begin)
 | 
			
		||||
  // TODO: here we need to keep only the latest element in beforePos (if any) and
 | 
			
		||||
  //   don't need atAndAfterPos to be eagerly collected
 | 
			
		||||
  var (beforePos, atAndAfterPos) = changes.partition { it.position < currentPos }
 | 
			
		||||
 | 
			
		||||
  var (atPos, afterPos) = atAndAfterPos.partition { it.position == currentPos }
 | 
			
		||||
  assert(atPos.size <= 1) { "Multiple characters at position $currentPos in string $chars" }
 | 
			
		||||
  if (atPos.isNotEmpty()) {
 | 
			
		||||
    val atPosChange = atPos[0]
 | 
			
		||||
    if (afterPos.isEmpty()) {
 | 
			
		||||
      // it is situation when cursor is on closing quote, so we must consider that we are inside quotes pair
 | 
			
		||||
      afterPos = afterPos.toMutableList()
 | 
			
		||||
      afterPos.add(atPosChange)
 | 
			
		||||
    } else {
 | 
			
		||||
      // it is situation when cursor is on opening quote, so we must consider that we are inside quotes pair
 | 
			
		||||
      beforePos = beforePos.toMutableList()
 | 
			
		||||
      beforePos.add(atPosChange)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val lastBeforePos = beforePos.lastOrNull()
 | 
			
		||||
 | 
			
		||||
  // if opening quote was found before pos (inQuote=true), it doesn't mean pos is in string, we need
 | 
			
		||||
  // to find closing quote to be sure
 | 
			
		||||
  var posInQuote = lastBeforePos?.inQuote?.let { if (it) null else it }
 | 
			
		||||
 | 
			
		||||
  val lastOpenSingleQuotePosBeforeCurrentPos = lastBeforePos?.lastOpenSingleQuotePos ?: -1
 | 
			
		||||
  var posInChar = if (lastOpenSingleQuotePosBeforeCurrentPos == -1) false else null
 | 
			
		||||
 | 
			
		||||
  var inQuote: Boolean? = null
 | 
			
		||||
 | 
			
		||||
  for ((_, trigger, inQuoteAfter, lastOpenSingleQuotePosAfter) in afterPos) {
 | 
			
		||||
    inQuote = inQuoteAfter
 | 
			
		||||
    if (posInQuote != null && posInChar != null) break
 | 
			
		||||
    if (posInQuote == null && inQuoteAfter != null) {
 | 
			
		||||
      // if we found double quote
 | 
			
		||||
      if (trigger == '"') {
 | 
			
		||||
        // then previously it has opposite value
 | 
			
		||||
        posInQuote = !inQuoteAfter
 | 
			
		||||
        // if we found single quote
 | 
			
		||||
      } else if (trigger == '\'') {
 | 
			
		||||
        // then we found closing single quote
 | 
			
		||||
        posInQuote = inQuoteAfter
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (posInChar == null && lastOpenSingleQuotePosAfter != lastOpenSingleQuotePosBeforeCurrentPos) {
 | 
			
		||||
      // if we found double quote and we reset position of last single quote
 | 
			
		||||
      if (trigger == '"' && lastOpenSingleQuotePosAfter == -1) {
 | 
			
		||||
        // then it means previously there supposed to be open single quote
 | 
			
		||||
        posInChar = false
 | 
			
		||||
        // if we found single quote
 | 
			
		||||
      } else if (trigger == '\'') {
 | 
			
		||||
        // if we reset position of last single quote
 | 
			
		||||
        // it means we found closing single quote
 | 
			
		||||
        // else it means we found opening single quote
 | 
			
		||||
        posInChar = lastOpenSingleQuotePosAfter == -1
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return if (str) posInQuote != null && posInQuote && (inQuote == null || !inQuote) else posInChar != null && posInChar
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// yields changes of inQuote and lastOpenSingleQuotePos during while iterating over chars
 | 
			
		||||
// rules are that:
 | 
			
		||||
// - escaped quotes are skipped
 | 
			
		||||
// - single quoted group may enclose only one character, maybe escaped,
 | 
			
		||||
// - so distance between opening and closing single quotes cannot be more than 3
 | 
			
		||||
// - bounds are considered inside corresponding quotes
 | 
			
		||||
private fun quoteChanges(chars: CharSequence, begin: Int) = sequence {
 | 
			
		||||
  // position of last found unpaired single quote
 | 
			
		||||
  var lastOpenSingleQuotePos = -1
 | 
			
		||||
  // whether we are in double quotes
 | 
			
		||||
  // true - definitely yes
 | 
			
		||||
  // false - definitely no
 | 
			
		||||
  // null - maybe yes, in case we found such combination: '"
 | 
			
		||||
  //   in that situation it may be double quote inside single quotes, so we cannot threat it as double quote pair open/close
 | 
			
		||||
  var inQuote: Boolean? = false
 | 
			
		||||
  val charsToSearch = setOf('\'', '"', '\n')
 | 
			
		||||
  var found = findPositionOfFirstCharacter(chars, begin, charsToSearch, false, Direction.FORWARDS)
 | 
			
		||||
  while (found != null && found.first != '\n') {
 | 
			
		||||
    val i = found.second
 | 
			
		||||
 | 
			
		||||
    val c = found.first
 | 
			
		||||
    when (c) {
 | 
			
		||||
      '"' -> {
 | 
			
		||||
        // if [maybe] in quote, then we know we found closing quote, so now we surely are not in quote
 | 
			
		||||
        if (inQuote == null || inQuote) {
 | 
			
		||||
          // we just found closing double quote
 | 
			
		||||
          inQuote = false
 | 
			
		||||
          // reset last found single quote, as it was in string literal
 | 
			
		||||
          lastOpenSingleQuotePos = -1
 | 
			
		||||
          // if we previously found unclosed single quote
 | 
			
		||||
        } else if (lastOpenSingleQuotePos >= 0) {
 | 
			
		||||
          // ...but we are too far from it
 | 
			
		||||
          if (i - lastOpenSingleQuotePos > 2) {
 | 
			
		||||
            // then it definitely was not opening single quote
 | 
			
		||||
            lastOpenSingleQuotePos = -1
 | 
			
		||||
            // and we found opening double quote
 | 
			
		||||
            inQuote = true
 | 
			
		||||
          } else {
 | 
			
		||||
            // else we don't know if we inside double or single quotes or not
 | 
			
		||||
            inQuote = null
 | 
			
		||||
          }
 | 
			
		||||
          // we were not in double nor in single quote, so now we are in double quote
 | 
			
		||||
        } else {
 | 
			
		||||
          inQuote = true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      '\'' -> {
 | 
			
		||||
        // if we previously found unclosed single quote
 | 
			
		||||
        if (lastOpenSingleQuotePos >= 0) {
 | 
			
		||||
          // ...but we are too far from it
 | 
			
		||||
          if (i - lastOpenSingleQuotePos > 3) {
 | 
			
		||||
            // ... forget about it and threat current one as unclosed
 | 
			
		||||
            lastOpenSingleQuotePos = i
 | 
			
		||||
          } else {
 | 
			
		||||
            // else we found closing single quote
 | 
			
		||||
            lastOpenSingleQuotePos = -1
 | 
			
		||||
            // and if we didn't know whether we are in double quote or not
 | 
			
		||||
            if (inQuote == null) {
 | 
			
		||||
              // then now we are definitely not in
 | 
			
		||||
              inQuote = false
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          // we found opening single quote
 | 
			
		||||
          lastOpenSingleQuotePos = i
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    yield(State(i, c, inQuote, lastOpenSingleQuotePos))
 | 
			
		||||
    found =
 | 
			
		||||
      findPositionOfFirstCharacter(chars, i + Direction.FORWARDS.toInt(), charsToSearch, false, Direction.FORWARDS)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntComparator
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntIterator
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntRBTreeSet
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntSortedSet
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check ignorecase and smartcase options to see if a case insensitive search should be performed with the given pattern.
 | 
			
		||||
@@ -180,3 +54,354 @@ private fun containsUpperCase(pattern: String): Boolean {
 | 
			
		||||
  }
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This counts all the words in the file.
 | 
			
		||||
 */
 | 
			
		||||
fun countWords(
 | 
			
		||||
  vimEditor: VimEditor,
 | 
			
		||||
  start: Int = 0,
 | 
			
		||||
  end: Long = vimEditor.fileSize(),
 | 
			
		||||
): CountPosition {
 | 
			
		||||
  val offset = vimEditor.currentCaret().offset
 | 
			
		||||
 | 
			
		||||
  var count = 1
 | 
			
		||||
  var position = 0
 | 
			
		||||
  var last = -1
 | 
			
		||||
  var res = start
 | 
			
		||||
  while (true) {
 | 
			
		||||
    res = injector.searchHelper.findNextWord(vimEditor, res, 1, true, false)
 | 
			
		||||
    if (res == start || res == 0 || res > end || res == last) {
 | 
			
		||||
      break
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    count++
 | 
			
		||||
 | 
			
		||||
    if (res == offset) {
 | 
			
		||||
      position = count
 | 
			
		||||
    } else if (last < offset && res >= offset) {
 | 
			
		||||
      position = if (count == 2) {
 | 
			
		||||
        1
 | 
			
		||||
      } else {
 | 
			
		||||
        count - 1
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    last = res
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (position == 0 && res == offset) {
 | 
			
		||||
    position = count
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return CountPosition(count, position)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun findNumbersInRange(
 | 
			
		||||
  editor: Editor,
 | 
			
		||||
  textRange: TextRange,
 | 
			
		||||
  alpha: Boolean,
 | 
			
		||||
  hex: Boolean,
 | 
			
		||||
  octal: Boolean,
 | 
			
		||||
): List<Pair<TextRange, NumberType>> {
 | 
			
		||||
  val result: MutableList<Pair<TextRange, NumberType>> = ArrayList()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  for (i in 0 until textRange.size()) {
 | 
			
		||||
    val startOffset = textRange.startOffsets[i]
 | 
			
		||||
    val end = textRange.endOffsets[i]
 | 
			
		||||
    val text: String = editor.vim.getText(startOffset, end)
 | 
			
		||||
    val textChunks = text.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
 | 
			
		||||
    var chunkStart = 0
 | 
			
		||||
    for (chunk in textChunks) {
 | 
			
		||||
      val number = findNumberInText(chunk, 0, alpha, hex, octal)
 | 
			
		||||
 | 
			
		||||
      if (number != null) {
 | 
			
		||||
        result.add(
 | 
			
		||||
          Pair(
 | 
			
		||||
            TextRange(
 | 
			
		||||
              number.first.startOffset + startOffset + chunkStart,
 | 
			
		||||
              number.first.endOffset + startOffset + chunkStart
 | 
			
		||||
            ),
 | 
			
		||||
            number.second
 | 
			
		||||
          )
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      chunkStart += 1 + chunk.length
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun findNumberUnderCursor(
 | 
			
		||||
  editor: Editor,
 | 
			
		||||
  caret: Caret,
 | 
			
		||||
  alpha: Boolean,
 | 
			
		||||
  hex: Boolean,
 | 
			
		||||
  octal: Boolean,
 | 
			
		||||
): Pair<TextRange, NumberType>? {
 | 
			
		||||
  val lline = caret.logicalPosition.line
 | 
			
		||||
  val text = IjVimEditor(editor).getLineText(lline).lowercase(Locale.getDefault())
 | 
			
		||||
  val startLineOffset = IjVimEditor(editor).getLineStartOffset(lline)
 | 
			
		||||
  val posOnLine = caret.offset - startLineOffset
 | 
			
		||||
 | 
			
		||||
  val numberTextRange = findNumberInText(text, posOnLine, alpha, hex, octal) ?: return null
 | 
			
		||||
 | 
			
		||||
  return Pair(
 | 
			
		||||
    TextRange(
 | 
			
		||||
      numberTextRange.first.startOffset + startLineOffset,
 | 
			
		||||
      numberTextRange.first.endOffset + startLineOffset
 | 
			
		||||
    ),
 | 
			
		||||
    numberTextRange.second
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search for number in given text from start position
 | 
			
		||||
 *
 | 
			
		||||
 * @param textInRange    - text to search in
 | 
			
		||||
 * @param startPosOnLine - start offset to search
 | 
			
		||||
 * @return - text range with number
 | 
			
		||||
 */
 | 
			
		||||
fun findNumberInText(
 | 
			
		||||
  textInRange: String,
 | 
			
		||||
  startPosOnLine: Int,
 | 
			
		||||
  alpha: Boolean,
 | 
			
		||||
  hex: Boolean,
 | 
			
		||||
  octal: Boolean,
 | 
			
		||||
): Pair<TextRange, NumberType>? {
 | 
			
		||||
  if (logger.isDebugEnabled) {
 | 
			
		||||
    logger.debug("text=$textInRange")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var pos = startPosOnLine
 | 
			
		||||
  val lineEndOffset = textInRange.length
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    // Skip over current whitespace if any
 | 
			
		||||
    while (pos < lineEndOffset && !isNumberChar(textInRange[pos], alpha, hex, octal, true)) {
 | 
			
		||||
      pos++
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (logger.isDebugEnabled) logger.debug("pos=$pos")
 | 
			
		||||
    if (pos >= lineEndOffset) {
 | 
			
		||||
      logger.debug("no number char on line")
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val isHexChar = "abcdefABCDEF".indexOf(textInRange[pos]) >= 0
 | 
			
		||||
 | 
			
		||||
    if (hex) {
 | 
			
		||||
      // Ox and OX handling
 | 
			
		||||
      if (textInRange[pos] == '0' && pos < lineEndOffset - 1 && "xX".indexOf(textInRange[pos + 1]) >= 0) {
 | 
			
		||||
        pos += 2
 | 
			
		||||
      } else if ("xX".indexOf(textInRange[pos]) >= 0 && pos > 0 && textInRange[pos - 1] == '0') {
 | 
			
		||||
        pos++
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      logger.debug("checking hex")
 | 
			
		||||
      val range = findRange(textInRange, pos, false, true, false, false)
 | 
			
		||||
      val start = range.first
 | 
			
		||||
      val end = range.second
 | 
			
		||||
 | 
			
		||||
      // Ox and OX
 | 
			
		||||
      if (start >= 2 && textInRange.substring(start - 2, start).equals("0x", ignoreCase = true)) {
 | 
			
		||||
        logger.debug("found hex")
 | 
			
		||||
        return Pair(TextRange(start - 2, end), NumberType.HEX)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!isHexChar || alpha) {
 | 
			
		||||
        break
 | 
			
		||||
      } else {
 | 
			
		||||
        pos++
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      break
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (octal) {
 | 
			
		||||
    logger.debug("checking octal")
 | 
			
		||||
    val range = findRange(textInRange, pos, false, false, true, false)
 | 
			
		||||
    val start = range.first
 | 
			
		||||
    val end = range.second
 | 
			
		||||
 | 
			
		||||
    if (end - start == 1 && textInRange[start] == '0') {
 | 
			
		||||
      return Pair(TextRange(start, end), NumberType.DEC)
 | 
			
		||||
    }
 | 
			
		||||
    if (textInRange[start] == '0' && end > start &&
 | 
			
		||||
      !(start > 0 && isNumberChar(textInRange[start - 1], false, false, false, true))
 | 
			
		||||
    ) {
 | 
			
		||||
      logger.debug("found octal")
 | 
			
		||||
      return Pair(TextRange(start, end), NumberType.OCT)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (alpha) {
 | 
			
		||||
    if (logger.isDebugEnabled) logger.debug("checking alpha for " + textInRange[pos])
 | 
			
		||||
    if (isNumberChar(textInRange[pos], true, false, false, false)) {
 | 
			
		||||
      if (logger.isDebugEnabled) logger.debug("found alpha at $pos")
 | 
			
		||||
      return Pair(TextRange(pos, pos + 1), NumberType.ALPHA)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val range = findRange(textInRange, pos, false, false, false, true)
 | 
			
		||||
  var start = range.first
 | 
			
		||||
  val end = range.second
 | 
			
		||||
  if (start > 0 && textInRange[start - 1] == '-') {
 | 
			
		||||
    start--
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return Pair(TextRange(start, end), NumberType.DEC)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Searches for digits block that matches parameters
 | 
			
		||||
 */
 | 
			
		||||
private fun findRange(
 | 
			
		||||
  text: String,
 | 
			
		||||
  pos: Int,
 | 
			
		||||
  alpha: Boolean,
 | 
			
		||||
  hex: Boolean,
 | 
			
		||||
  octal: Boolean,
 | 
			
		||||
  decimal: Boolean,
 | 
			
		||||
): Pair<Int, Int> {
 | 
			
		||||
  var end = pos
 | 
			
		||||
  while (end < text.length && isNumberChar(text[end], alpha, hex, octal, decimal || octal)) {
 | 
			
		||||
    end++
 | 
			
		||||
  }
 | 
			
		||||
  var start = pos
 | 
			
		||||
  while (start >= 0 && isNumberChar(text[start], alpha, hex, octal, decimal || octal)) {
 | 
			
		||||
    start--
 | 
			
		||||
  }
 | 
			
		||||
  if (start < end &&
 | 
			
		||||
    (start == -1 ||
 | 
			
		||||
      0 <= start && start < text.length &&
 | 
			
		||||
      !isNumberChar(text[start], alpha, hex, octal, decimal || octal))
 | 
			
		||||
  ) {
 | 
			
		||||
    start++
 | 
			
		||||
  }
 | 
			
		||||
  if (octal) {
 | 
			
		||||
    for (i in start until end) {
 | 
			
		||||
      if (!isNumberChar(text[i], false, false, true, false)) return Pair(0, 0)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return Pair(start, end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun isNumberChar(ch: Char, alpha: Boolean, hex: Boolean, octal: Boolean, decimal: Boolean): Boolean {
 | 
			
		||||
  return if (alpha && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {
 | 
			
		||||
    true
 | 
			
		||||
  } else if (octal && (ch >= '0' && ch <= '7')) {
 | 
			
		||||
    true
 | 
			
		||||
  } else if (hex && ((ch >= '0' && ch <= '9') || "abcdefABCDEF".indexOf(ch) >= 0)) {
 | 
			
		||||
    true
 | 
			
		||||
  } else {
 | 
			
		||||
    decimal && (ch >= '0' && ch <= '9')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Find the word under the cursor or the next word to the right of the cursor on the current line.
 | 
			
		||||
 *
 | 
			
		||||
 * @param editor The editor to find the word in
 | 
			
		||||
 * @param caret  The caret to find word under
 | 
			
		||||
 * @return The text range of the found word or null if there is no word under/after the cursor on the line
 | 
			
		||||
 */
 | 
			
		||||
fun findWordUnderCursor(editor: Editor, caret: Caret): TextRange? {
 | 
			
		||||
  val vimEditor = IjVimEditor(editor)
 | 
			
		||||
  val chars = editor.document.charsSequence
 | 
			
		||||
  val stop = vimEditor.getLineEndOffset(caret.logicalPosition.line, true)
 | 
			
		||||
 | 
			
		||||
  val pos = caret.offset
 | 
			
		||||
  // Technically the first condition is covered by the second one, but let it be
 | 
			
		||||
  if (chars.length == 0 || chars.length <= pos) return null
 | 
			
		||||
 | 
			
		||||
  //if (pos == chars.length() - 1) return new TextRange(chars.length() - 1, chars.length());
 | 
			
		||||
  var start = pos
 | 
			
		||||
  val types = arrayOf(
 | 
			
		||||
    CharacterHelper.CharacterType.KEYWORD,
 | 
			
		||||
    CharacterHelper.CharacterType.PUNCTUATION
 | 
			
		||||
  )
 | 
			
		||||
  for (i in 0..1) {
 | 
			
		||||
    start = pos
 | 
			
		||||
    val type = charType(vimEditor, chars[start], false)
 | 
			
		||||
    if (type == types[i]) {
 | 
			
		||||
      // Search back for start of word
 | 
			
		||||
      while (start > 0 && charType(vimEditor, chars[start - 1], false) == types[i]) {
 | 
			
		||||
        start--
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // Search forward for start of word
 | 
			
		||||
      while (start < stop && charType(vimEditor, chars[start], false) != types[i]) {
 | 
			
		||||
        start++
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (start != stop) {
 | 
			
		||||
      break
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (start == stop) {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
  // Special case 1 character words because 'findNextWordEnd' returns one to many chars
 | 
			
		||||
  val end = if (start < stop &&
 | 
			
		||||
    (start >= chars.length - 1 ||
 | 
			
		||||
      charType(vimEditor, chars[start + 1], false) != CharacterHelper.CharacterType.KEYWORD)
 | 
			
		||||
  ) {
 | 
			
		||||
    start + 1
 | 
			
		||||
  } else {
 | 
			
		||||
    injector.searchHelper.findNextWordEnd(vimEditor, start, 1, false, false) + 1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return TextRange(start, end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun findMisspelledWords(
 | 
			
		||||
  editor: Editor,
 | 
			
		||||
  startOffset: Int,
 | 
			
		||||
  endOffset: Int,
 | 
			
		||||
  skipCount: Int,
 | 
			
		||||
  offsetOrdering: IntComparator?,
 | 
			
		||||
): Int {
 | 
			
		||||
  val project = editor.project ?: return -1
 | 
			
		||||
 | 
			
		||||
  val offsets: IntSortedSet = IntRBTreeSet(offsetOrdering)
 | 
			
		||||
  DaemonCodeAnalyzerEx.processHighlights(
 | 
			
		||||
    editor.document, project, SpellCheckerSeveritiesProvider.TYPO,
 | 
			
		||||
    startOffset, endOffset
 | 
			
		||||
  ) { highlight: HighlightInfo ->
 | 
			
		||||
    if (highlight.severity === SpellCheckerSeveritiesProvider.TYPO) {
 | 
			
		||||
      val offset = highlight.getStartOffset()
 | 
			
		||||
      if (offset >= startOffset && offset <= endOffset) {
 | 
			
		||||
        offsets.add(offset)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (offsets.isEmpty()) {
 | 
			
		||||
    return -1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (skipCount >= offsets.size) {
 | 
			
		||||
    return offsets.lastInt()
 | 
			
		||||
  } else {
 | 
			
		||||
    val offsetIterator: IntIterator = offsets.iterator()
 | 
			
		||||
    skip(offsetIterator, skipCount)
 | 
			
		||||
    return offsetIterator.nextInt()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun skip(iterator: IntIterator, n: Int) {
 | 
			
		||||
  require(n >= 0) { "Argument must be nonnegative: $n" }
 | 
			
		||||
  var i = n
 | 
			
		||||
  while (i-- != 0 && iterator.hasNext()) iterator.nextInt()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CountPosition(val count: Int, val position: Int)
 | 
			
		||||
 | 
			
		||||
private val logger = logger<SearchLogger>()
 | 
			
		||||
private class SearchLogger
 | 
			
		||||
@@ -210,12 +210,28 @@ private fun findClosestMatch(
 | 
			
		||||
    return -1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val sortedResults = results.sortedBy { it.startOffset }.let { if (!forwards) it.reversed() else it }
 | 
			
		||||
  val nextIndex = sortedResults.indexOfFirst {
 | 
			
		||||
    if (forwards) it.startOffset > initialOffset else it.startOffset < initialOffset
 | 
			
		||||
  val sortedResults = if (forwards) {
 | 
			
		||||
    results.sortedBy { it.startOffset }
 | 
			
		||||
  } else {
 | 
			
		||||
    results.sortedByDescending { it.startOffset }
 | 
			
		||||
  }
 | 
			
		||||
  val toDrop = (nextIndex + count - 1).let { if (injector.globalOptions().wrapscan) it % results.size else it }
 | 
			
		||||
  return sortedResults.drop(toDrop).firstOrNull()?.startOffset ?: -1
 | 
			
		||||
  val closestIndex = if (forwards) {
 | 
			
		||||
    sortedResults.indexOfFirst { it.startOffset > initialOffset }
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    sortedResults.indexOfFirst { it.startOffset < initialOffset }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (closestIndex == -1 && !injector.globalOptions().wrapscan) {
 | 
			
		||||
    return -1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val nextIndex = closestIndex.coerceAtLeast(0) + (count - 1)
 | 
			
		||||
  if (nextIndex >= sortedResults.size && !injector.globalOptions().wrapscan) {
 | 
			
		||||
    return -1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return sortedResults[nextIndex % results.size].startOffset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun highlightSearchResults(editor: Editor, pattern: String, results: List<TextRange>, currentMatchOffset: Int) {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,31 +8,17 @@
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.stream.Collectors
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * COMPATIBILITY-LAYER: Created a helper class
 | 
			
		||||
 * Please see: https://jb.gg/zo8n0r
 | 
			
		||||
 */
 | 
			
		||||
public object StringHelper {
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  @Deprecated("Use injector.parser.parseKeys(string)",
 | 
			
		||||
    ReplaceWith("injector.parser.parseKeys(string)", "com.maddyhome.idea.vim.api.injector")
 | 
			
		||||
  )
 | 
			
		||||
  public fun parseKeys(string: String): List<KeyStroke> {
 | 
			
		||||
    return injector.parser.parseKeys(string)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
object StringHelper {
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  @Deprecated("Use injector.parser.parseKeys(string)")
 | 
			
		||||
  public fun parseKeys(vararg string: String): List<KeyStroke> {
 | 
			
		||||
  @ApiStatus.ScheduledForRemoval
 | 
			
		||||
  fun parseKeys(vararg string: String): List<KeyStroke> {
 | 
			
		||||
    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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,14 +12,14 @@ import com.intellij.openapi.editor.Editor
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
// Do not remove until it's used in EasyMotion plugin in tests
 | 
			
		||||
public class TestInputModel private constructor() {
 | 
			
		||||
class TestInputModel private constructor() {
 | 
			
		||||
  private val myKeyStrokes: MutableList<KeyStroke> = Lists.newArrayList()
 | 
			
		||||
  public fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
 | 
			
		||||
  fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
 | 
			
		||||
    myKeyStrokes.clear()
 | 
			
		||||
    myKeyStrokes.addAll(keyStrokes)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun nextKeyStroke(): KeyStroke? {
 | 
			
		||||
  fun nextKeyStroke(): KeyStroke? {
 | 
			
		||||
    // Return key from the unfinished mapping
 | 
			
		||||
    /*
 | 
			
		||||
MappingStack mappingStack = KeyHandler.getInstance().getMappingStack();
 | 
			
		||||
@@ -34,9 +34,9 @@ if (mappingStack.hasStroke()) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
  companion object {
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    public fun getInstance(editor: Editor): TestInputModel {
 | 
			
		||||
    fun getInstance(editor: Editor): TestInputModel {
 | 
			
		||||
      var model = editor.vimTestInputModel
 | 
			
		||||
      if (model == null) {
 | 
			
		||||
        model = TestInputModel()
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,13 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.ui.UISettings
 | 
			
		||||
import com.intellij.ide.ui.UISettingsUtils
 | 
			
		||||
import com.intellij.openapi.application.ModalityState
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.EditorKind
 | 
			
		||||
import com.intellij.openapi.editor.colors.EditorColorsManager
 | 
			
		||||
import com.intellij.openapi.editor.impl.EditorImpl
 | 
			
		||||
import com.intellij.openapi.wm.IdeFocusManager
 | 
			
		||||
import java.awt.Font
 | 
			
		||||
import javax.swing.JComponent
 | 
			
		||||
@@ -32,11 +37,21 @@ internal fun runAfterGotFocus(runnable: Runnable) {
 | 
			
		||||
  IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable, ModalityState.defaultModalityState())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun selectFont(forStr: String): Font {
 | 
			
		||||
  val scheme = EditorColorsManager.getInstance().globalScheme
 | 
			
		||||
internal fun selectEditorFont(editor: Editor?, forText: String): Font {
 | 
			
		||||
  val fontSize = when {
 | 
			
		||||
    editor is EditorImpl -> editor.fontSize2D
 | 
			
		||||
    UISettings.getInstance().presentationMode -> UISettingsUtils.getInstance().presentationModeFontSize
 | 
			
		||||
    editor?.editorKind == EditorKind.CONSOLE -> UISettingsUtils.getInstance().scaledConsoleFontSize
 | 
			
		||||
    else -> UISettingsUtils.getInstance().scaledEditorFontSize
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val fontName = scheme.fontPreferences.realFontFamilies.firstOrNull {
 | 
			
		||||
    Font(it, Font.PLAIN, scheme.editorFontSize).canDisplayUpTo(forStr) == -1
 | 
			
		||||
  } ?: return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize)
 | 
			
		||||
  return Font(fontName, Font.PLAIN, scheme.editorFontSize)
 | 
			
		||||
  val scheme = EditorColorsManager.getInstance().globalScheme
 | 
			
		||||
  scheme.fontPreferences.realFontFamilies.forEach { fontName ->
 | 
			
		||||
    val font = Font(fontName, Font.PLAIN, scheme.editorFontSize)
 | 
			
		||||
    if (font.canDisplayUpTo(forText) == -1) {
 | 
			
		||||
      return font.deriveFont(fontSize)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize).deriveFont(fontSize)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,14 +13,18 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.command.CommandProcessor
 | 
			
		||||
import com.intellij.openapi.command.undo.UndoManager
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
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.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.ChangesListener
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.common.InsertSequence
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
@@ -32,6 +36,10 @@ import com.maddyhome.idea.vim.undo.UndoRedoBase
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
  companion object {
 | 
			
		||||
    private val logger = logger<UndoRedoHelper>()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
 | 
			
		||||
    val ijContext = context.context as DataContext
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
 | 
			
		||||
@@ -63,7 +71,17 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
 | 
			
		||||
        undoManager.undo(fileEditor)
 | 
			
		||||
        var nextUndoNanoTime = undoManager.getNextUndoNanoTime(fileEditor)
 | 
			
		||||
        val insertInfo = (editor.primaryCaret() as IjVimCaret).getInsertSequenceForTime(nextUndoNanoTime)
 | 
			
		||||
        if (insertInfo == null || undoManager.isNextUndoAskConfirmation(fileEditor)) {
 | 
			
		||||
          undoManager.undo(fileEditor)
 | 
			
		||||
        } else {
 | 
			
		||||
          while (insertInfo.contains(nextUndoNanoTime)) {
 | 
			
		||||
            undoManager.undo(fileEditor)
 | 
			
		||||
            nextUndoNanoTime = undoManager.getNextUndoNanoTime(fileEditor)
 | 
			
		||||
            if (undoManager.isNextUndoAskConfirmation(fileEditor)) break
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
@@ -89,6 +107,22 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun startInsertSequence(caret: VimCaret, startOffset: Int, startNanoTime: Long) {
 | 
			
		||||
    (caret as IjVimCaret).startInsertSequence(startOffset, startNanoTime)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun endInsertSequence(caret: VimCaret, endOffset: Int, endNanoTime: Long) {
 | 
			
		||||
    (caret as IjVimCaret).endInsertSequence(endOffset, endNanoTime)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun abandonCurrentInsertSequence(caret: VimCaret) {
 | 
			
		||||
    (caret as IjVimCaret).abandonCurrentInsertSequece()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getInsertSequence(caret: VimCaret, nanoTime: Long): InsertSequence? {
 | 
			
		||||
    return (caret as IjVimCaret).getInsertSequenceForTime(nanoTime)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun performRedo(
 | 
			
		||||
    undoManager: UndoManager,
 | 
			
		||||
    fileEditor: TextEditor,
 | 
			
		||||
@@ -114,10 +148,23 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
 | 
			
		||||
        undoManager.redo(fileEditor)
 | 
			
		||||
        var nextRedoNanoTime = undoManager.getNextRedoNanoTime(fileEditor)
 | 
			
		||||
        val insertInfo = (editor.primaryCaret() as IjVimCaret).getInsertSequenceForTime(nextRedoNanoTime)
 | 
			
		||||
        if (insertInfo == null || undoManager.isNextRedoAskConfirmation(fileEditor)) {
 | 
			
		||||
          undoManager.redo(fileEditor)
 | 
			
		||||
        } else {
 | 
			
		||||
          while (insertInfo.contains(nextRedoNanoTime)) {
 | 
			
		||||
            undoManager.redo(fileEditor)
 | 
			
		||||
            nextRedoNanoTime = undoManager.getNextRedoNanoTime(fileEditor)
 | 
			
		||||
            if (undoManager.isNextRedoAskConfirmation(fileEditor)) break
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
        // TODO all the carets should be moved to their corresponding insertInfo.startOffset
 | 
			
		||||
        // It's a bit tricky because the offsets where calculated before text in input sequence was inserted
 | 
			
		||||
        // So it will require adjusting offsets to proper one in multicaret case
 | 
			
		||||
        removeSelections(editor)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,9 @@ import com.intellij.openapi.editor.VisualPosition
 | 
			
		||||
import com.intellij.openapi.editor.markup.RangeHighlighter
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
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.common.InsertSequence
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VisualChange
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
 | 
			
		||||
@@ -44,7 +44,7 @@ import kotlin.reflect.KProperty
 | 
			
		||||
/**
 | 
			
		||||
 * Caret's offset when entering visual mode
 | 
			
		||||
 */
 | 
			
		||||
public var Caret.vimSelectionStart: Int
 | 
			
		||||
var Caret.vimSelectionStart: Int
 | 
			
		||||
  get() {
 | 
			
		||||
    val selectionStart = _vimSelectionStart
 | 
			
		||||
    if (selectionStart == null) {
 | 
			
		||||
@@ -95,7 +95,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Data could be lost during visual block motion
 | 
			
		||||
internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretToEditor()
 | 
			
		||||
internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
 | 
			
		||||
internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()
 | 
			
		||||
 | 
			
		||||
@@ -104,7 +103,6 @@ internal var Editor.vimInitialised: Boolean by userDataOr { false }
 | 
			
		||||
// ------------------ Editor
 | 
			
		||||
internal fun unInitializeEditor(editor: Editor) {
 | 
			
		||||
  editor.vimLastSelectionType = null
 | 
			
		||||
  editor.vimStateMachine = null
 | 
			
		||||
  editor.vimMorePanel = null
 | 
			
		||||
  editor.vimExOutput = null
 | 
			
		||||
  editor.vimLastHighlighters = null
 | 
			
		||||
@@ -119,7 +117,6 @@ internal var Editor.vimIncsearchCurrentMatchOffset: Int? by userData()
 | 
			
		||||
 * @see :help visualmode()
 | 
			
		||||
 */
 | 
			
		||||
internal var Editor.vimLastSelectionType: SelectionType? by userData()
 | 
			
		||||
internal var Editor.vimStateMachine: VimStateMachine? by userData()
 | 
			
		||||
internal var Editor.vimEditorGroup: Boolean by userDataOr { false }
 | 
			
		||||
internal var Editor.vimHasRelativeLineNumbersInstalled: Boolean by userDataOr { false }
 | 
			
		||||
internal var Editor.vimMorePanel: ExOutputPanel? by userData()
 | 
			
		||||
@@ -128,6 +125,9 @@ internal var Editor.vimTestInputModel: TestInputModel? by userData()
 | 
			
		||||
 | 
			
		||||
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
 | 
			
		||||
 | 
			
		||||
internal var Caret.currentInsert: InsertSequence? by userData()
 | 
			
		||||
internal val Caret.insertHistory: MutableList<InsertSequence> by userDataOr { mutableListOf() }
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function for delegated properties.
 | 
			
		||||
 * The property will be delegated to UserData and has nullable type.
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.listener
 | 
			
		||||
 | 
			
		||||
import com.intellij.execution.impl.ConsoleViewImpl
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.editor.EditorKind
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.EditorListener
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inInsertMode
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This listener is similar to the one we introduce in vim-engine to handle focus change,
 | 
			
		||||
 * However, in IJ we would like to start editing in some editors in INSERT mode (e.g., consoles)
 | 
			
		||||
 * It is different to we had previously. Now we go to INSERT mode not only when we focus on the console the first time, but every time.
 | 
			
		||||
 * Going to INSERT on every focus is easier to implement and more consistent (behavior is always the same, you don't have to remember if you are focusing a console the first time or not)
 | 
			
		||||
 */
 | 
			
		||||
class IJEditorFocusListener : EditorListener {
 | 
			
		||||
  override fun focusGained(editor: VimEditor) {
 | 
			
		||||
    // We add Vim bindings to all opened editors, including editors used as UI controls rather than just project file
 | 
			
		||||
    // editors. This includes editors used as part of the UI, such as the VCS commit message, or used as read-only
 | 
			
		||||
    // viewers for text output, such as log files in run configurations or the Git Console tab. And editors are used for
 | 
			
		||||
    // interactive stdin/stdout for console-based run configurations.
 | 
			
		||||
    // We want to provide an intuitive experience for working with these additional editors, so we automatically switch
 | 
			
		||||
    // to INSERT mode if they are interactive editors. Recognising these can be a bit tricky.
 | 
			
		||||
    // These additional interactive editors are not file-based, but must have a writable document. However, log output
 | 
			
		||||
    // documents are also writable (the IDE is writing new content as it becomes available) just not user-editable. So
 | 
			
		||||
    // we must also check that the editor is not in read-only "viewer" mode (this includes "rendered" mode, which is
 | 
			
		||||
    // read-only and also hides the caret).
 | 
			
		||||
    // Furthermore, interactive stdin/stdout console output in run configurations is hosted in a read-only editor, but
 | 
			
		||||
    // it can still be edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's
 | 
			
		||||
    // `isViewer` property and allows typing if the associated process (if any) is still running. We can get the
 | 
			
		||||
    // editor's console view and check this ourselves, but we have to wait until the editor has finished initialising
 | 
			
		||||
    // before it's available in user data.
 | 
			
		||||
    // Finally, we have a special check for diff windows. If we compare against clipboard, we get a diff editor that is
 | 
			
		||||
    // not file based, is writable, and not a viewer, but we don't want to treat this as an interactive editor.
 | 
			
		||||
    // 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.
 | 
			
		||||
    val switchToInsertMode = Runnable {
 | 
			
		||||
      val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
 | 
			
		||||
      VimPlugin.getChange().insertBeforeCursor(editor, context)
 | 
			
		||||
    }
 | 
			
		||||
    val ijEditor = editor.ij
 | 
			
		||||
    if (!ijEditor.isViewer &&
 | 
			
		||||
      !EditorHelper.isFileEditor(ijEditor) &&
 | 
			
		||||
      ijEditor.document.isWritable &&
 | 
			
		||||
      !ijEditor.inInsertMode && ijEditor.editorKind != EditorKind.DIFF
 | 
			
		||||
    ) {
 | 
			
		||||
      switchToInsertMode.run()
 | 
			
		||||
    }
 | 
			
		||||
    ApplicationManager.getApplication().invokeLater {
 | 
			
		||||
      if (ijEditor.isDisposed) return@invokeLater
 | 
			
		||||
      val consoleView: ConsoleViewImpl? = ijEditor.getUserData(ConsoleViewImpl.CONSOLE_VIEW_IN_EDITOR_VIEW)
 | 
			
		||||
      if (consoleView != null && consoleView.isRunning && !ijEditor.inInsertMode) {
 | 
			
		||||
        switchToInsertMode.run()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    KeyHandler.getInstance().reset(editor)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -37,7 +37,6 @@ import com.maddyhome.idea.vim.action.VimShortcutKeyAction
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.group.NotificationService
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
@@ -128,7 +127,6 @@ internal object IdeaSpecifics {
 | 
			
		||||
          }
 | 
			
		||||
        ) {
 | 
			
		||||
          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)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,6 @@ package com.maddyhome.idea.vim.listener
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
 | 
			
		||||
public interface VimInsertListener {
 | 
			
		||||
  public fun insertModeStarted(editor: Editor)
 | 
			
		||||
interface VimInsertListener {
 | 
			
		||||
  fun insertModeStarted(editor: Editor)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,6 @@ 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
 | 
			
		||||
@@ -75,7 +74,6 @@ import com.maddyhome.idea.vim.group.IjVimRedrawService
 | 
			
		||||
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
 | 
			
		||||
@@ -94,7 +92,10 @@ 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.helper.vimInitialised
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
@@ -102,7 +103,6 @@ import com.maddyhome.idea.vim.state.mode.inSelectMode
 | 
			
		||||
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
 | 
			
		||||
@@ -130,8 +130,23 @@ import javax.swing.SwingUtilities
 | 
			
		||||
 * Make sure the selected editor isn't the new editor, which can happen if there are no other editors open.
 | 
			
		||||
 */
 | 
			
		||||
private fun getOpeningEditor(newEditor: Editor) = newEditor.project?.let { project ->
 | 
			
		||||
  FileEditorManager.getInstance(project).selectedTextEditor?.takeUnless { it == newEditor }
 | 
			
		||||
  // Some TextEditor implementations create a dummy Editor instance on demand, e.g., while downloading a file to edit
 | 
			
		||||
  // (see BaseRemoteFileEditor). This can cause recursion if the newly opened/created TextEditor is also the currently
 | 
			
		||||
  // selected TextEditor, because we will be notified of the new dummy Editor before it has finished initialisation, and
 | 
			
		||||
  // try to get its opening editor, causing a new dummy Editor to be created and notifications sent, and so on.
 | 
			
		||||
  // This was reported for 232 and 233 (see VIM-3066), but I can't recreate in 241. The callstack looks different, now
 | 
			
		||||
  // using coroutines, so it's possible the deadlock has been broken. However, it's sensible to leave the recursion
 | 
			
		||||
  // guard in.
 | 
			
		||||
  if (openingEditorRecursionGuard) return null
 | 
			
		||||
  openingEditorRecursionGuard = true
 | 
			
		||||
  try {
 | 
			
		||||
    FileEditorManager.getInstance(project).selectedTextEditor?.takeUnless { it == newEditor }
 | 
			
		||||
  }
 | 
			
		||||
  finally {
 | 
			
		||||
    openingEditorRecursionGuard = false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
private var openingEditorRecursionGuard = false
 | 
			
		||||
 | 
			
		||||
internal object VimListenerManager {
 | 
			
		||||
 | 
			
		||||
@@ -150,6 +165,9 @@ internal object VimListenerManager {
 | 
			
		||||
    injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
 | 
			
		||||
    caretVisualAttributesListener.updateAllEditorsCaretsVisual()
 | 
			
		||||
 | 
			
		||||
    val insertTimeRecorder = InsertTimeRecorder()
 | 
			
		||||
    injector.listenersNotifier.modeChangeListeners.add(insertTimeRecorder)
 | 
			
		||||
 | 
			
		||||
    val modeWidgetListener = ModeWidgetListener()
 | 
			
		||||
    injector.listenersNotifier.modeChangeListeners.add(modeWidgetListener)
 | 
			
		||||
    injector.listenersNotifier.myEditorListeners.add(modeWidgetListener)
 | 
			
		||||
@@ -159,7 +177,7 @@ internal object VimListenerManager {
 | 
			
		||||
    injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener)
 | 
			
		||||
    injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener)
 | 
			
		||||
 | 
			
		||||
    injector.listenersNotifier.myEditorListeners.add(KeyHandlerStateResetter())
 | 
			
		||||
    injector.listenersNotifier.myEditorListeners.add(IJEditorFocusListener())
 | 
			
		||||
    injector.listenersNotifier.myEditorListeners.add(ShowCmdWidgetUpdater())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -182,7 +200,7 @@ internal object VimListenerManager {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val optionGroup = VimPlugin.getOptionGroup()
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(IjOptions.number, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
			
		||||
@@ -214,7 +232,7 @@ internal object VimListenerManager {
 | 
			
		||||
      EventFacade.getInstance().restoreTypedActionHandler()
 | 
			
		||||
 | 
			
		||||
      val optionGroup = VimPlugin.getOptionGroup()
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(IjOptions.number, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
 | 
			
		||||
@@ -274,6 +292,11 @@ internal object VimListenerManager {
 | 
			
		||||
      val disposable =
 | 
			
		||||
        Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
 | 
			
		||||
 | 
			
		||||
      // Protect against double initialisation
 | 
			
		||||
      if (editor.getUserData(editorListenersDisposableKey) != null) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val listenersDisposable = Disposer.newDisposable(disposable)
 | 
			
		||||
      editor.putUserData(editorListenersDisposableKey, listenersDisposable)
 | 
			
		||||
 | 
			
		||||
@@ -343,13 +366,13 @@ internal object VimListenerManager {
 | 
			
		||||
  private object VimDocumentListener : DocumentListener {
 | 
			
		||||
    override fun beforeDocumentChange(event: DocumentEvent) {
 | 
			
		||||
      VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
 | 
			
		||||
      SearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
 | 
			
		||||
      IjVimSearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
 | 
			
		||||
      IjVimRedrawService.RedrawListener.beforeDocumentChange(event)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun documentChanged(event: DocumentEvent) {
 | 
			
		||||
      VimMarkServiceImpl.MarkUpdater.documentChanged(event)
 | 
			
		||||
      SearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
 | 
			
		||||
      IjVimSearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
 | 
			
		||||
      IjVimRedrawService.RedrawListener.documentChanged(event)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -473,6 +496,9 @@ internal object VimListenerManager {
 | 
			
		||||
        (it.fileEditor as? TextEditor)?.editor?.let { editor ->
 | 
			
		||||
          if (vimDisabled(editor)) return@let
 | 
			
		||||
 | 
			
		||||
          // Protect against double initialisation, in case the editor was already initialised in editorCreated
 | 
			
		||||
          if (editor.vimInitialised) return@let
 | 
			
		||||
 | 
			
		||||
          val openingEditor = editor.removeUserData(openingEditorKey)
 | 
			
		||||
          val owningEditorWindow = getOwningEditorWindow(editor)
 | 
			
		||||
          val isInSameSplit = owningEditorWindow == openingEditor?.owningEditorWindow
 | 
			
		||||
@@ -716,13 +742,13 @@ internal object VimListenerManager {
 | 
			
		||||
      logger.debug("Mouse clicked")
 | 
			
		||||
 | 
			
		||||
      if (event.area == EditorMouseEventArea.EDITING_AREA) {
 | 
			
		||||
        VimPlugin.getMotion()
 | 
			
		||||
        val editor = event.editor
 | 
			
		||||
        if (ExEntryPanel.getInstance().isActive) {
 | 
			
		||||
          VimPlugin.getProcess().cancelExEntry(editor.vim, false)
 | 
			
		||||
        val commandLine = injector.commandLine.getActiveCommandLine()
 | 
			
		||||
        if (commandLine != null) {
 | 
			
		||||
          injector.processGroup.cancelExEntry(editor.vim, refocusOwningEditor = true, resetCaret = false)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ExOutputModel.getInstance(editor).clear()
 | 
			
		||||
        ExOutputModel.tryGetInstance(editor)?.close()
 | 
			
		||||
 | 
			
		||||
        val caretModel = editor.caretModel
 | 
			
		||||
        if (editor.vim.mode.selectionType != null) {
 | 
			
		||||
@@ -748,12 +774,12 @@ internal object VimListenerManager {
 | 
			
		||||
        event.area != EditorMouseEventArea.FOLDING_OUTLINE_AREA &&
 | 
			
		||||
        event.mouseEvent.button != MouseEvent.BUTTON3
 | 
			
		||||
      ) {
 | 
			
		||||
        VimPlugin.getMotion()
 | 
			
		||||
        if (ExEntryPanel.getInstance().isActive) {
 | 
			
		||||
          VimPlugin.getProcess().cancelExEntry(event.editor.vim, false)
 | 
			
		||||
        val commandLine = injector.commandLine.getActiveCommandLine()
 | 
			
		||||
        if (commandLine != null) {
 | 
			
		||||
          injector.processGroup.cancelExEntry(event.editor.vim, refocusOwningEditor = true, resetCaret = false)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ExOutputModel.getInstance(event.editor).clear()
 | 
			
		||||
        ExOutputModel.getInstance(event.editor).close()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import com.intellij.ide.bookmark.BookmarkType
 | 
			
		||||
import com.intellij.ide.bookmark.BookmarksManager
 | 
			
		||||
import com.intellij.ide.bookmark.LineBookmark
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFileManager
 | 
			
		||||
import java.lang.ref.WeakReference
 | 
			
		||||
 | 
			
		||||
internal class IntellijMark(bookmark: LineBookmark, override val col: Int, project: Project?) : Mark {
 | 
			
		||||
@@ -24,8 +23,8 @@ internal class IntellijMark(bookmark: LineBookmark, override val col: Int, proje
 | 
			
		||||
    get() = getMark()?.line ?: 0
 | 
			
		||||
  override val filepath: String
 | 
			
		||||
    get() = getMark()?.file?.path ?: ""
 | 
			
		||||
  override val protocol: String?
 | 
			
		||||
    get() = getMark()?.file?.let { VirtualFileManager.extractProtocol(it.url) } ?: ""
 | 
			
		||||
  override val protocol: String
 | 
			
		||||
    get() = getMark()?.file?.fileSystem?.protocol ?: ""
 | 
			
		||||
 | 
			
		||||
  fun clear() {
 | 
			
		||||
    val mark = getMark() ?: return
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@ internal val runFromVimKey = Key.create<Boolean>("RunFromVim")
 | 
			
		||||
internal val DataContext.actionStartedFromVim: Boolean
 | 
			
		||||
  get() = (this as? UserDataHolder)?.getUserData(runFromVimKey) ?: false
 | 
			
		||||
 | 
			
		||||
public val DataContext.vim: ExecutionContext
 | 
			
		||||
val DataContext.vim: ExecutionContext
 | 
			
		||||
  get() = IjEditorExecutionContext(this)
 | 
			
		||||
 | 
			
		||||
public val ExecutionContext.ij: DataContext
 | 
			
		||||
val ExecutionContext.ij: DataContext
 | 
			
		||||
  get() = (this as IjEditorExecutionContext).context
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +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.newapi
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExEntryPanel
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class IjExEntryPanel : ExEntryPanel {
 | 
			
		||||
  override fun isActive(): Boolean {
 | 
			
		||||
    return com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstance().isActive
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun clearCurrentAction() {
 | 
			
		||||
    com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstance().entry.clearCurrentAction()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setCurrentActionPromptCharacter(char: Char) {
 | 
			
		||||
    com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstance().entry.setCurrentActionPromptCharacter(char)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,7 +13,6 @@ 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.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorDataContext
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class IjExecutionContextManager : ExecutionContextManagerBase() {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,8 @@ internal class IjLiveRange(val marker: RangeMarker) : LiveRange {
 | 
			
		||||
    get() = marker.startOffset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public val RangeMarker.vim: LiveRange
 | 
			
		||||
val RangeMarker.vim: LiveRange
 | 
			
		||||
  get() = IjLiveRange(this)
 | 
			
		||||
 | 
			
		||||
public val LiveRange.ij: RangeMarker
 | 
			
		||||
val LiveRange.ij: RangeMarker
 | 
			
		||||
  get() = (this as IjLiveRange).marker
 | 
			
		||||
 
 | 
			
		||||
@@ -31,10 +31,10 @@ internal class IjNativeActionManager : NativeActionManager {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public val AnAction.vim: IjNativeAction
 | 
			
		||||
val AnAction.vim: IjNativeAction
 | 
			
		||||
  get() = IjNativeAction(this)
 | 
			
		||||
 | 
			
		||||
public class IjNativeAction(override val action: AnAction) : NativeAction {
 | 
			
		||||
class IjNativeAction(override val action: AnAction) : NativeAction {
 | 
			
		||||
  override fun toString(): String {
 | 
			
		||||
    return "IjNativeAction(action=$action)"
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,6 @@ import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.LogicalPosition
 | 
			
		||||
import com.intellij.openapi.editor.VisualPosition
 | 
			
		||||
import com.maddyhome.idea.vim.api.BufferPosition
 | 
			
		||||
import com.maddyhome.idea.vim.api.CaretRegisterStorage
 | 
			
		||||
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.LocalMarkStorage
 | 
			
		||||
import com.maddyhome.idea.vim.api.SelectionInfo
 | 
			
		||||
@@ -21,12 +19,14 @@ 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.InsertSequence
 | 
			
		||||
import com.maddyhome.idea.vim.common.LiveRange
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VisualChange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.currentInsert
 | 
			
		||||
import com.maddyhome.idea.vim.helper.insertHistory
 | 
			
		||||
import com.maddyhome.idea.vim.helper.lastSelectionInfo
 | 
			
		||||
import com.maddyhome.idea.vim.helper.markStorage
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
import com.maddyhome.idea.vim.helper.registerStorage
 | 
			
		||||
import com.maddyhome.idea.vim.helper.resetVimLastColumn
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimInsertStart
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimLastColumn
 | 
			
		||||
@@ -38,17 +38,6 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
 | 
			
		||||
internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
 | 
			
		||||
 | 
			
		||||
  override val registerStorage: CaretRegisterStorage
 | 
			
		||||
    get() {
 | 
			
		||||
      var storage = this.caret.registerStorage
 | 
			
		||||
      if (storage == null) {
 | 
			
		||||
        storage = CaretRegisterStorageBase(this)
 | 
			
		||||
        this.caret.registerStorage = storage
 | 
			
		||||
      } else if (storage.caret != this) {
 | 
			
		||||
        storage.caret = this
 | 
			
		||||
      }
 | 
			
		||||
      return storage
 | 
			
		||||
    }
 | 
			
		||||
  override val markStorage: LocalMarkStorage
 | 
			
		||||
    get() {
 | 
			
		||||
      var storage = this.caret.markStorage
 | 
			
		||||
@@ -169,15 +158,44 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
 | 
			
		||||
    caret.removeSelection()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  internal fun getInsertSequenceForTime(time: Long): InsertSequence? {
 | 
			
		||||
    val insertHistory = caret.insertHistory
 | 
			
		||||
    for (i in insertHistory.lastIndex downTo 0) {
 | 
			
		||||
      val insertInfo = insertHistory[i]
 | 
			
		||||
      if (time > insertInfo.endNanoTime) return null
 | 
			
		||||
      if (time >= insertInfo.startNanoTime) return insertInfo
 | 
			
		||||
    }
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  internal fun startInsertSequence(startOffset: Int, startNanoTime: Long) {
 | 
			
		||||
    if (caret.currentInsert != null) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    caret.currentInsert = InsertSequence(startOffset, startNanoTime)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  internal fun endInsertSequence(endInsert: Int, endNanoTime: Long) {
 | 
			
		||||
    val currentInsert = caret.currentInsert ?: return
 | 
			
		||||
    currentInsert.endNanoTime = endNanoTime
 | 
			
		||||
    currentInsert.endOffset = endInsert
 | 
			
		||||
    caret.insertHistory.add(currentInsert)
 | 
			
		||||
    caret.currentInsert = null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  internal fun abandonCurrentInsertSequece() {
 | 
			
		||||
    caret.currentInsert = null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun equals(other: Any?): Boolean = this.caret == (other as? IjVimCaret)?.caret
 | 
			
		||||
 | 
			
		||||
  override fun hashCode(): Int = this.caret.hashCode()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public val VimCaret.ij: Caret
 | 
			
		||||
val VimCaret.ij: Caret
 | 
			
		||||
  get() = (this as IjVimCaret).caret
 | 
			
		||||
public val ImmutableVimCaret.ij: Caret
 | 
			
		||||
val ImmutableVimCaret.ij: Caret
 | 
			
		||||
  get() = (this as IjVimCaret).caret
 | 
			
		||||
 | 
			
		||||
public val Caret.vim: VimCaret
 | 
			
		||||
val Caret.vim: VimCaret
 | 
			
		||||
  get() = IjVimCaret(this)
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.common.IndentConfig
 | 
			
		||||
import com.maddyhome.idea.vim.common.LiveRange
 | 
			
		||||
import com.maddyhome.idea.vim.common.ModeChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemBlockSelectionSilently
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
@@ -117,7 +118,10 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
 | 
			
		||||
    return atPosition
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun insertText(atPosition: Int, text: CharSequence) {
 | 
			
		||||
  override fun insertText(caret: VimCaret, atPosition: Int, text: CharSequence) {
 | 
			
		||||
    if (editor.isInsertMode) {
 | 
			
		||||
      injector.undo.startInsertSequence(caret, atPosition, System.nanoTime())
 | 
			
		||||
    }
 | 
			
		||||
    editor.document.insertString(atPosition, text)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -147,21 +151,38 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
 | 
			
		||||
    return editor.caretModel.allCarets.map { IjVimCaret(it) }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override var isFirstCaret = true
 | 
			
		||||
  override var isReversingCarets = false
 | 
			
		||||
  
 | 
			
		||||
  @Suppress("ideavimRunForEachCaret")
 | 
			
		||||
  override fun forEachCaret(action: (VimCaret) -> Unit) {
 | 
			
		||||
    if (editor.vim.inBlockSelection) {
 | 
			
		||||
      action(IjVimCaret(editor.caretModel.primaryCaret))
 | 
			
		||||
    } else {
 | 
			
		||||
      editor.caretModel.runForEachCaret({
 | 
			
		||||
        if (it.isValid) {
 | 
			
		||||
          action(IjVimCaret(it))
 | 
			
		||||
        }
 | 
			
		||||
      }, false)
 | 
			
		||||
      try {
 | 
			
		||||
        editor.caretModel.runForEachCaret({
 | 
			
		||||
          if (it.isValid) {
 | 
			
		||||
            action(IjVimCaret(it))
 | 
			
		||||
            isFirstCaret = false
 | 
			
		||||
          }
 | 
			
		||||
        }, false)
 | 
			
		||||
      } finally {
 | 
			
		||||
        isFirstCaret = true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
 | 
			
		||||
    editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse)
 | 
			
		||||
    isReversingCarets = reverse
 | 
			
		||||
    try {
 | 
			
		||||
      editor.caretModel.runForEachCaret({
 | 
			
		||||
        action(IjVimCaret(it))
 | 
			
		||||
        isFirstCaret = false
 | 
			
		||||
      }, reverse)
 | 
			
		||||
    } finally {
 | 
			
		||||
      isFirstCaret = true
 | 
			
		||||
      isReversingCarets = false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isInForEachCaretScope(): Boolean {
 | 
			
		||||
@@ -256,7 +277,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
 | 
			
		||||
    val vf = EditorHelper.getVirtualFile(editor)
 | 
			
		||||
    return vf?.let {
 | 
			
		||||
      object : VirtualFile {
 | 
			
		||||
        override val path = vf.path
 | 
			
		||||
        override val path: String = vf.path
 | 
			
		||||
        override val protocol: String = vf.fileSystem.protocol
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -494,10 +516,21 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public val Editor.vim: VimEditor
 | 
			
		||||
val Editor.vim: VimEditor
 | 
			
		||||
  get() = IjVimEditor(this)
 | 
			
		||||
public val VimEditor.ij: Editor
 | 
			
		||||
val VimEditor.ij: Editor
 | 
			
		||||
  get() = (this as IjVimEditor).editor
 | 
			
		||||
 | 
			
		||||
public val com.intellij.openapi.util.TextRange.vim: TextRange
 | 
			
		||||
val com.intellij.openapi.util.TextRange.vim: TextRange
 | 
			
		||||
  get() = TextRange(this.startOffset, this.endOffset)
 | 
			
		||||
 | 
			
		||||
internal class InsertTimeRecorder: ModeChangeListener {
 | 
			
		||||
  override fun modeChanged(editor: VimEditor, oldMode: Mode) {
 | 
			
		||||
    editor as IjVimEditor
 | 
			
		||||
    if (oldMode == Mode.INSERT) {
 | 
			
		||||
      val undo = injector.undo
 | 
			
		||||
      val nanoTime = System.nanoTime()
 | 
			
		||||
      editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,8 @@ package com.maddyhome.idea.vim.newapi
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.components.serviceIfCreated
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
 | 
			
		||||
import com.maddyhome.idea.vim.api.EngineEditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExEntryPanel
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContextManager
 | 
			
		||||
import com.maddyhome.idea.vim.api.LocalOptionInitialisationScenario
 | 
			
		||||
import com.maddyhome.idea.vim.api.NativeActionManager
 | 
			
		||||
@@ -29,8 +27,6 @@ import com.maddyhome.idea.vim.api.VimDigraphGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditorGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEnabler
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimExOutputPanel
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimExOutputPanelService
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimExtensionRegistrator
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimFile
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimInjector
 | 
			
		||||
@@ -42,9 +38,11 @@ import com.maddyhome.idea.vim.api.VimMarkService
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimMessages
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimMotionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimOptionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimOutputPanelService
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimProcessGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimPsiService
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimRedrawService
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimRegexServiceBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimRegexpService
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimScrollGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimSearchGroup
 | 
			
		||||
@@ -71,7 +69,6 @@ import com.maddyhome.idea.vim.group.IjVimOptionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjVimPsiService
 | 
			
		||||
import com.maddyhome.idea.vim.group.MacroGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.MotionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.SearchGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.TabService
 | 
			
		||||
import com.maddyhome.idea.vim.group.VimWindowGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.WindowGroup
 | 
			
		||||
@@ -80,9 +77,7 @@ import com.maddyhome.idea.vim.helper.IjActionExecutor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.IjEditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.IjVimStringParser
 | 
			
		||||
import com.maddyhome.idea.vim.helper.UndoRedoHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.history.VimHistory
 | 
			
		||||
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
 | 
			
		||||
import com.maddyhome.idea.vim.macro.VimMacro
 | 
			
		||||
import com.maddyhome.idea.vim.put.VimPut
 | 
			
		||||
import com.maddyhome.idea.vim.register.VimRegisterGroup
 | 
			
		||||
@@ -90,7 +85,6 @@ import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.ui.VimRcFileState
 | 
			
		||||
import com.maddyhome.idea.vim.undo.VimUndoRedo
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.Executor
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.services.PatternService
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.services.VariableService
 | 
			
		||||
import com.maddyhome.idea.vim.yank.VimYankGroup
 | 
			
		||||
import com.maddyhome.idea.vim.yank.YankGroupBase
 | 
			
		||||
@@ -107,14 +101,6 @@ internal class IjVimInjector : VimInjectorBase() {
 | 
			
		||||
 | 
			
		||||
  override val actionExecutor: VimActionExecutor
 | 
			
		||||
    get() = service<IjActionExecutor>()
 | 
			
		||||
  override val exEntryPanel: ExEntryPanel
 | 
			
		||||
    get() = service<IjExEntryPanel>()
 | 
			
		||||
  override val exOutputPanel: VimExOutputPanelService
 | 
			
		||||
    get() = object : VimExOutputPanelService {
 | 
			
		||||
      override fun getPanel(editor: VimEditor): VimExOutputPanel {
 | 
			
		||||
        return ExOutputModel.getInstance(editor.ij)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  override val historyGroup: VimHistory
 | 
			
		||||
    get() = service<HistoryGroup>()
 | 
			
		||||
  override val extensionRegistrator: VimExtensionRegistrator
 | 
			
		||||
@@ -122,7 +108,7 @@ internal class IjVimInjector : VimInjectorBase() {
 | 
			
		||||
  override val tabService: TabService
 | 
			
		||||
    get() = service()
 | 
			
		||||
  override val regexpService: VimRegexpService
 | 
			
		||||
    get() = PatternService
 | 
			
		||||
    get() = VimRegexServiceBase()
 | 
			
		||||
  override val clipboardManager: VimClipboardManager
 | 
			
		||||
    get() = service<IjClipboardManager>()
 | 
			
		||||
  override val searchHelper: VimSearchHelper
 | 
			
		||||
@@ -136,7 +122,7 @@ internal class IjVimInjector : VimInjectorBase() {
 | 
			
		||||
  override val templateManager: VimTemplateManager
 | 
			
		||||
    get() = service<IjTemplateManager>()
 | 
			
		||||
  override val searchGroup: VimSearchGroup
 | 
			
		||||
    get() = service<SearchGroup>()
 | 
			
		||||
    get() = service<IjVimSearchGroup>()
 | 
			
		||||
  override val put: VimPut
 | 
			
		||||
    get() = service<PutGroup>()
 | 
			
		||||
  override val window: VimWindowGroup
 | 
			
		||||
@@ -197,6 +183,8 @@ internal class IjVimInjector : VimInjectorBase() {
 | 
			
		||||
    get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
 | 
			
		||||
  override val commandLine: VimCommandLineService
 | 
			
		||||
    get() = service()
 | 
			
		||||
  override val outputPanel: VimOutputPanelService
 | 
			
		||||
    get() = service()
 | 
			
		||||
 | 
			
		||||
  override val optionGroup: VimOptionGroup
 | 
			
		||||
    get() = service()
 | 
			
		||||
@@ -210,21 +198,14 @@ internal class IjVimInjector : VimInjectorBase() {
 | 
			
		||||
  override val redrawService: VimRedrawService
 | 
			
		||||
    get() = service()
 | 
			
		||||
 | 
			
		||||
  @Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
 | 
			
		||||
  override fun commandStateFor(editor: VimEditor): VimStateMachine {
 | 
			
		||||
    var res = editor.ij.vimStateMachine
 | 
			
		||||
    if (res == null) {
 | 
			
		||||
      res = VimStateMachineImpl()
 | 
			
		||||
      editor.ij.vimStateMachine = res
 | 
			
		||||
    }
 | 
			
		||||
    return res
 | 
			
		||||
    return vimState
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
 | 
			
		||||
  override fun commandStateFor(editor: Any): VimStateMachine {
 | 
			
		||||
    return when (editor) {
 | 
			
		||||
      is VimEditor -> this.commandStateFor(editor)
 | 
			
		||||
      is Editor -> this.commandStateFor(IjVimEditor(editor))
 | 
			
		||||
      else -> error("Unexpected type: $editor")
 | 
			
		||||
    }
 | 
			
		||||
    return vimState
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override val engineEditorHelper: EngineEditorHelper
 | 
			
		||||
@@ -236,10 +217,10 @@ internal class IjVimInjector : VimInjectorBase() {
 | 
			
		||||
/**
 | 
			
		||||
 * Convenience function to get the IntelliJ implementation specific global option accessor
 | 
			
		||||
 */
 | 
			
		||||
public fun VimInjector.globalIjOptions(): GlobalIjOptions = (this.optionGroup as IjVimOptionGroup).getGlobalIjOptions()
 | 
			
		||||
fun VimInjector.globalIjOptions(): GlobalIjOptions = (this.optionGroup as IjVimOptionGroup).getGlobalIjOptions()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convenience function to get the IntelliJ implementation specific option accessor for the given editor's scope
 | 
			
		||||
 */
 | 
			
		||||
public fun VimInjector.ijOptions(editor: VimEditor): EffectiveIjOptions =
 | 
			
		||||
fun VimInjector.ijOptions(editor: VimEditor): EffectiveIjOptions =
 | 
			
		||||
  (this.optionGroup as IjVimOptionGroup).getEffectiveIjOptions(editor)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,15 @@
 | 
			
		||||
package com.maddyhome.idea.vim.newapi
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.components.PersistentStateComponent
 | 
			
		||||
import com.intellij.openapi.components.RoamingType
 | 
			
		||||
import com.intellij.openapi.components.State
 | 
			
		||||
import com.intellij.openapi.components.Storage
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
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.util.Ref
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
@@ -20,6 +27,9 @@ import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimSearchGroupBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction.Companion.fromInt
 | 
			
		||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.TestInputModel.Companion.getInstance
 | 
			
		||||
import com.maddyhome.idea.vim.helper.addSubstitutionConfirmationHighlight
 | 
			
		||||
@@ -27,13 +37,23 @@ import com.maddyhome.idea.vim.helper.highlightSearchResults
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
 | 
			
		||||
import com.maddyhome.idea.vim.helper.shouldIgnoreCase
 | 
			
		||||
import com.maddyhome.idea.vim.helper.updateSearchHighlights
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimLastHighlighters
 | 
			
		||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ModalEntry
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler
 | 
			
		||||
import org.jdom.Element
 | 
			
		||||
import org.jetbrains.annotations.Contract
 | 
			
		||||
import org.jetbrains.annotations.TestOnly
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
 | 
			
		||||
@State(
 | 
			
		||||
  name = "VimSearchSettings",
 | 
			
		||||
  storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)]
 | 
			
		||||
)
 | 
			
		||||
open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Element> {
 | 
			
		||||
  companion object {
 | 
			
		||||
    private val logger = vimLogger<IjVimSearchGroup>()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init {
 | 
			
		||||
    // We use the global option listener instead of the effective listener that gets called for each affected editor
 | 
			
		||||
@@ -117,18 +137,15 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
 | 
			
		||||
      val exEntryPanel: com.maddyhome.idea.vim.ui.ex.ExEntryPanel =
 | 
			
		||||
        com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstanceWithoutShortcuts()
 | 
			
		||||
      exEntryPanel.activate(
 | 
			
		||||
        editor.ij,
 | 
			
		||||
        (context as IjEditorExecutionContext).context,
 | 
			
		||||
      val exEntryPanel = injector.commandLine.createWithoutShortcuts(
 | 
			
		||||
        editor,
 | 
			
		||||
        context,
 | 
			
		||||
        MessageHelper.message("replace.with.0", match),
 | 
			
		||||
        "",
 | 
			
		||||
        1
 | 
			
		||||
      )
 | 
			
		||||
      caret.moveToOffset(startOffset)
 | 
			
		||||
      ModalEntry.activate(editor, keyStrokeProcessor)
 | 
			
		||||
      exEntryPanel.deactivate(true, false)
 | 
			
		||||
      exEntryPanel.deactivate(refocusOwningEditor = true, resetCaret = false)
 | 
			
		||||
    }
 | 
			
		||||
    return result.get()
 | 
			
		||||
  }
 | 
			
		||||
@@ -178,6 +195,104 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
 | 
			
		||||
    updateSearchHighlights(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun saveData(element: Element) {
 | 
			
		||||
    logger.debug("saveData")
 | 
			
		||||
    val search = Element("search")
 | 
			
		||||
 | 
			
		||||
    addOptionalTextElement(search, "last-search", lastSearchPattern)
 | 
			
		||||
    addOptionalTextElement(search, "last-substitute", lastSubstitutePattern)
 | 
			
		||||
    addOptionalTextElement(search, "last-offset", lastPatternTrailing)
 | 
			
		||||
    addOptionalTextElement(search, "last-replace", lastReplaceString)
 | 
			
		||||
    addOptionalTextElement(
 | 
			
		||||
      search,
 | 
			
		||||
      "last-pattern",
 | 
			
		||||
      if (lastPatternType == PatternType.SEARCH) lastSearchPattern else lastSubstitutePattern
 | 
			
		||||
    )
 | 
			
		||||
    addOptionalTextElement(search, "last-dir", getLastSearchDirection().toInt().toString())
 | 
			
		||||
    addOptionalTextElement(search, "show-last", showSearchHighlight.toString())
 | 
			
		||||
 | 
			
		||||
    element.addContent(search)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun addOptionalTextElement(element: Element, name: String, text: String?) {
 | 
			
		||||
    if (text != null) {
 | 
			
		||||
      element.addContent(VimPlugin.getXML().setSafeXmlText(Element(name), text))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun readData(element: Element) {
 | 
			
		||||
    logger.debug("readData")
 | 
			
		||||
    val search = element.getChild("search") ?: return
 | 
			
		||||
 | 
			
		||||
    lastSearchPattern = getSafeChildText(search, "last-search")
 | 
			
		||||
    lastSubstitutePattern = getSafeChildText(search, "last-substitute")
 | 
			
		||||
    lastReplaceString = getSafeChildText(search, "last-replace")
 | 
			
		||||
    lastPatternTrailing = getSafeChildText(search, "last-offset", "")
 | 
			
		||||
 | 
			
		||||
    val lastPatternText = getSafeChildText(search, "last-pattern")
 | 
			
		||||
    if (lastPatternText == null || lastPatternText == lastSearchPattern) {
 | 
			
		||||
      lastPatternType = PatternType.SEARCH
 | 
			
		||||
    } else {
 | 
			
		||||
      lastPatternType = PatternType.SUBSTITUTE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val dir = search.getChild("last-dir")
 | 
			
		||||
    try {
 | 
			
		||||
      lastDirection = fromInt(dir.text.toInt())
 | 
			
		||||
    } catch (e: NumberFormatException) {
 | 
			
		||||
      lastDirection = Direction.FORWARDS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val show = search.getChild("show-last")
 | 
			
		||||
    val disableHighlight = injector.globalOptions().viminfo.contains("h")
 | 
			
		||||
    showSearchHighlight = !disableHighlight && show.text.toBoolean()
 | 
			
		||||
    if (logger.isDebug()) {
 | 
			
		||||
      logger.debug("show=" + show + "(" + show.text + ")")
 | 
			
		||||
      logger.debug("showSearchHighlight=$showSearchHighlight")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getSafeChildText(element: Element, name: String): String? {
 | 
			
		||||
    val child = element.getChild(name)
 | 
			
		||||
    return if (child != null) VimPlugin.getXML().getSafeXmlText(child) else null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getSafeChildText(element: Element, name: String, defaultValue: String): String {
 | 
			
		||||
    val child = element.getChild(name)
 | 
			
		||||
    if (child != null) {
 | 
			
		||||
      val value = VimPlugin.getXML().getSafeXmlText(child)
 | 
			
		||||
      return value ?: defaultValue
 | 
			
		||||
    }
 | 
			
		||||
    return defaultValue
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getState(): Element? {
 | 
			
		||||
    val element = Element("search")
 | 
			
		||||
    saveData(element)
 | 
			
		||||
    return element
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun loadState(state: Element) {
 | 
			
		||||
    readData(state)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Updates search highlights when the selected editor changes
 | 
			
		||||
   */
 | 
			
		||||
  fun fileEditorManagerSelectionChangedCallback(@Suppress("unused") event: FileEditorManagerEvent) {
 | 
			
		||||
    updateSearchHighlights(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun turnOn() {
 | 
			
		||||
    updateSearchHighlights(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun turnOff() {
 | 
			
		||||
    val show = showSearchHighlight
 | 
			
		||||
    clearSearchHighlight()
 | 
			
		||||
    showSearchHighlight = show
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class IjSearchHighlight(private val editor: Editor, private val highlighter: RangeHighlighter) :
 | 
			
		||||
    SearchHighlight() {
 | 
			
		||||
 | 
			
		||||
@@ -185,4 +300,60 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
 | 
			
		||||
      editor.markupModel.removeHighlighter(highlighter)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes and adds highlights for current search pattern when the document is edited
 | 
			
		||||
   */
 | 
			
		||||
  class DocumentSearchListener @Contract(pure = true) private constructor() : DocumentListener {
 | 
			
		||||
    override fun documentChanged(event: DocumentEvent) {
 | 
			
		||||
      // 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.
 | 
			
		||||
      val document = event.document
 | 
			
		||||
      for (vimEditor in injector.editorGroup.getEditors(IjVimDocument(document))) {
 | 
			
		||||
        val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
        var existingHighlighters = editor.vimLastHighlighters ?: continue
 | 
			
		||||
 | 
			
		||||
        if (logger.isDebug()) {
 | 
			
		||||
          logger.debug("hls=$existingHighlighters")
 | 
			
		||||
          logger.debug("event=$event")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // We can only re-highlight whole lines, so clear any highlights in the affected lines.
 | 
			
		||||
        // If we're deleting lines, this will clear + re-highlight the new current line, which hasn't been modified.
 | 
			
		||||
        // However, we still want to re-highlight this line in case any highlights cross the line boundaries.
 | 
			
		||||
        // If we're adding lines, this will clear + re-highlight all new lines.
 | 
			
		||||
        val startPosition = editor.offsetToLogicalPosition(event.offset)
 | 
			
		||||
        val endPosition = editor.offsetToLogicalPosition(event.offset + event.newLength)
 | 
			
		||||
        val startLineOffset = document.getLineStartOffset(startPosition.line)
 | 
			
		||||
        val endLineOffset = document.getLineEndOffset(endPosition.line)
 | 
			
		||||
 | 
			
		||||
        // Remove any highlights that have already been deleted, and remove + clear those that intersect with the change
 | 
			
		||||
        val iter = existingHighlighters.iterator()
 | 
			
		||||
        while (iter.hasNext()) {
 | 
			
		||||
          val highlighter = iter.next()
 | 
			
		||||
          if (!highlighter.isValid) {
 | 
			
		||||
            iter.remove()
 | 
			
		||||
          } else if (highlighter.textRange.intersects(startLineOffset, endLineOffset)) {
 | 
			
		||||
            iter.remove()
 | 
			
		||||
            editor.markupModel.removeHighlighter(highlighter)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        (injector.searchGroup as VimSearchGroupBase).highlightSearchLines(editor.vim, startPosition.line, endPosition.line)
 | 
			
		||||
 | 
			
		||||
        if (logger.isDebug()) {
 | 
			
		||||
          existingHighlighters = editor.vimLastHighlighters!!
 | 
			
		||||
          logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line)
 | 
			
		||||
          logger.debug("hls=$existingHighlighters")
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
      var INSTANCE: DocumentSearchListener = DocumentSearchListener()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,14 +13,10 @@ import com.intellij.openapi.diagnostic.Logger
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimSearchHelperBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.PsiHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchOptions
 | 
			
		||||
import com.maddyhome.idea.vim.helper.findMisspelledWords
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntComparator
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntComparators
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class IjVimSearchHelper : VimSearchHelperBase() {
 | 
			
		||||
@@ -38,28 +34,6 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
 | 
			
		||||
    return PsiHelper.findMethodStart(editor.ij, caret.ij.offset, count)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun findPattern(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    pattern: String?,
 | 
			
		||||
    startOffset: Int,
 | 
			
		||||
    count: Int,
 | 
			
		||||
    searchOptions: EnumSet<SearchOptions>?,
 | 
			
		||||
  ): TextRange? {
 | 
			
		||||
    return if (injector.globalIjOptions().useNewRegex) super.findPattern(editor, pattern, startOffset, count, searchOptions)
 | 
			
		||||
    else SearchHelper.findPattern(editor.ij, pattern, startOffset, count, searchOptions)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun findAll(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    pattern: String,
 | 
			
		||||
    startLine: Int,
 | 
			
		||||
    endLine: Int,
 | 
			
		||||
    ignoreCase: Boolean,
 | 
			
		||||
  ): List<TextRange> {
 | 
			
		||||
    return if (injector.globalIjOptions().useNewRegex) super.findAll(editor, pattern, startLine, endLine, ignoreCase)
 | 
			
		||||
    else SearchHelper.findAll(editor.ij, pattern, startLine, endLine, ignoreCase)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
 | 
			
		||||
    val startOffset: Int
 | 
			
		||||
    val endOffset: Int
 | 
			
		||||
@@ -80,6 +54,6 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO add it to PsiService
 | 
			
		||||
    return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
 | 
			
		||||
    return findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -18,7 +18,7 @@ import com.intellij.openapi.wm.WindowManager
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory
 | 
			
		||||
 | 
			
		||||
public class WidgetState : ApplicationUsagesCollector() {
 | 
			
		||||
class WidgetState : ApplicationUsagesCollector() {
 | 
			
		||||
  override fun getGroup(): EventLogGroup = GROUP
 | 
			
		||||
 | 
			
		||||
  override fun getMetrics(): Set<MetricEvent> {
 | 
			
		||||
@@ -54,7 +54,7 @@ public class WidgetState : ApplicationUsagesCollector() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
  companion object {
 | 
			
		||||
    private val GROUP = EventLogGroup("vim.widget", 1, "FUS")
 | 
			
		||||
 | 
			
		||||
    private val IS_MODE_WIDGET_SHOWN = EventFields.Boolean("is-mode-widget-shown")
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
  private @Nullable LayoutManager myOldLayout = null;
 | 
			
		||||
  private boolean myWasOpaque = false;
 | 
			
		||||
 | 
			
		||||
  private boolean myActive = false;
 | 
			
		||||
  public boolean myActive = false;
 | 
			
		||||
 | 
			
		||||
  private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class);
 | 
			
		||||
 | 
			
		||||
@@ -90,12 +90,16 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
    updateUI();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static @Nullable ExOutputPanel getNullablePanel(@NotNull Editor editor) {
 | 
			
		||||
    return UserDataManager.getVimMorePanel(editor);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static boolean isPanelActive(@NotNull Editor editor) {
 | 
			
		||||
    return UserDataManager.getVimMorePanel(editor) != null;
 | 
			
		||||
    return getNullablePanel(editor) != null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static @NotNull ExOutputPanel getInstance(@NotNull Editor editor) {
 | 
			
		||||
    ExOutputPanel panel = UserDataManager.getVimMorePanel(editor);
 | 
			
		||||
    ExOutputPanel panel = getNullablePanel(editor);
 | 
			
		||||
    if (panel == null) {
 | 
			
		||||
      panel = new ExOutputPanel(editor);
 | 
			
		||||
      UserDataManager.setVimMorePanel(editor, panel);
 | 
			
		||||
@@ -135,6 +139,9 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
      myText.setBorder(null);
 | 
			
		||||
      myScrollPane.setBorder(null);
 | 
			
		||||
      myLabel.setForeground(myText.getForeground());
 | 
			
		||||
 | 
			
		||||
      // Make sure the panel is positioned correctly in case we're changing font size
 | 
			
		||||
      positionPanel();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -144,7 +151,7 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    myText.setText(data);
 | 
			
		||||
    myText.setFont(UiHelper.selectFont(data));
 | 
			
		||||
    myText.setFont(UiHelper.selectEditorFont(myEditor, data));
 | 
			
		||||
    myText.setCaretPosition(0);
 | 
			
		||||
    if (!data.isEmpty()) {
 | 
			
		||||
      activate();
 | 
			
		||||
@@ -189,7 +196,7 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns on the more window for the given editor
 | 
			
		||||
   */
 | 
			
		||||
  private void activate() {
 | 
			
		||||
  public void activate() {
 | 
			
		||||
    JRootPane root = SwingUtilities.getRootPane(myEditor.getContentComponent());
 | 
			
		||||
    myOldGlass = (JComponent)root.getGlassPane();
 | 
			
		||||
    if (myOldGlass != null) {
 | 
			
		||||
@@ -213,8 +220,8 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void setFontForElements() {
 | 
			
		||||
    myText.setFont(UiHelper.selectFont(myText.getText()));
 | 
			
		||||
    myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
 | 
			
		||||
    myText.setFont(UiHelper.selectEditorFont(myEditor, myText.getText()));
 | 
			
		||||
    myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void scrollLine() {
 | 
			
		||||
@@ -242,7 +249,7 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
 | 
			
		||||
  private void badKey() {
 | 
			
		||||
    myLabel.setText(MessageHelper.message("more.ret.line.space.page.d.half.page.q.quit"));
 | 
			
		||||
    myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
 | 
			
		||||
    myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void scrollOffset(int more) {
 | 
			
		||||
@@ -258,12 +265,18 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
    else {
 | 
			
		||||
      myLabel.setText(MessageHelper.message("ex.output.panel.more"));
 | 
			
		||||
    }
 | 
			
		||||
    myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
 | 
			
		||||
    myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void positionPanel() {
 | 
			
		||||
    final JComponent contentComponent = myEditor.getContentComponent();
 | 
			
		||||
    Container scroll = SwingUtilities.getAncestorOfClass(JScrollPane.class, contentComponent);
 | 
			
		||||
    JRootPane rootPane = SwingUtilities.getRootPane(contentComponent);
 | 
			
		||||
    if (scroll == null || rootPane == null) {
 | 
			
		||||
      // These might be null if we're invoked during component initialisation and before it's been added to the tree
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setSize(scroll.getSize());
 | 
			
		||||
 | 
			
		||||
    myLineHeight = myText.getFontMetrics(myText.getFont()).getHeight();
 | 
			
		||||
@@ -277,8 +290,7 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
    Rectangle bounds = scroll.getBounds();
 | 
			
		||||
    bounds.translate(0, scroll.getHeight() - height);
 | 
			
		||||
    bounds.height = height;
 | 
			
		||||
    Point pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.getLocation(),
 | 
			
		||||
                                            SwingUtilities.getRootPane(contentComponent).getGlassPane());
 | 
			
		||||
    Point pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.getLocation(), rootPane.getGlassPane());
 | 
			
		||||
    bounds.setLocation(pos);
 | 
			
		||||
    setBounds(bounds);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,11 @@ import javax.swing.KeyStroke
 | 
			
		||||
/**
 | 
			
		||||
 * @author dhleong
 | 
			
		||||
 */
 | 
			
		||||
public object ModalEntry {
 | 
			
		||||
object ModalEntry {
 | 
			
		||||
 | 
			
		||||
  public val LOG: Logger = logger<ModalEntry>()
 | 
			
		||||
  val LOG: Logger = logger<ModalEntry>()
 | 
			
		||||
 | 
			
		||||
  public inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
 | 
			
		||||
  inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
 | 
			
		||||
    // Firstly we pull the unfinished keys of the current mapping
 | 
			
		||||
    val mappingStack = KeyHandler.getInstance().keyStack
 | 
			
		||||
    LOG.trace("Dumping key stack:")
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user