mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-10-31 20:17:13 +01:00 
			
		
		
		
	Compare commits
	
		
			157 Commits
		
	
	
		
			57ddf2083e
			...
			c79286b9b0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c79286b9b0 | |||
| 5f59b47b19 | |||
| 8d51537f79 | |||
| 052de10e3a | |||
| 9ece9a7a04 | |||
| 84c868afc3 | |||
| f29ebab390 | |||
| 0cb8bba3fd | |||
| c0ff2b5cd0 | |||
| 460234553d | |||
| cdd5b2abaf | |||
| 9db1732eb3 | |||
| 63e292b21f | |||
| 362175431d | |||
| 5e2cab4eda | |||
| b63792c8f8 | |||
| f543b6a1d1 | |||
| d367b3bc72 | |||
| da2d8d707f | |||
|   | 75a417773f | ||
|   | b3b3ee4f21 | ||
|   | 07b1db4b28 | ||
|   | dc775a0f22 | ||
|   | 10228f953e | ||
|   | afceecadbe | ||
|   | b2a4e59571 | ||
|   | b0b944bbf3 | ||
|   | 89a3d74b93 | ||
|   | f4eef04750 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | e62c86b99f | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 82bd792da5 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a58c9065e6 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 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 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 16e18f3ca7 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | ee0d67fbbb | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 450527f172 | ||
|   | 135518ee39 | ||
|   | 58715ecb5f | ||
|   | 75e26b101d | ||
|   | 6421a6face | ||
|   | 948520f90a | ||
|   | 0765118ce2 | ||
|   | efd4c7b617 | ||
|   | c5346fbece | ||
|   | fe8e8ccc3e | ||
|   | eae111bc2c | ||
|   | 6a6c1dc6b4 | ||
|   | 86bbb282ab | ||
|   | 28aa156cb7 | ||
|   | a7814e69de | ||
|   | 1452c116cf | ||
|   | 23dfc4b339 | ||
|   | 931d4be972 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 7dceda587b | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 52a969074d | ||
|   | e7b87d31cf | ||
|   | 5eb0fae08f | ||
|   | 798d805a0f | ||
|   | 0d4ba06e57 | ||
|   | 4913b13a2d | ||
|   | b0bab992db | ||
|   | af5f4227b7 | ||
|   | fa6a694ea4 | ||
|   | 1da7ffc052 | ||
|   | c673f5818c | ||
|   | ec78a87644 | ||
|   | 69d14ddcf5 | ||
|   | f62819df00 | ||
|   | 39a85b6bc2 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | f76ae3e867 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 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-36 | ||||
| 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 | ||||
|       } | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ import com.maddyhome.idea.vim.helper.vimExOutput | ||||
| import com.maddyhome.idea.vim.ui.ExOutputPanel | ||||
|  | ||||
| // 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 constructor(private val myEditor: Editor) : VimExOutputPanel { | ||||
|   private var isActiveInTestMode = false | ||||
|  | ||||
|   override val isActive: Boolean | ||||
| @@ -28,14 +28,18 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V | ||||
|     get() = if (!ApplicationManager.getApplication().isUnitTestMode) { | ||||
|       ExOutputPanel.getInstance(myEditor).text | ||||
|     } else { | ||||
|       field | ||||
|       // 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 ?: "") | ||||
|         ExOutputPanel.getInstance(myEditor).setText(newValue ?: "") | ||||
|       } else { | ||||
|         field = value | ||||
|         isActiveInTestMode = !value.isNullOrEmpty() | ||||
|         field = newValue | ||||
|         isActiveInTestMode = !newValue.isNullOrEmpty() | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @@ -56,9 +60,9 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   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) | ||||
| @@ -66,5 +70,8 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V | ||||
|       } | ||||
|       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 | ||||
| @@ -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,7 +150,7 @@ public object VimExtensionFacade { | ||||
|  | ||||
|   /** Returns a single key stroke from the user input similar to 'getchar()'. */ | ||||
|   @JvmStatic | ||||
|   public fun inputKeyStroke(editor: Editor): KeyStroke { | ||||
|   fun inputKeyStroke(editor: Editor): KeyStroke { | ||||
|     if (editor.vim.vimStateMachine.isDotRepeatInProgress) { | ||||
|       val input = Extension.consumeKeystroke() | ||||
|       LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input") | ||||
| @@ -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 | ||||
| @@ -183,10 +184,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,22 +213,28 @@ 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. | ||||
|     // 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); | ||||
|     } | ||||
|  | ||||
|     // 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 for interactive editors. Recognising these can be a bit tricky. | ||||
|     // 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, 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. | ||||
|     // 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. | ||||
|     Runnable switchToInsertMode = () -> { | ||||
| @@ -234,7 +245,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor | ||||
|     if (!editor.isViewer() && | ||||
|         !EditorHelper.isFileEditor(editor) && | ||||
|         editor.getDocument().isWritable() && | ||||
|         !CommandStateHelper.inInsertMode(editor)) { | ||||
|         !CommandStateHelper.inInsertMode(editor) && | ||||
|         editor.getEditorKind() != EditorKind.DIFF) { | ||||
|       switchToInsertMode.run(); | ||||
|     } | ||||
|     ApplicationManager.getApplication().invokeLater( | ||||
| @@ -253,6 +265,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 +330,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 +397,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), 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 (getInstance(IjVimEditor(editor)).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")) { | ||||
|   | ||||
| @@ -314,7 +314,7 @@ internal class MotionGroup : VimMotionGroupBase() { | ||||
|               } | ||||
|               is Mode.CMD_LINE -> { | ||||
|                 injector.processGroup.cancelExEntry(vimEditor, false) | ||||
|                 ExOutputModel.getInstance(editor).clear() | ||||
|                 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) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -144,7 +144,7 @@ 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) | ||||
|   } | ||||
| @@ -163,7 +163,7 @@ public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeLi | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   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,55 +21,15 @@ 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") | ||||
|   | ||||
| @@ -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() | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
										
											
												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() | ||||
|  | ||||
| @@ -128,6 +127,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. | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -75,7 +75,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 +93,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 +104,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 +131,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 +166,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) | ||||
| @@ -182,7 +201,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 +233,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 +293,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 +367,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 +497,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 +743,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, false) | ||||
|         } | ||||
|  | ||||
|         ExOutputModel.getInstance(editor).clear() | ||||
|         ExOutputModel.tryGetInstance(editor)?.close() | ||||
|  | ||||
|         val caretModel = editor.caretModel | ||||
|         if (editor.vim.mode.selectionType != null) { | ||||
| @@ -748,12 +775,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, 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,40 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { | ||||
|     return editor.caretModel.allCarets.map { IjVimCaret(it) } | ||||
|   } | ||||
|  | ||||
|   override var isFirstCaret = false | ||||
|   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) | ||||
|       isFirstCaret = true | ||||
|       try { | ||||
|         editor.caretModel.runForEachCaret({ | ||||
|           if (it.isValid) { | ||||
|             action(IjVimCaret(it)) | ||||
|             isFirstCaret = false | ||||
|           } | ||||
|         }, false) | ||||
|       } finally { | ||||
|         isFirstCaret = false | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) { | ||||
|     editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse) | ||||
|     isFirstCaret = true | ||||
|     isReversingCarets = reverse | ||||
|     try { | ||||
|       editor.caretModel.runForEachCaret({ | ||||
|         action(IjVimCaret(it)) | ||||
|         isFirstCaret = false | ||||
|       }, reverse) | ||||
|     } finally { | ||||
|       isFirstCaret = false | ||||
|       isReversingCarets = false | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   override fun isInForEachCaretScope(): Boolean { | ||||
| @@ -256,7 +279,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 +518,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) } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,6 @@ 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 | ||||
| @@ -45,6 +44,7 @@ import com.maddyhome.idea.vim.api.VimOptionGroup | ||||
| 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 +71,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 | ||||
| @@ -90,7 +89,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,8 +105,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 { | ||||
| @@ -122,7 +118,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 +132,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 | ||||
| @@ -236,10 +232,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") | ||||
|   | ||||
| @@ -135,6 +135,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 +147,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(); | ||||
| @@ -213,8 +216,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 +245,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 +261,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 +286,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:") | ||||
|   | ||||
| @@ -19,6 +19,7 @@ import com.intellij.openapi.wm.StatusBarWidgetFactory | ||||
| import com.intellij.openapi.wm.WindowManager | ||||
| import com.intellij.openapi.wm.impl.status.EditorBasedWidget | ||||
| import com.intellij.util.Consumer | ||||
| import com.maddyhome.idea.vim.KeyHandler | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.api.VimEditor | ||||
| import com.maddyhome.idea.vim.api.globalOptions | ||||
| @@ -26,9 +27,7 @@ import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.common.EditorListener | ||||
| import com.maddyhome.idea.vim.helper.EngineStringHelper | ||||
| import com.maddyhome.idea.vim.helper.VimNlsSafe | ||||
| import com.maddyhome.idea.vim.helper.vimStateMachine | ||||
| import com.maddyhome.idea.vim.newapi.ij | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.options.GlobalOptionChangeListener | ||||
| import org.jetbrains.annotations.NonNls | ||||
| import java.awt.Component | ||||
| @@ -61,8 +60,8 @@ internal object ShowCmd { | ||||
|   fun getFullText(editor: Editor?): String { | ||||
|     if (!injector.globalOptions().showcmd || editor == null || editor.isDisposed) return "" | ||||
|  | ||||
|     val editorState = editor.vim.vimStateMachine | ||||
|     return EngineStringHelper.toPrintableCharacters(editorState.commandBuilder.keys + editorState.mappingState.keys) | ||||
|     val keyState = KeyHandler.getInstance().keyHandlerState | ||||
|     return EngineStringHelper.toPrintableCharacters(keyState.commandBuilder.keys + keyState.mappingState.keys) | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -86,11 +86,20 @@ internal class VimEmulationConfigurable : Configurable { | ||||
|  | ||||
|     init { | ||||
|       val shortcutConflictsTable = VimShortcutConflictsTable(model) | ||||
|       val rowsToBeChangedOnToggleAllHandlers = getRowsToBeToggled() | ||||
|  | ||||
|       layout = BorderLayout() | ||||
|       val decorator = ToolbarDecorator.createDecorator(shortcutConflictsTable) | ||||
|       decorator.setToolbarPosition(ActionToolbarPosition.RIGHT) | ||||
|       decorator.addExtraAction(CopyForRcAction(model)) | ||||
|       decorator.addExtraAction(ResetHandlersAction(model, shortcutConflictsTable)) | ||||
|       decorator.addExtraAction( | ||||
|         ToggleAllHandlersAction( | ||||
|           model, | ||||
|           shortcutConflictsTable, | ||||
|           rowsToBeChangedOnToggleAllHandlers | ||||
|         ) | ||||
|       ) | ||||
|       val scrollPane = decorator.createPanel() | ||||
|       scrollPane.border = LineBorder(JBColor.border()) | ||||
|       val conflictsPanel = JPanel(BorderLayout()) | ||||
| @@ -101,6 +110,19 @@ internal class VimEmulationConfigurable : Configurable { | ||||
|       addHelpLine(model) | ||||
|     } | ||||
|  | ||||
|     private fun getRowsToBeToggled(): MutableList<Int> { | ||||
|       val rowsToChange = mutableListOf<Int>() | ||||
|  | ||||
|       for (rowIndex in 0 until model.rows.size) { | ||||
|         val row = model.rows[rowIndex] | ||||
|         if (row.owner is AllModes && row.owner == ShortcutOwnerInfo.allUndefined) { | ||||
|           rowsToChange.add(rowIndex) | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       return rowsToChange; | ||||
|     } | ||||
|  | ||||
|     fun addHelpLine(model: VimShortcutConflictsTable.Model) { | ||||
|       val firstPerMode = ContainerUtil.find(model.rows) { row: VimShortcutConflictsTable.Row -> | ||||
|         val owner: ShortcutOwnerInfo = row.owner | ||||
| @@ -328,6 +350,40 @@ internal class VimEmulationConfigurable : Configurable { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private class ToggleAllHandlersAction( | ||||
|     private val myModel: VimShortcutConflictsTable.Model, | ||||
|     private val myTable: VimShortcutConflictsTable, | ||||
|     private val rowsToBeChangedOnToggleAllHandlers: MutableList<Int>, | ||||
|   ) : DumbAwareAction( | ||||
|     "Toggle all unset Handlers", | ||||
|     "Toggle all action handlers to use either IDE or Vim shortcuts", | ||||
|     AllIcons.Actions.ChangeView, | ||||
|   ) { | ||||
|     override fun update(e: AnActionEvent) { | ||||
|       e.presentation.isEnabled = true | ||||
|     } | ||||
|  | ||||
|     override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT | ||||
|  | ||||
|     override fun actionPerformed(e: AnActionEvent) { | ||||
|       TableUtil.stopEditing(myTable) | ||||
|  | ||||
|       for (rowIndex in rowsToBeChangedOnToggleAllHandlers) { | ||||
|         val row = myModel.rows[rowIndex] | ||||
|         row.owner = | ||||
|           when (row.owner) { | ||||
|             ShortcutOwnerInfo.allUndefined -> ShortcutOwnerInfo.allVim | ||||
|             ShortcutOwnerInfo.allVim -> ShortcutOwnerInfo.allIde | ||||
|             else -> ShortcutOwnerInfo.allUndefined | ||||
|           } | ||||
|       } | ||||
|  | ||||
|       IdeFocusManager.getGlobalInstance() | ||||
|         .doWhenFocusSettlesDown { IdeFocusManager.getGlobalInstance().requestFocus(myTable, true) } | ||||
|       TableUtil.updateScroller(myTable) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   class ResetHandlersAction( | ||||
|     private val myModel: VimShortcutConflictsTable.Model, | ||||
|     private val myTable: VimShortcutConflictsTable, | ||||
|   | ||||
| @@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.ui.ex | ||||
| import com.intellij.openapi.diagnostic.logger | ||||
| import com.intellij.openapi.editor.textarea.TextComponentEditorImpl | ||||
| import com.maddyhome.idea.vim.KeyHandler | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.api.LocalOptionInitialisationScenario | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| @@ -76,7 +75,7 @@ internal class CompleteEntryAction : TextAction(ExEditorKit.CompleteEntry) { | ||||
|     // * The key handler routines get the chance to clean up and reset state | ||||
|     val entry = ExEntryPanel.getInstance().entry | ||||
|     val keyHandler = KeyHandler.getInstance() | ||||
|     keyHandler.handleKey(entry.editor.vim, stroke, entry.context.vim, keyHandler.keyHandlerState) | ||||
|     keyHandler.handleKey(entry.editor!!.vim, stroke, entry.context.vim, keyHandler.keyHandlerState) | ||||
|   } | ||||
|  | ||||
|   companion object { | ||||
| @@ -198,7 +197,7 @@ internal class DeletePreviousWordAction : TextAction(DefaultEditorKit.deletePrev | ||||
|     target.saveLastEntry() | ||||
|     val doc = target.document | ||||
|     val caret = target.caret | ||||
|     val project = target.editor.project | ||||
|     val project = target.editor!!.project | ||||
|  | ||||
|     // Create a VimEditor instance on the Swing text field which we can pass to the search helpers. We need an editor | ||||
|     // rather than just working on a buffer because the search helpers need local options (specifically the local to | ||||
| @@ -208,7 +207,7 @@ internal class DeletePreviousWordAction : TextAction(DefaultEditorKit.deletePrev | ||||
|     // This will mean we always have an editor that has been initialised for options, etc. But also means that we can | ||||
|     // share the command line entry actions between IdeaVim implementations | ||||
|     val editor = TextComponentEditorImpl(project, target).vim | ||||
|     injector.optionGroup.initialiseLocalOptions(editor, target.editor.vim, LocalOptionInitialisationScenario.CMD_LINE) | ||||
|     injector.optionGroup.initialiseLocalOptions(editor, target.editor!!.vim, LocalOptionInitialisationScenario.CMD_LINE) | ||||
|  | ||||
|     val offset = injector.searchHelper.findNextWord(editor, caret.dot, -1, bigWord = false, spaceWords = false) | ||||
|     if (logger.isDebugEnabled) logger.debug("offset=$offset") | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| package com.maddyhome.idea.vim.ui.ex; | ||||
|  | ||||
| import com.intellij.openapi.util.SystemInfo; | ||||
| import com.maddyhome.idea.vim.api.VimCommandLine; | ||||
| import org.jetbrains.annotations.NotNull; | ||||
|  | ||||
| import javax.swing.text.*; | ||||
| @@ -18,6 +19,8 @@ import java.text.AttributedCharacterIterator; | ||||
| import java.text.AttributedString; | ||||
| import java.util.Map; | ||||
|  | ||||
| import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; | ||||
|  | ||||
| /** | ||||
|  * This document provides insert/overwrite mode | ||||
|  * Note that PlainDocument will remove CRs from text for single line text fields | ||||
| @@ -29,6 +32,10 @@ public class ExDocument extends PlainDocument { | ||||
|    * Toggles the insert/overwrite state | ||||
|    */ | ||||
|   void toggleInsertReplace() { | ||||
|     VimCommandLine commandLine = injector.getCommandLine().getActiveCommandLine(); | ||||
|     if (commandLine != null) { | ||||
|       commandLine.toggleReplaceMode(); | ||||
|     } | ||||
|     overwrite = !overwrite; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -122,7 +122,7 @@ internal object ExEditorKit : DefaultEditorKit() { | ||||
|               val entry = ExEntryPanel.getInstance().entry | ||||
|               val editor = entry.editor | ||||
|               val keyHandler = KeyHandler.getInstance() | ||||
|               keyHandler.handleKey(editor.vim, key, entry.context.vim, keyHandler.keyHandlerState) | ||||
|               keyHandler.handleKey(editor!!.vim, key, entry.context.vim, keyHandler.keyHandlerState) | ||||
|             } else { | ||||
|               val event = ActionEvent(e.source, e.id, c.toString(), e.getWhen(), e.modifiers) | ||||
|               super.actionPerformed(event) | ||||
|   | ||||
| @@ -16,6 +16,7 @@ import com.intellij.openapi.diagnostic.Logger; | ||||
| import com.intellij.openapi.editor.Caret; | ||||
| import com.intellij.openapi.editor.Editor; | ||||
| import com.intellij.openapi.editor.ScrollingModel; | ||||
| import com.intellij.openapi.wm.IdeFocusManager; | ||||
| import com.intellij.ui.DocumentAdapter; | ||||
| import com.intellij.util.IJSwingUtilities; | ||||
| import com.maddyhome.idea.vim.EventFacade; | ||||
| @@ -30,8 +31,6 @@ import com.maddyhome.idea.vim.helper.SearchHighlightsHelper; | ||||
| import com.maddyhome.idea.vim.helper.UiHelper; | ||||
| import com.maddyhome.idea.vim.newapi.IjVimCaret; | ||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor; | ||||
| import com.maddyhome.idea.vim.regexp.CharPointer; | ||||
| import com.maddyhome.idea.vim.regexp.RegExp; | ||||
| import com.maddyhome.idea.vim.ui.ExPanelBorder; | ||||
| import com.maddyhome.idea.vim.vimscript.model.commands.Command; | ||||
| import com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand; | ||||
| @@ -48,6 +47,7 @@ import java.awt.*; | ||||
| import java.awt.event.ComponentAdapter; | ||||
| import java.awt.event.ComponentEvent; | ||||
| import java.awt.event.ComponentListener; | ||||
| import java.lang.ref.WeakReference; | ||||
|  | ||||
| import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions; | ||||
| import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; | ||||
| @@ -58,7 +58,9 @@ import static com.maddyhome.idea.vim.group.KeyGroup.toShortcutSet; | ||||
|  */ | ||||
| public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|   public static ExEntryPanel instance; | ||||
|   private static ExEntryPanel instanceWithoutShortcuts; | ||||
|   public static ExEntryPanel instanceWithoutShortcuts; | ||||
|   private boolean isReplaceMode = false; | ||||
|   private WeakReference<Editor> weakEditor = null; | ||||
|  | ||||
|   private ExEntryPanel(boolean enableShortcuts) { | ||||
|     label = new JLabel(" "); | ||||
| @@ -125,6 +127,18 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public @Nullable Editor getEditor() { | ||||
|     return weakEditor != null ? weakEditor.get() : null; | ||||
|   } | ||||
|  | ||||
|   public void setEditor(@Nullable Editor editor) { | ||||
|     if (editor == null) { | ||||
|       weakEditor = null; | ||||
|     } else { | ||||
|       weakEditor = new WeakReference<>(editor); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns on the ex entry field for the given editor | ||||
|    * | ||||
| @@ -132,17 +146,15 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|    * @param context  The data context | ||||
|    * @param label    The label for the ex entry (i.e. :, /, or ?) | ||||
|    * @param initText The initial text for the entry | ||||
|    * @param count    A holder for the ex entry count | ||||
|    */ | ||||
|   public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) { | ||||
|   public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText) { | ||||
|     logger.info("Activate ex entry panel"); | ||||
|     this.label.setText(label); | ||||
|     this.label.setFont(UiHelper.selectFont(label)); | ||||
|     this.count = count; | ||||
|     this.label.setFont(UiHelper.selectEditorFont(editor, label)); | ||||
|     entry.reset(); | ||||
|     entry.setEditor(editor, context); | ||||
|     entry.setText(initText); | ||||
|     entry.setFont(UiHelper.selectFont(initText)); | ||||
|     entry.setFont(UiHelper.selectEditorFont(editor, initText)); | ||||
|     entry.setType(label); | ||||
|     parent = editor.getContentComponent(); | ||||
|  | ||||
| @@ -191,7 +203,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|   public void deactivate(boolean refocusOwningEditor, boolean resetCaret) { | ||||
|     logger.info("Deactivate ex entry panel"); | ||||
|     if (!active) return; | ||||
|     active = false; | ||||
|  | ||||
|     try { | ||||
|       entry.getDocument().removeDocumentListener(fontListener); | ||||
| @@ -215,6 +226,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|         VimPlugin.getSearch().resetIncsearchHighlights(); | ||||
|       } | ||||
|  | ||||
|       isReplaceMode = false; | ||||
|       entry.deactivate(); | ||||
|     } | ||||
|     finally { | ||||
| @@ -234,6 +246,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|  | ||||
|       parent = null; | ||||
|     } | ||||
|  | ||||
|     // We have this in the end, because `entry.deactivate()` communicates with active panel during deactivation | ||||
|     active = false; | ||||
|   } | ||||
|  | ||||
|   private void reset() { | ||||
| @@ -257,8 +272,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|   private final @NotNull DocumentListener fontListener = new DocumentAdapter() { | ||||
|     @Override | ||||
|     protected void textChanged(@NotNull DocumentEvent e) { | ||||
|       String text = entry.getActualText(); | ||||
|       Font newFont = UiHelper.selectFont(text); | ||||
|       String text = entry.getText(); | ||||
|       Font newFont = UiHelper.selectEditorFont(getEditor(), text); | ||||
|       if (newFont != entry.getFont()) { | ||||
|         entry.setFont(newFont); | ||||
|       } | ||||
| @@ -276,7 +291,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|         boolean searchCommand = false; | ||||
|         LineRange searchRange = null; | ||||
|         char separator = labelText.charAt(0); | ||||
|         String searchText = entry.getActualText(); | ||||
|         String searchText = getActualText(); | ||||
|         if (labelText.equals(":")) { | ||||
|           if (searchText.isEmpty()) return; | ||||
|           final Command command = getIncsearchCommand(searchText); | ||||
| @@ -313,10 +328,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|  | ||||
|         if (labelText.equals("/") || labelText.equals("?") || searchCommand) { | ||||
|           final boolean forwards = !labelText.equals("?");  // :s, :g, :v are treated as forwards | ||||
|           final String pattern; | ||||
|           final CharPointer p = new CharPointer(searchText); | ||||
|           final CharPointer end = RegExp.skip_regexp(new CharPointer(searchText), separator, true); | ||||
|           pattern = p.substring(end.pointer() - p.pointer()); | ||||
|           int pattenEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0); | ||||
|           final String pattern = searchText.substring(0, pattenEnd); | ||||
|  | ||||
|           VimPlugin.getEditor().closeEditorSearchSession(editor); | ||||
|           final int matchOffset = | ||||
| @@ -365,13 +378,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|     return label.getText(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Gets the count given during activation | ||||
|    * | ||||
|    * @return The count | ||||
|    */ | ||||
|   public int getCount() { | ||||
|     return count; | ||||
|   @Override | ||||
|   public void toggleReplaceMode() { | ||||
|     isReplaceMode = !isReplaceMode; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -383,19 +392,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|     return active; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Gets the text entered by the user. This includes any initial text but does not include the label | ||||
|    * | ||||
|    * @return The user entered text | ||||
|    */ | ||||
|   @Override | ||||
|   public @NotNull String getText() { | ||||
|     return entry.getActualText(); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void setText(@NotNull String s) { | ||||
|     entry.setText(s); | ||||
|   public @NotNull String getVisibleText() { | ||||
|     return entry.getText(); | ||||
|   } | ||||
|  | ||||
|   public @NotNull ExTextField getEntry() { | ||||
| @@ -427,6 +426,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|  | ||||
|       // Label background is automatically picked up | ||||
|       label.setForeground(entry.getForeground()); | ||||
|  | ||||
|       // Make sure the panel is positioned correctly if we're changing font size | ||||
|       positionPanel(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -444,8 +446,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|   } | ||||
|  | ||||
|   private void setFontForElements() { | ||||
|     label.setFont(UiHelper.selectFont(label.getText())); | ||||
|     entry.setFont(UiHelper.selectFont(entry.getActualText())); | ||||
|     label.setFont(UiHelper.selectEditorFont(getEditor(), label.getText())); | ||||
|     entry.setFont(UiHelper.selectEditorFont(getEditor(), getVisibleText())); | ||||
|   } | ||||
|  | ||||
|   private void positionPanel() { | ||||
| @@ -469,7 +471,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|   } | ||||
|  | ||||
|   private boolean active; | ||||
|   private int count; | ||||
|  | ||||
|   // UI stuff | ||||
|   private @Nullable JComponent parent; | ||||
| @@ -499,6 +500,41 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | ||||
|     return (VimCommandLineCaret) entry.getCaret(); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void setText(@NotNull String string) { | ||||
|     // It's a feature of Swing that caret is moved when we set new text. However, our API is Swing independent and we do not expect this | ||||
|     int offset = getCaret().getOffset(); | ||||
|     entry.updateText(string); | ||||
|     getCaret().setOffset(Math.min(offset, getVisibleText().length())); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void clearCurrentAction() { | ||||
|     entry.clearCurrentAction(); | ||||
|   } | ||||
|  | ||||
|   @Nullable | ||||
|   @Override | ||||
|   public Integer getPromptCharacterOffset() { | ||||
|     int offset = entry.currentActionPromptCharacterOffset; | ||||
|     return offset == -1 ? null : offset; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void setPromptCharacterOffset(@Nullable Integer integer) { | ||||
|     entry.currentActionPromptCharacterOffset = integer == null ? -1 : integer; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public boolean isReplaceMode() { | ||||
|     return isReplaceMode; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public void focus() { | ||||
|     IdeFocusManager.findInstance().requestFocus(entry, true); | ||||
|   } | ||||
|  | ||||
|   public static class LafListener implements LafManagerListener { | ||||
|     @Override | ||||
|     public void lookAndFeelChanged(@NotNull LafManager source) { | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user