mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2024-11-24 22:42:53 +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 | ||
|
e62c86b99f | ||
|
82bd792da5 | ||
|
a58c9065e6 | ||
|
e8bf984b76 | ||
|
23e1a3499f | ||
|
6b4e4bacd7 | ||
|
a84c04ca08 | ||
|
e67c71e440 | ||
|
5078ff9c7a | ||
|
647510de5d | ||
|
84e11e4236 | ||
|
9538714af1 | ||
|
ffd832d990 | ||
|
8de2b8976b | ||
|
a6aa26b5d9 | ||
|
2505651c68 | ||
|
e67c7b23ff | ||
|
453cca3b0c | ||
|
6cee04a4be | ||
|
ae8b9b4773 | ||
|
e748b7b265 | ||
|
c2401ec013 | ||
|
8073d7ecd0 | ||
|
64f7859ba7 | ||
|
f1b94d7026 | ||
|
79653b6048 | ||
|
b0e6b72281 | ||
|
e6220e5e53 | ||
|
3c064845b1 | ||
|
736cb219ca | ||
|
fb30e4e387 | ||
|
74550ffa16 | ||
|
d0a0672282 | ||
|
16e92ddf60 | ||
|
4d8e68d800 | ||
|
bbebfaf32a | ||
|
7e56331e47 | ||
|
750db8e71c | ||
|
4255ef68a3 | ||
|
3313464214 | ||
|
683ba32a15 | ||
|
90a60155e5 | ||
|
b25d06ed9e | ||
|
706ae3dd91 | ||
|
9b15ed8181 | ||
|
f355bef36b | ||
|
4391e69c48 | ||
|
0710d80391 | ||
|
cf41a3a76c | ||
|
31b2cd872f | ||
|
2b6945cbb2 | ||
|
ae5f43918f | ||
|
6b6bc2752e | ||
|
4556adae3c | ||
|
1b0886041b | ||
|
16e18f3ca7 | ||
|
ee0d67fbbb | ||
|
450527f172 | ||
|
135518ee39 | ||
|
58715ecb5f | ||
|
75e26b101d | ||
|
6421a6face | ||
|
948520f90a | ||
|
0765118ce2 | ||
|
efd4c7b617 | ||
|
c5346fbece | ||
|
fe8e8ccc3e | ||
|
eae111bc2c | ||
|
6a6c1dc6b4 | ||
|
86bbb282ab | ||
|
28aa156cb7 | ||
|
a7814e69de | ||
|
1452c116cf | ||
|
23dfc4b339 | ||
|
931d4be972 | ||
|
7dceda587b | ||
|
52a969074d | ||
|
e7b87d31cf | ||
|
5eb0fae08f | ||
|
798d805a0f | ||
|
0d4ba06e57 | ||
|
4913b13a2d | ||
|
b0bab992db | ||
|
af5f4227b7 | ||
|
fa6a694ea4 | ||
|
1da7ffc052 | ||
|
c673f5818c | ||
|
ec78a87644 | ||
|
69d14ddcf5 | ||
|
f62819df00 | ||
|
39a85b6bc2 | ||
|
f76ae3e867 | ||
|
3651e5f2f0 | ||
|
89e016ef6c | ||
|
e4996f4c4d | ||
|
c44ed58142 | ||
|
0091af2a41 | ||
|
d1eea68719 | ||
|
133aff7fd8 | ||
|
efde94db7a | ||
|
6ec072b34e | ||
|
4027a21514 | ||
|
3665b1ab00 | ||
|
cf6b292f0c | ||
|
507e4173d3 | ||
|
abc3575d3e | ||
|
2c0ff587e3 | ||
|
26c87535d6 | ||
|
6ac8e672be | ||
|
04ee2dd1e7 | ||
|
3106a98aee | ||
|
73769a3472 | ||
|
085e253d77 | ||
|
b2af8f153e | ||
|
37fb41fca8 | ||
|
e2b05ab639 | ||
|
354fd8fef0 | ||
|
9b97867be1 | ||
|
06685d1721 | ||
|
ae4b88a06b | ||
|
c83ecc46ed | ||
|
c32050a208 | ||
|
4a8c7227e6 | ||
|
55e61a7094 | ||
|
60977d05b6 | ||
|
601747f720 | ||
|
0c91bc3207 | ||
|
f5cd2c173f |
6
.github/workflows/closeYoutrackOnCommit.yml
vendored
6
.github/workflows/closeYoutrackOnCommit.yml
vendored
@ -20,10 +20,10 @@ jobs:
|
|||||||
fetch-depth: 300
|
fetch-depth: 300
|
||||||
- name: Get tags
|
- name: Get tags
|
||||||
run: git fetch --tags origin
|
run: git fetch --tags origin
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
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
|
echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-close-youtrack)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Update YouTrack
|
- name: Update YouTrack
|
||||||
run: ./gradlew updateYoutrackOnCommit
|
run: ./gradlew --no-configuration-cache updateYoutrackOnCommit
|
||||||
env:
|
env:
|
||||||
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
||||||
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
|
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
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 300
|
fetch-depth: 300
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
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
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 300
|
fetch-depth: 300
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
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
|
fetch-depth: 50
|
||||||
# See end of file updateChangeslog.yml for explanation of this secret
|
# See end of file updateChangeslog.yml for explanation of this secret
|
||||||
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||||
|
|
||||||
- name: Update authors
|
- name: Update authors
|
||||||
id: update_authors
|
id: update_authors
|
||||||
run: ./gradlew updateMergedPr -PprId=${{ github.event.number }}
|
run: ./gradlew --no-configuration-cache updateMergedPr -PprId=${{ github.event.number }}
|
||||||
env:
|
env:
|
||||||
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
|
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 }}
|
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
||||||
- name: Get tags
|
- name: Get tags
|
||||||
run: git fetch --tags origin
|
run: git fetch --tags origin
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||||
@ -40,7 +40,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update authors
|
- name: Update authors
|
||||||
id: update_authors
|
id: update_authors
|
||||||
run: ./gradlew updateAuthors --stacktrace
|
run: ./gradlew --no-configuration-cache updateAuthors --stacktrace
|
||||||
env:
|
env:
|
||||||
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
||||||
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
6
.github/workflows/updateChangelog.yml
vendored
6
.github/workflows/updateChangelog.yml
vendored
@ -22,10 +22,10 @@ jobs:
|
|||||||
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
||||||
- name: Get tags
|
- name: Get tags
|
||||||
run: git fetch --tags origin
|
run: git fetch --tags origin
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
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
|
echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-changelog)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Update changelog
|
- name: Update changelog
|
||||||
run: ./gradlew updateChangelog
|
run: ./gradlew --no-configuration-cache updateChangelog
|
||||||
env:
|
env:
|
||||||
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
||||||
|
|
||||||
|
4
.github/workflows/updateFormatting.yml
vendored
4
.github/workflows/updateFormatting.yml
vendored
@ -20,10 +20,10 @@ jobs:
|
|||||||
fetch-depth: 50
|
fetch-depth: 50
|
||||||
# See end of file updateChangeslog.yml for explanation of this secret
|
# See end of file updateChangeslog.yml for explanation of this secret
|
||||||
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -10,6 +10,8 @@
|
|||||||
!/.idea/runConfigurations
|
!/.idea/runConfigurations
|
||||||
!/.idea/codeStyles
|
!/.idea/codeStyles
|
||||||
!/.idea/vcs.xml
|
!/.idea/vcs.xml
|
||||||
|
!/.idea/misc.xml
|
||||||
|
!/.idea/.name
|
||||||
|
|
||||||
**/build/
|
**/build/
|
||||||
**/out/
|
**/out/
|
||||||
@ -22,7 +24,7 @@
|
|||||||
.teamcity/*.iml
|
.teamcity/*.iml
|
||||||
|
|
||||||
# Generated by gradle task "generateGrammarSource"
|
# 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
|
vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
|
||||||
# Generated JSONs for lazy classloading
|
# Generated JSONs for lazy classloading
|
||||||
/vim-engine/src/main/resources/ksp-generated
|
/vim-engine/src/main/resources/ksp-generated
|
||||||
|
1
.idea/.name
Normal file
1
.idea/.name
Normal file
@ -0,0 +1 @@
|
|||||||
|
IdeaVim
|
22
.idea/misc.xml
Normal file
22
.idea/misc.xml
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>
|
@ -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.buildFeatures.sshAgent
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
|
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.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 ReleaseMajor : ReleasePlugin("major")
|
||||||
object ReleaseMinor : ReleasePlugin("minor")
|
object ReleaseMinor : ReleasePlugin("minor")
|
||||||
@ -158,16 +156,4 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
|
|||||||
teamcitySshKey = "IdeaVim ssh keys"
|
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)
|
[![icon][github]](https://github.com/Aisper)
|
||||||
|
|
||||||
Egor Nikolaevsky
|
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:
|
Previous contributors:
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
IdeaVim project is licensed under MIT license except the following parts of it:
|
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 [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.
|
* File [Tutor.kt](src/main/java/com/maddyhome/idea/vim/ui/Tutor.kt) is licensed under Vim License.
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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") {
|
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
|
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||||
|
@ -13,7 +13,7 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
|||||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||||
import com.intellij.vim.processors.VimscriptFunctionProcessor
|
import com.intellij.vim.processors.VimscriptFunctionProcessor
|
||||||
|
|
||||||
public class VimscriptFunctionProcessorProvider : SymbolProcessorProvider {
|
class VimscriptFunctionProcessorProvider : SymbolProcessorProvider {
|
||||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||||
return VimscriptFunctionProcessor(environment)
|
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")
|
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||||
|
|
||||||
// This is needed for jgit to connect to ssh
|
// This is needed for jgit to connect to ssh
|
||||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.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("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-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-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
|
// This comes from the changelog plugin
|
||||||
// classpath("org.jetbrains:markdown:0.3.1")
|
// classpath("org.jetbrains:markdown:0.3.1")
|
||||||
@ -63,7 +63,6 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
antlr
|
|
||||||
java
|
java
|
||||||
kotlin("jvm") version "1.9.22"
|
kotlin("jvm") version "1.9.22"
|
||||||
application
|
application
|
||||||
@ -73,26 +72,11 @@ plugins {
|
|||||||
id("org.jetbrains.changelog") version "2.2.0"
|
id("org.jetbrains.changelog") version "2.2.0"
|
||||||
|
|
||||||
id("org.jetbrains.kotlinx.kover") version "0.6.1"
|
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"
|
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
|
// Import variables from gradle.properties file
|
||||||
val javaVersion: String by project
|
val javaVersion: String by project
|
||||||
val kotlinVersion: String by project
|
val kotlinVersion: String by project
|
||||||
@ -100,8 +84,8 @@ val ideaVersion: String by project
|
|||||||
val ideaType: String by project
|
val ideaType: String by project
|
||||||
val downloadIdeaSources: String by project
|
val downloadIdeaSources: String by project
|
||||||
val instrumentPluginCode: String by project
|
val instrumentPluginCode: String by project
|
||||||
val antlrVersion: String by project
|
|
||||||
val remoteRobotVersion: String by project
|
val remoteRobotVersion: String by project
|
||||||
|
val splitModeVersion: String by project
|
||||||
|
|
||||||
val publishChannels: String by project
|
val publishChannels: String by project
|
||||||
val publishToken: String by project
|
val publishToken: String by project
|
||||||
@ -109,6 +93,8 @@ val publishToken: String by project
|
|||||||
val slackUrl: String by project
|
val slackUrl: String by project
|
||||||
val youtrackToken: String by project
|
val youtrackToken: String by project
|
||||||
|
|
||||||
|
val releaseType: String? by project
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
|
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
|
||||||
@ -117,12 +103,10 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api(project(":vim-engine"))
|
api(project(":vim-engine"))
|
||||||
ksp(project(":annotation-processors"))
|
ksp(project(":annotation-processors"))
|
||||||
implementation(project(":annotation-processors"))
|
compileOnly(project(":annotation-processors"))
|
||||||
|
|
||||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||||
runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
|
|
||||||
antlr("org.antlr:antlr4:$antlrVersion")
|
|
||||||
|
|
||||||
// --------- Test dependencies ----------
|
// --------- Test dependencies ----------
|
||||||
|
|
||||||
@ -143,12 +127,12 @@ dependencies {
|
|||||||
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
|
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
|
||||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
|
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-api:5.10.3")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
|
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
|
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
|
||||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
|
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
|
||||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
|
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
|
||||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
|
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
@ -159,6 +143,8 @@ configurations {
|
|||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
test {
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
// Set teamcity env variable locally to run additional tests for leaks.
|
// 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,
|
// 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
|
// so we can turn it on for local development
|
||||||
@ -171,6 +157,9 @@ tasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileJava {
|
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
|
sourceCompatibility = javaVersion
|
||||||
targetCompatibility = javaVersion
|
targetCompatibility = javaVersion
|
||||||
|
|
||||||
@ -196,6 +185,10 @@ tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runIde {
|
||||||
|
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
||||||
|
}
|
||||||
|
|
||||||
downloadRobotServerPlugin {
|
downloadRobotServerPlugin {
|
||||||
version.set(remoteRobotVersion)
|
version.set(remoteRobotVersion)
|
||||||
}
|
}
|
||||||
@ -206,11 +199,33 @@ tasks {
|
|||||||
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
|
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
|
||||||
systemProperty("jb.consents.confirmation.enabled", "false")
|
systemProperty("jb.consents.confirmation.enabled", "false")
|
||||||
systemProperty("ide.show.tips.on.startup.default.value", "false")
|
systemProperty("ide.show.tips.on.startup.default.value", "false")
|
||||||
|
|
||||||
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
runIde {
|
// Add plugin open API sources to the plugin ZIP
|
||||||
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
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 {
|
kotlin {
|
||||||
explicitApi()
|
|
||||||
jvmToolchain {
|
jvmToolchain {
|
||||||
languageVersion.set(JavaLanguageVersion.of(javaVersion))
|
languageVersion.set(JavaLanguageVersion.of(javaVersion))
|
||||||
}
|
}
|
||||||
@ -266,48 +280,6 @@ tasks {
|
|||||||
teamCityOutputFormat.set(true)
|
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 {
|
patchPluginXml {
|
||||||
// Don't forget to update plugin.xml
|
// Don't forget to update plugin.xml
|
||||||
sinceBuild.set("241.15989.150")
|
sinceBuild.set("241.15989.150")
|
||||||
@ -318,14 +290,21 @@ tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Tests
|
ksp {
|
||||||
|
arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
|
||||||
tasks {
|
arg("vimscript_functions_file", "intellij_vimscript_functions.json")
|
||||||
test {
|
arg("ex_commands_file", "intellij_ex_commands.json")
|
||||||
useJUnitPlatform()
|
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
|
||||||
|
|
||||||
changelog {
|
changelog {
|
||||||
@ -451,6 +430,8 @@ val fixVersionsElementType = "VersionBundleElement"
|
|||||||
tasks.register("releaseActions") {
|
tasks.register("releaseActions") {
|
||||||
group = "other"
|
group = "other"
|
||||||
doLast {
|
doLast {
|
||||||
|
if (releaseType == "patch") return@doLast
|
||||||
|
|
||||||
val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D%20and%20tag:%20%7BIdeaVim%20Released%20In%20EAP%7D%20")
|
val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D%20and%20tag:%20%7BIdeaVim%20Released%20In%20EAP%7D%20")
|
||||||
if (tickets.isNotEmpty()) {
|
if (tickets.isNotEmpty()) {
|
||||||
println("Updating statuses for tickets: $tickets")
|
println("Updating statuses for tickets: $tickets")
|
||||||
|
@ -130,7 +130,25 @@ Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-
|
|||||||
|
|
||||||
### Instructions
|
### 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>
|
</details>
|
||||||
|
|
||||||
|
@ -40,30 +40,33 @@ Plug 'nerdtree'
|
|||||||
- `:NERDTreeFind`
|
- `:NERDTreeFind`
|
||||||
- `:NERDTreeRefreshRoot`
|
- `:NERDTreeRefreshRoot`
|
||||||
|
|
||||||
| Key | Description | Map Setting |
|
| Key | Description | Map Setting |
|
||||||
|---------|---------------------------------------------------------|--------------------------------|
|
|---------|--------------------------------------------------------|--------------------------------|
|
||||||
| `o` | Open files, directories and bookmarks | `g:NERDTreeMapActivateNode` |
|
| `o` | Open files, directories and bookmarks | `g:NERDTreeMapActivateNode` |
|
||||||
| `go` | Open selected file, but leave cursor in the NERDTree | `g:NERDTreeMapPreview` |
|
| `go` | Open selected file, but leave cursor in the NERDTree | `g:NERDTreeMapPreview` |
|
||||||
| `t` | Open selected node/bookmark in a new tab | `g:NERDTreeMapOpenInTab` |
|
| `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` |
|
| `T` | Same as 't' but keep the focus on the current tab | `g:NERDTreeMapOpenInTabSilent` |
|
||||||
| `i` | Open selected file in a split window | `g:NERDTreeMapOpenSplit` |
|
| `i` | Open selected file in a split window | `g:NERDTreeMapOpenSplit` |
|
||||||
| `gi` | Same as i, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewSplit` |
|
| `gi` | Same as i, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewSplit` |
|
||||||
| `s` | Open selected file in a new vsplit | `g:NERDTreeMapOpenVSplit` |
|
| `s` | Open selected file in a new vsplit | `g:NERDTreeMapOpenVSplit` |
|
||||||
| `gs` | Same as s, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewVSplit` |
|
| `gs` | Same as s, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewVSplit` |
|
||||||
| `O` | Recursively open the selected directory | `g:NERDTreeMapOpenRecursively` |
|
| `O` | Recursively open the selected directory | `g:NERDTreeMapOpenRecursively` |
|
||||||
| `x` | Close the current nodes parent | `g:NERDTreeMapCloseDir` |
|
| `x` | Close the current nodes parent | `g:NERDTreeMapCloseDir` |
|
||||||
| `X` | Recursively close all children of the current node | `g:NERDTreeMapCloseChildren` |
|
| `X` | Recursively close all children of the current node | `g:NERDTreeMapCloseChildren` |
|
||||||
| `P` | Jump to the root node | `g:NERDTreeMapJumpRoot` |
|
| `P` | Jump to the root node | `g:NERDTreeMapJumpRoot` |
|
||||||
| `p` | Jump to current nodes parent | `g:NERDTreeMapJumpParent` |
|
| `p` | Jump to current nodes parent | `g:NERDTreeMapJumpParent` |
|
||||||
| `K` | Jump up inside directories at the current tree depth | `g:NERDTreeMapJumpFirstChild` |
|
| `K` | Jump up inside directories at the current tree depth | `g:NERDTreeMapJumpFirstChild` |
|
||||||
| `J` | Jump down inside directories at the current tree depth | `g:NERDTreeMapJumpLastChild` |
|
| `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-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` |
|
| `<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 directory | `g:NERDTreeMapRefresh` |
|
||||||
| `R` | Recursively refresh the current root | `g:NERDTreeMapRefreshRoot` |
|
| `R` | Recursively refresh the current root | `g:NERDTreeMapRefreshRoot` |
|
||||||
| `m` | Display the NERDTree menu | `g:NERDTreeMapMenu` |
|
| `m` | Display the NERDTree menu | `g:NERDTreeMapMenu` |
|
||||||
| `q` | Close the NERDTree window | `g:NERDTreeMapQuit` |
|
| `q` | Close the NERDTree window | `g:NERDTreeMapQuit` |
|
||||||
| `A` | Zoom (maximize/minimize) the NERDTree window | `g:NERDTreeMapToggleZoom` |
|
| `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
|
### Troubleshooting
|
||||||
|
|
||||||
|
@ -8,18 +8,30 @@
|
|||||||
|
|
||||||
# suppress inspection "UnusedProperty" for whole file
|
# 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
|
ideaVersion=2024.1.1
|
||||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||||
ideaType=IC
|
ideaType=IC
|
||||||
downloadIdeaSources=true
|
downloadIdeaSources=true
|
||||||
instrumentPluginCode=true
|
instrumentPluginCode=true
|
||||||
version=chylex-35
|
version=chylex-36
|
||||||
javaVersion=17
|
javaVersion=17
|
||||||
remoteRobotVersion=0.11.22
|
remoteRobotVersion=0.11.22
|
||||||
antlrVersion=4.10.1
|
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
|
# Please don't forget to update kotlin version in buildscript section
|
||||||
# Also update kotlinxSerializationVersion version
|
# Also update kotlinxSerializationVersion version
|
||||||
@ -36,6 +48,7 @@ youtrackToken=
|
|||||||
|
|
||||||
# Gradle settings
|
# Gradle settings
|
||||||
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
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
|
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary
|
||||||
kotlin.stdlib.default.dependency=false
|
kotlin.stdlib.default.dependency=false
|
||||||
|
@ -22,15 +22,15 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
|
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-cio:2.3.10")
|
||||||
implementation("io.ktor:ktor-client-content-negotiation: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-serialization-kotlinx-json:2.3.12")
|
||||||
implementation("io.ktor:ktor-client-auth:2.3.11")
|
implementation("io.ktor:ktor-client-auth:2.3.12")
|
||||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||||
|
|
||||||
// This is needed for jgit to connect to ssh
|
// This is needed for jgit to connect to ssh
|
||||||
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.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")
|
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 java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
public object RegisterActions {
|
object RegisterActions {
|
||||||
/**
|
/**
|
||||||
* Register all the key/action mappings for the plugin.
|
* Register all the key/action mappings for the plugin.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun registerActions() {
|
fun registerActions() {
|
||||||
registerVimCommandActions()
|
registerVimCommandActions()
|
||||||
registerShortcutsWithoutActions()
|
registerShortcutsWithoutActions()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun findAction(id: String): EditorActionHandlerBase? {
|
fun findAction(id: String): EditorActionHandlerBase? {
|
||||||
val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
|
val commandBean = IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id }
|
||||||
?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
|
?: EngineCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
|
||||||
return commandBean.instance
|
return commandBean.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun findActionOrDie(id: String): EditorActionHandlerBase {
|
fun findActionOrDie(id: String): EditorActionHandlerBase {
|
||||||
return findAction(id) ?: throw RuntimeException("Action $id is not registered")
|
return findAction(id) ?: throw RuntimeException("Action $id is not registered")
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun unregisterActions() {
|
fun unregisterActions() {
|
||||||
val keyGroup = VimPlugin.getKeyIfCreated()
|
val keyGroup = VimPlugin.getKeyIfCreated()
|
||||||
keyGroup?.unregisterCommandActions()
|
keyGroup?.unregisterCommandActions()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerVimCommandActions() {
|
private fun registerVimCommandActions() {
|
||||||
val parser = VimPlugin.getKey()
|
val parser = VimPlugin.getKey()
|
||||||
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
|
||||||
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||||
|
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerShortcutsWithoutActions() {
|
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.helper.MacKeyRepeat;
|
||||||
import com.maddyhome.idea.vim.listener.VimListenerManager;
|
import com.maddyhome.idea.vim.listener.VimListenerManager;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimInjector;
|
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.StatusBarIconFactory;
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
|
|
||||||
import com.maddyhome.idea.vim.vimscript.services.VariableService;
|
import com.maddyhome.idea.vim.vimscript.services.VariableService;
|
||||||
import com.maddyhome.idea.vim.yank.YankGroupBase;
|
import com.maddyhome.idea.vim.yank.YankGroupBase;
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
@ -46,6 +46,7 @@ import org.jetbrains.annotations.Nls;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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.EditorGroup.EDITOR_STORE_ELEMENT;
|
||||||
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
|
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
|
||||||
import static com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc;
|
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();
|
return (FileGroup)VimInjectorKt.getInjector().getFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull SearchGroup getSearch() {
|
public static @NotNull IjVimSearchGroup getSearch() {
|
||||||
return ApplicationManager.getApplication().getService(SearchGroup.class);
|
return ApplicationManager.getApplication().getService(IjVimSearchGroup.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @Nullable SearchGroup getSearchIfCreated() {
|
public static @Nullable IjVimSearchGroup getSearchIfCreated() {
|
||||||
return ApplicationManager.getApplication().getServiceIfCreated(SearchGroup.class);
|
return ApplicationManager.getApplication().getServiceIfCreated(IjVimSearchGroup.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull ProcessGroup getProcess() {
|
public static @NotNull ProcessGroup getProcess() {
|
||||||
@ -283,11 +284,11 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
|
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||||
try {
|
try {
|
||||||
VimInjectorKt.injector.getOptionGroup().startInitVimRc();
|
injector.getOptionGroup().startInitVimRc();
|
||||||
executeIdeaVimRc(editor);
|
executeIdeaVimRc(editor);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
VimInjectorKt.injector.getOptionGroup().endInitVimRc();
|
injector.getOptionGroup().endInitVimRc();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,14 +346,14 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void turnOffPlugin(boolean unsubscribe) {
|
private void turnOffPlugin(boolean unsubscribe) {
|
||||||
SearchGroup searchGroup = getSearchIfCreated();
|
IjVimSearchGroup searchGroup = getSearchIfCreated();
|
||||||
if (searchGroup != null) {
|
if (searchGroup != null) {
|
||||||
searchGroup.turnOff();
|
searchGroup.turnOff();
|
||||||
}
|
}
|
||||||
if (unsubscribe) {
|
if (unsubscribe) {
|
||||||
VimListenerManager.INSTANCE.turnOff();
|
VimListenerManager.INSTANCE.turnOff();
|
||||||
}
|
}
|
||||||
ExEntryPanel.fullReset();
|
injector.getCommandLine().fullReset();
|
||||||
|
|
||||||
// Unregister vim actions in command mode
|
// Unregister vim actions in command mode
|
||||||
RegisterActions.unregisterActions();
|
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.components.service
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.project.Project
|
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)
|
@Service(Service.Level.PROJECT)
|
||||||
internal class VimProjectService(val project: Project) : Disposable {
|
internal class VimProjectService(val project: Project) : Disposable {
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
// Not sure if this is a best solution
|
// Not sure if this is a best solution
|
||||||
EditorHolderService.getInstance().editor = null
|
ExEntryPanel.getInstance().editor = null
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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
|
* 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
|
* 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 handler = KeyHandler.getInstance()
|
||||||
private val traceTime = injector.globalOptions().ideatracetime
|
private val traceTime = injector.globalOptions().ideatracetime
|
||||||
|
|
||||||
|
@ -8,6 +8,6 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.action
|
package com.maddyhome.idea.vim.action
|
||||||
|
|
||||||
public object IntellijCommandProvider : CommandProvider {
|
object IntellijCommandProvider : CommandProvider {
|
||||||
override val commandListFileName: String = "intellij_commands.json"
|
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.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.group.EditorHolderService
|
|
||||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||||
import com.maddyhome.idea.vim.group.IjOptions
|
import com.maddyhome.idea.vim.group.IjOptions
|
||||||
import com.maddyhome.idea.vim.handler.enableOctopus
|
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.listener.AppCodeTemplates.appCodeTemplateCaptured
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExTextField
|
import com.maddyhome.idea.vim.ui.ex.ExTextField
|
||||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
import java.awt.event.InputEvent
|
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
|
* 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
|
* 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
|
private val traceTime: Boolean
|
||||||
get() {
|
get() {
|
||||||
// Make sure the injector is initialized
|
// Make sure the injector is initialized
|
||||||
@ -257,7 +257,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
|||||||
private fun getEditor(e: AnActionEvent): Editor? {
|
private fun getEditor(e: AnActionEvent): Editor? {
|
||||||
return e.getData(PlatformDataKeys.EDITOR)
|
return e.getData(PlatformDataKeys.EDITOR)
|
||||||
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
|
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
|
||||||
EditorHolderService.getInstance().editor
|
ExEntryPanel.getInstance().editor
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
|||||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
|
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
|
||||||
public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
|
class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.DELETE
|
override val type: Command.Type = Command.Type.DELETE
|
||||||
override fun runAsMulticaret(
|
override fun runAsMulticaret(
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
|
@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
|||||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
|
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
|
||||||
public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
|
class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.DELETE
|
override val type: Command.Type = Command.Type.DELETE
|
||||||
|
|
||||||
override fun execute(
|
override fun execute(
|
||||||
|
@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ijOptions
|
|||||||
* @author vlan
|
* @author vlan
|
||||||
*/
|
*/
|
||||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL])
|
@CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL])
|
||||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
|
class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.DELETE
|
override val type: Command.Type = Command.Type.DELETE
|
||||||
|
|
||||||
override fun executeForAllCarets(
|
override fun executeForAllCarets(
|
||||||
|
@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ijOptions
|
|||||||
* @author vlan
|
* @author vlan
|
||||||
*/
|
*/
|
||||||
@CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL])
|
@CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL])
|
||||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
|
class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.DELETE
|
override val type: Command.Type = Command.Type.DELETE
|
||||||
|
|
||||||
override fun executeForAllCarets(
|
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) {
|
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN) {
|
||||||
override val type: Command.Type = Command.Type.MOTION
|
override val type: Command.Type = Command.Type.MOTION
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
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])
|
@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) {
|
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP) {
|
||||||
override val type: Command.Type = Command.Type.MOTION
|
override val type: Command.Type = Command.Type.MOTION
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
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])
|
@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.intellij.openapi.editor.LogicalPosition
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
|
||||||
public class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) {
|
class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) {
|
||||||
public fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column
|
fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column
|
||||||
|
|
||||||
public companion object {
|
companion object {
|
||||||
public fun fromOffset(editor: Editor, offset: Int): CharacterPosition {
|
fun fromOffset(editor: Editor, offset: Int): CharacterPosition {
|
||||||
// logical position "expands" tabs
|
// logical position "expands" tabs
|
||||||
val logicalPosition = editor.offsetToLogicalPosition(offset)
|
val logicalPosition = editor.offsetToLogicalPosition(offset)
|
||||||
val lineStartOffset = editor.vim.getLineStartOffset(logicalPosition.line)
|
val lineStartOffset = editor.vim.getLineStartOffset(logicalPosition.line)
|
||||||
return CharacterPosition(logicalPosition.line, offset - lineStartOffset)
|
return CharacterPosition(logicalPosition.line, offset - lineStartOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun atCaret(editor: Editor): CharacterPosition {
|
fun atCaret(editor: Editor): CharacterPosition {
|
||||||
return fromOffset(editor, editor.caretModel.offset)
|
return fromOffset(editor, editor.caretModel.offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import com.maddyhome.idea.vim.helper.vimExOutput
|
|||||||
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
||||||
|
|
||||||
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
|
// 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
|
private var isActiveInTestMode = false
|
||||||
|
|
||||||
override val isActive: Boolean
|
override val isActive: Boolean
|
||||||
@ -28,14 +28,18 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V
|
|||||||
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
|
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
ExOutputPanel.getInstance(myEditor).text
|
ExOutputPanel.getInstance(myEditor).text
|
||||||
} else {
|
} else {
|
||||||
field
|
// ExOutputPanel always returns a non-null string
|
||||||
|
field ?: ""
|
||||||
}
|
}
|
||||||
set(value) {
|
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) {
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
ExOutputPanel.getInstance(myEditor).setText(value ?: "")
|
ExOutputPanel.getInstance(myEditor).setText(newValue ?: "")
|
||||||
} else {
|
} else {
|
||||||
field = value
|
field = newValue
|
||||||
isActiveInTestMode = !value.isNullOrEmpty()
|
isActiveInTestMode = !newValue.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,9 +60,9 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun getInstance(editor: Editor): ExOutputModel {
|
fun getInstance(editor: Editor): ExOutputModel {
|
||||||
var model = editor.vimExOutput
|
var model = editor.vimExOutput
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
model = ExOutputModel(editor)
|
model = ExOutputModel(editor)
|
||||||
@ -66,5 +70,8 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V
|
|||||||
}
|
}
|
||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun tryGetInstance(editor: Editor) = editor.vimExOutput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ package com.maddyhome.idea.vim.extension
|
|||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.components.service
|
|
||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
@ -51,13 +50,13 @@ import javax.swing.KeyStroke
|
|||||||
*
|
*
|
||||||
* @author vlan
|
* @author vlan
|
||||||
*/
|
*/
|
||||||
public object VimExtensionFacade {
|
object VimExtensionFacade {
|
||||||
|
|
||||||
private val LOG = logger<VimExtensionFacade>()
|
private val LOG = logger<VimExtensionFacade>()
|
||||||
|
|
||||||
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun putExtensionHandlerMapping(
|
fun putExtensionHandlerMapping(
|
||||||
modes: Set<MappingMode>,
|
modes: Set<MappingMode>,
|
||||||
fromKeys: List<KeyStroke>,
|
fromKeys: List<KeyStroke>,
|
||||||
pluginOwner: MappingOwner,
|
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
|
@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>,
|
modes: Set<MappingMode>,
|
||||||
fromKeys: List<KeyStroke>,
|
fromKeys: List<KeyStroke>,
|
||||||
pluginOwner: MappingOwner,
|
pluginOwner: MappingOwner,
|
||||||
@ -86,7 +87,7 @@ public object VimExtensionFacade {
|
|||||||
|
|
||||||
/** The 'map' command for mapping keys to other keys. */
|
/** The 'map' command for mapping keys to other keys. */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun putKeyMapping(
|
fun putKeyMapping(
|
||||||
modes: Set<MappingMode>,
|
modes: Set<MappingMode>,
|
||||||
fromKeys: List<KeyStroke>,
|
fromKeys: List<KeyStroke>,
|
||||||
pluginOwner: MappingOwner,
|
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 */
|
/** The 'map' command for mapping keys to other keys if there is no other mapping to these keys */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun putKeyMappingIfMissing(
|
fun putKeyMappingIfMissing(
|
||||||
modes: Set<MappingMode>,
|
modes: Set<MappingMode>,
|
||||||
fromKeys: List<KeyStroke>,
|
fromKeys: List<KeyStroke>,
|
||||||
pluginOwner: MappingOwner,
|
pluginOwner: MappingOwner,
|
||||||
@ -112,7 +113,7 @@ public object VimExtensionFacade {
|
|||||||
/**
|
/**
|
||||||
* Equivalent to calling 'command' to set up a user-defined command or alias
|
* Equivalent to calling 'command' to set up a user-defined command or alias
|
||||||
*/
|
*/
|
||||||
public fun addCommand(
|
fun addCommand(
|
||||||
name: String,
|
name: String,
|
||||||
handler: CommandAliasHandler,
|
handler: CommandAliasHandler,
|
||||||
) {
|
) {
|
||||||
@ -123,7 +124,7 @@ public object VimExtensionFacade {
|
|||||||
* Equivalent to calling 'command' to set up a user-defined command or alias
|
* Equivalent to calling 'command' to set up a user-defined command or alias
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun addCommand(
|
fun addCommand(
|
||||||
name: String,
|
name: String,
|
||||||
minimumNumberOfArguments: Int,
|
minimumNumberOfArguments: Int,
|
||||||
maximumNumberOfArguments: Int,
|
maximumNumberOfArguments: Int,
|
||||||
@ -141,7 +142,7 @@ public object VimExtensionFacade {
|
|||||||
* leaves the editor in the insert mode if it's been activated.
|
* leaves the editor in the insert mode if it's been activated.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
|
fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
|
||||||
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim)
|
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim)
|
||||||
val keyHandler = KeyHandler.getInstance()
|
val keyHandler = KeyHandler.getInstance()
|
||||||
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
|
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()'. */
|
/** Returns a single key stroke from the user input similar to 'getchar()'. */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun inputKeyStroke(editor: Editor): KeyStroke {
|
fun inputKeyStroke(editor: Editor): KeyStroke {
|
||||||
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
|
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
|
||||||
val input = Extension.consumeKeystroke()
|
val input = Extension.consumeKeystroke()
|
||||||
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
|
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()'. */
|
/** Returns a string typed in the input box similar to 'input()'. */
|
||||||
@JvmStatic
|
@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) ?: ""
|
return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the current contents of the given register similar to 'getreg()'. */
|
/** Get the current contents of the given register similar to 'getreg()'. */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun getRegister(register: Char): List<KeyStroke>? {
|
fun getRegister(register: Char): List<KeyStroke>? {
|
||||||
val reg = VimPlugin.getRegister().getRegister(register) ?: return null
|
val reg = VimPlugin.getRegister().getRegister(register) ?: return null
|
||||||
return reg.keys
|
return reg.keys
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
|
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
|
||||||
val reg = caret.registerStorage.getRegister(register) ?: return null
|
val reg = injector.registerGroup.getRegister(register) ?: return null
|
||||||
return reg.keys
|
return reg.keys
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the current contents of the given register */
|
/** Set the current contents of the given register */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun setRegister(register: Char, keys: List<KeyStroke?>?) {
|
fun setRegister(register: Char, keys: List<KeyStroke?>?) {
|
||||||
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList())
|
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the current contents of the given register */
|
/** Set the current contents of the given register */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
|
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
|
||||||
caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
|
injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the current contents of the given register */
|
/** Set the current contents of the given register */
|
||||||
@JvmStatic
|
@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)
|
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun exportScriptFunction(
|
fun exportScriptFunction(
|
||||||
scope: Scope?,
|
scope: Scope?,
|
||||||
name: String,
|
name: String,
|
||||||
args: List<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()) {
|
exportScriptFunction(null, name, listOf("type"), emptyList(), false, noneOfEnum()) {
|
||||||
editor, context, args ->
|
editor, context, args ->
|
||||||
|
|
||||||
@ -274,6 +275,6 @@ public fun VimExtensionFacade.exportOperatorFunction(name: String, function: Ope
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun interface ScriptFunction {
|
fun interface ScriptFunction {
|
||||||
public fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
|
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
|
* COMPATIBILITY-LAYER: Created a class, renamed original class
|
||||||
* Please see: https://jb.gg/zo8n0r
|
* Please see: https://jb.gg/zo8n0r
|
||||||
*/
|
*/
|
||||||
public interface VimExtensionHandler : ExtensionHandler {
|
interface VimExtensionHandler : ExtensionHandler {
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
execute(editor.ij, context.ij)
|
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;
|
package com.maddyhome.idea.vim.extension.argtextobj;
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Document;
|
import com.intellij.openapi.editor.Document;
|
||||||
|
import com.maddyhome.idea.vim.KeyHandler;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.api.*;
|
import com.maddyhome.idea.vim.api.*;
|
||||||
import com.maddyhome.idea.vim.command.*;
|
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.listener.VimListenerSuppressor;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||||
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.state.mode.Mode;
|
||||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
|
||||||
import org.jetbrains.annotations.Nls;
|
import org.jetbrains.annotations.Nls;
|
||||||
@ -244,19 +245,18 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||||
|
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||||
IjVimEditor vimEditor = (IjVimEditor) editor;
|
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||||
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
|
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||||
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
|
|
||||||
|
|
||||||
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
||||||
//noinspection DuplicatedCode
|
//noinspection DuplicatedCode
|
||||||
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
|
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||||
editor.nativeCarets().forEach((VimCaret caret) -> {
|
editor.nativeCarets().forEach((VimCaret caret) -> {
|
||||||
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
|
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
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);
|
com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||||
} else {
|
} else {
|
||||||
InlayHelperKt.moveToInlayAwareOffset(((IjVimCaret)caret).getCaret(), range.getStartOffset());
|
InlayHelperKt.moveToInlayAwareOffset(((IjVimCaret)caret).getCaret(), range.getStartOffset());
|
||||||
@ -265,7 +265,7 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} 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))));
|
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.PsiFile
|
||||||
import com.intellij.psi.PsiWhiteSpace
|
import com.intellij.psi.PsiWhiteSpace
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
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.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
@ -183,10 +184,10 @@ internal class CommentaryExtension : VimExtension {
|
|||||||
override val isRepeatable = true
|
override val isRepeatable = true
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
val commandState = editor.vimStateMachine
|
|
||||||
|
|
||||||
val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
|
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.PsiComment
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
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.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.PsiHelper
|
import com.maddyhome.idea.vim.helper.PsiHelper
|
||||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
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.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
@ -91,22 +91,23 @@ internal class Matchit : VimExtension {
|
|||||||
private class MatchitHandler(private val reverse: Boolean) : ExtensionHandler {
|
private class MatchitHandler(private val reverse: Boolean) : ExtensionHandler {
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
val commandState = editor.vimStateMachine
|
val keyHandler = KeyHandler.getInstance()
|
||||||
val count = commandState.commandBuilder.count
|
val keyState = keyHandler.keyHandlerState
|
||||||
|
val count = keyState.commandBuilder.count
|
||||||
|
|
||||||
// Reset the command count so it doesn't transfer onto subsequent commands.
|
// 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
|
// 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.
|
// 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) {
|
if (isInOpPending) {
|
||||||
val matchitAction = MatchitAction()
|
val matchitAction = MatchitAction()
|
||||||
matchitAction.reverse = reverse
|
matchitAction.reverse = reverse
|
||||||
matchitAction.isInOpPending = true
|
matchitAction.isInOpPending = true
|
||||||
|
|
||||||
commandState.commandBuilder.completeCommandPart(
|
keyState.commandBuilder.completeCommandPart(
|
||||||
Argument(
|
Argument(
|
||||||
Command(
|
Command(
|
||||||
count,
|
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.extension.VimExtensionFacade.putKeyMappingIfMissing
|
||||||
import com.maddyhome.idea.vim.group.visual.vimSetSelection
|
import com.maddyhome.idea.vim.group.visual.vimSetSelection
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
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.SearchOptions
|
||||||
import com.maddyhome.idea.vim.helper.endOffsetInclusive
|
import com.maddyhome.idea.vim.helper.endOffsetInclusive
|
||||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
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.inVisualMode
|
||||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
||||||
import com.maddyhome.idea.vim.helper.userData
|
import com.maddyhome.idea.vim.helper.userData
|
||||||
@ -235,7 +235,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
|
|||||||
val text = if (editor.inVisualMode) {
|
val text = if (editor.inVisualMode) {
|
||||||
primaryCaret.selectedText ?: return
|
primaryCaret.selectedText ?: return
|
||||||
} else {
|
} else {
|
||||||
val range = SearchHelper.findWordUnderCursor(editor, primaryCaret) ?: return
|
val range = findWordUnderCursor(editor, primaryCaret) ?: return
|
||||||
if (range.startOffset > primaryCaret.offset) return
|
if (range.startOffset > primaryCaret.offset) return
|
||||||
IjVimEditor(editor).getText(range)
|
IjVimEditor(editor).getText(range)
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? {
|
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
|
if (range.startOffset > caret.offset) return null
|
||||||
|
|
||||||
enterVisualMode(editor.vim)
|
enterVisualMode(editor.vim)
|
||||||
@ -327,6 +327,6 @@ internal class VimMultipleCursorsExtension : VimExtension {
|
|||||||
|
|
||||||
private fun makePattern(text: String, whole: Boolean): String {
|
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
|
// 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"),
|
NerdAction.ToIj("SynchronizeCurrentFile"),
|
||||||
)
|
)
|
||||||
registerCommand("NERDTreeMapToggleHidden", "I", NerdAction.ToIj("ProjectView.ShowExcludedFiles"))
|
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("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
|
||||||
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
|
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
|
||||||
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
|
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.actionSystem.DataContext
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
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.extension.exportOperatorFunction
|
||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
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.key.OperatorFunction
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
@ -144,7 +144,7 @@ internal class ReplaceWithRegister : VimExtension {
|
|||||||
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
|
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
|
||||||
val registerGroup = injector.registerGroup
|
val registerGroup = injector.registerGroup
|
||||||
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
|
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 usedType = savedRegister.type
|
||||||
var usedText = savedRegister.text
|
var usedText = savedRegister.text
|
||||||
@ -166,13 +166,14 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
|
|||||||
putToLine = -1,
|
putToLine = -1,
|
||||||
)
|
)
|
||||||
val vimEditor = editor.vim
|
val vimEditor = editor.vim
|
||||||
|
val keyHandler = KeyHandler.getInstance()
|
||||||
ClipboardOptionHelper.IdeaputDisabler().use {
|
ClipboardOptionHelper.IdeaputDisabler().use {
|
||||||
VimPlugin.getPut().putText(
|
VimPlugin.getPut().putText(
|
||||||
vimEditor,
|
vimEditor,
|
||||||
context.vim,
|
context.vim,
|
||||||
putData,
|
putData,
|
||||||
operatorArguments = OperatorArguments(
|
operatorArguments = OperatorArguments(
|
||||||
editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,
|
keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState),
|
||||||
0,
|
0,
|
||||||
editor.vim.mode,
|
editor.vim.mode,
|
||||||
),
|
),
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
package com.maddyhome.idea.vim.extension.textobjentire;
|
package com.maddyhome.idea.vim.extension.textobjentire;
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
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.ExecutionContext;
|
||||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
|
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
|
||||||
import com.maddyhome.idea.vim.api.VimEditor;
|
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.listener.VimListenerSuppressor;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||||
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.state.mode.Mode;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -133,17 +134,18 @@ public class VimTextObjEntireExtension implements VimExtension {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||||
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(editor);
|
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||||
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
|
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||||
|
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||||
|
|
||||||
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
|
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
|
||||||
//noinspection DuplicatedCode
|
//noinspection DuplicatedCode
|
||||||
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
|
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||||
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||||
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
|
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
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);
|
com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(new IjVimCaret(caret), range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||||
} else {
|
} else {
|
||||||
InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
|
InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
|
||||||
@ -153,7 +155,7 @@ public class VimTextObjEntireExtension implements VimExtension {
|
|||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||||
textObjectHandler, Command.Type.MOTION,
|
textObjectHandler, Command.Type.MOTION,
|
||||||
EnumSet.noneOf(CommandFlags.class))));
|
EnumSet.noneOf(CommandFlags.class))));
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
package com.maddyhome.idea.vim.extension.textobjindent;
|
package com.maddyhome.idea.vim.extension.textobjindent;
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
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.ExecutionContext;
|
||||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
|
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
|
||||||
import com.maddyhome.idea.vim.api.VimEditor;
|
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.listener.VimListenerSuppressor;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||||
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.state.mode.Mode;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -263,17 +264,18 @@ public class VimIndentObject implements VimExtension {
|
|||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||||
IjVimEditor vimEditor = (IjVimEditor)editor;
|
IjVimEditor vimEditor = (IjVimEditor)editor;
|
||||||
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
|
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||||
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
|
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||||
|
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||||
|
|
||||||
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
|
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) -> {
|
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||||
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
|
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
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);
|
EngineVisualGroupKt.vimSetSelection(new IjVimCaret(caret), range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||||
} else {
|
} else {
|
||||||
InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
|
InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
|
||||||
@ -283,7 +285,7 @@ public class VimIndentObject implements VimExtension {
|
|||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||||
textObjectHandler, Command.Type.MOTION,
|
textObjectHandler, Command.Type.MOTION,
|
||||||
EnumSet.noneOf(CommandFlags.class))));
|
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.CharacterHelper.charType
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.NumberType
|
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.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.inInsertMode
|
||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
|
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
|
||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||||
@ -83,7 +84,7 @@ import kotlin.math.max
|
|||||||
/**
|
/**
|
||||||
* Provides all the insert/replace related functionality
|
* Provides all the insert/replace related functionality
|
||||||
*/
|
*/
|
||||||
public class ChangeGroup : VimChangeGroupBase() {
|
class ChangeGroup : VimChangeGroupBase() {
|
||||||
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
|
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
|
||||||
private val listener: EditorMouseListener = object : EditorMouseListener {
|
private val listener: EditorMouseListener = object : EditorMouseListener {
|
||||||
override fun mouseClicked(event: EditorMouseEvent) {
|
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)
|
EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +103,9 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
val editor = (vimEditor as IjVimEditor).editor
|
val editor = (vimEditor as IjVimEditor).editor
|
||||||
val ijContext = context.ij
|
val ijContext = context.ij
|
||||||
val doc = vimEditor.editor.document
|
val doc = vimEditor.editor.document
|
||||||
|
val undo = injector.undo
|
||||||
|
val nanoTime = System.nanoTime()
|
||||||
|
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
|
||||||
CommandProcessor.getInstance().executeCommand(
|
CommandProcessor.getInstance().executeCommand(
|
||||||
editor.project, {
|
editor.project, {
|
||||||
ApplicationManager.getApplication()
|
ApplicationManager.getApplication()
|
||||||
@ -650,7 +654,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
val alpha = nf.contains("alpha")
|
val alpha = nf.contains("alpha")
|
||||||
val hex = nf.contains("hex")
|
val hex = nf.contains("hex")
|
||||||
val octal = nf.contains("octal")
|
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()
|
val newNumbers: MutableList<String?> = ArrayList()
|
||||||
for (i in numberRanges.indices) {
|
for (i in numberRanges.indices) {
|
||||||
val numberRange = numberRanges[i]
|
val numberRange = numberRanges[i]
|
||||||
@ -673,8 +677,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
val alpha = nf.contains("alpha")
|
val alpha = nf.contains("alpha")
|
||||||
val hex = nf.contains("hex")
|
val hex = nf.contains("hex")
|
||||||
val octal = nf.contains("octal")
|
val octal = nf.contains("octal")
|
||||||
val range =
|
val range = findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
|
||||||
SearchHelper.findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
|
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
logger.debug("no number on line")
|
logger.debug("no number on line")
|
||||||
return false
|
return false
|
||||||
@ -783,11 +786,11 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
return number
|
return number
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun addInsertListener(listener: VimInsertListener) {
|
fun addInsertListener(listener: VimInsertListener) {
|
||||||
insertListeners.add(listener)
|
insertListeners.add(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun removeInsertListener(listener: VimInsertListener) {
|
fun removeInsertListener(listener: VimInsertListener) {
|
||||||
insertListeners.remove(listener)
|
insertListeners.remove(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,11 +20,13 @@ import com.intellij.openapi.components.Storage;
|
|||||||
import com.intellij.openapi.editor.*;
|
import com.intellij.openapi.editor.*;
|
||||||
import com.intellij.openapi.editor.event.CaretEvent;
|
import com.intellij.openapi.editor.event.CaretEvent;
|
||||||
import com.intellij.openapi.editor.event.CaretListener;
|
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.editor.ex.EditorGutterComponentEx;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.maddyhome.idea.vim.KeyHandler;
|
import com.maddyhome.idea.vim.KeyHandler;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.api.*;
|
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.CaretVisualAttributesHelperKt;
|
||||||
import com.maddyhome.idea.vim.helper.CommandStateHelper;
|
import com.maddyhome.idea.vim.helper.CommandStateHelper;
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
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.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -45,6 +49,7 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
import static com.intellij.openapi.editor.EditorSettings.*;
|
import static com.intellij.openapi.editor.EditorSettings.*;
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
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;
|
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,22 +213,28 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
|
|
||||||
initLineNumbers(editor);
|
initLineNumbers(editor);
|
||||||
|
|
||||||
// We add Vim bindings to all opened editors, even read-only editors. We also add bindings to editors that are used
|
// Listen for changes to the font size, so we can hide the ex text field/output panel
|
||||||
// elsewhere in the IDE, rather than just for editing project files. This includes editors used as part of the UI,
|
if (editor instanceof EditorEx editorEx) {
|
||||||
// such as the VCS commit message, or used as read-only viewers for text output, such as log files in run
|
editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE);
|
||||||
// configurations or the Git Console tab. And editors are used for interactive stdin/stdout for console-based run
|
}
|
||||||
// configurations.
|
|
||||||
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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).
|
// 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
|
// Furthermore, interactive stdin/stdout console output in run configurations is hosted in a read-only editor, but
|
||||||
// edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's `isViewer` property and
|
// it can still be edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's
|
||||||
// allows typing if the associated process (if any) is still running. We can get the editor's console view and check
|
// `isViewer` property and allows typing if the associated process (if any) is still running. We can get the
|
||||||
// this ourselves, but we have to wait until the editor has finished initialising before it's available in user
|
// editor's console view and check this ourselves, but we have to wait until the editor has finished initialising
|
||||||
// data.
|
// 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
|
// 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.
|
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
|
||||||
Runnable switchToInsertMode = () -> {
|
Runnable switchToInsertMode = () -> {
|
||||||
@ -234,7 +245,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
if (!editor.isViewer() &&
|
if (!editor.isViewer() &&
|
||||||
!EditorHelper.isFileEditor(editor) &&
|
!EditorHelper.isFileEditor(editor) &&
|
||||||
editor.getDocument().isWritable() &&
|
editor.getDocument().isWritable() &&
|
||||||
!CommandStateHelper.inInsertMode(editor)) {
|
!CommandStateHelper.inInsertMode(editor) &&
|
||||||
|
editor.getEditorKind() != EditorKind.DIFF) {
|
||||||
switchToInsertMode.run();
|
switchToInsertMode.run();
|
||||||
}
|
}
|
||||||
ApplicationManager.getApplication().invokeLater(
|
ApplicationManager.getApplication().invokeLater(
|
||||||
@ -253,6 +265,9 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
UserDataManager.unInitializeEditor(editor);
|
UserDataManager.unInitializeEditor(editor);
|
||||||
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
|
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
|
||||||
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
|
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
|
||||||
|
if (editor instanceof EditorEx editorEx) {
|
||||||
|
editorEx.removePropertyChangeListener(FontSizeChangeListener.INSTANCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyIdeaJoin(@Nullable Project project, @NotNull VimEditor editor) {
|
public void notifyIdeaJoin(@Nullable Project project, @NotNull VimEditor editor) {
|
||||||
@ -315,7 +330,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
@Override
|
@Override
|
||||||
public Integer convert(@NotNull Editor editor, int lineNumber) {
|
public Integer convert(@NotNull Editor editor, int lineNumber) {
|
||||||
final IjVimEditor ijVimEditor = new IjVimEditor(editor);
|
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;
|
final int caretLine = editor.getCaretModel().getLogicalPosition().line;
|
||||||
|
|
||||||
// lineNumber is 1 based
|
// lineNumber is 1 based
|
||||||
@ -382,4 +397,33 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
return Stream.empty();
|
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
|
* options
|
||||||
*/
|
*/
|
||||||
@Suppress("SpellCheckingInspection")
|
@Suppress("SpellCheckingInspection")
|
||||||
public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
|
open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
|
||||||
public var ide: String by optionProperty(IjOptions.ide)
|
var ide: String by optionProperty(IjOptions.ide)
|
||||||
public var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
|
var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
|
||||||
public var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
|
var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
|
||||||
public val ideavimsupport: StringListOptionValue by optionProperty(IjOptions.ideavimsupport)
|
val ideavimsupport: StringListOptionValue by optionProperty(IjOptions.ideavimsupport)
|
||||||
public var ideawrite: String by optionProperty(IjOptions.ideawrite)
|
var ideawrite: String by optionProperty(IjOptions.ideawrite)
|
||||||
public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
|
val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
|
||||||
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
||||||
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
|
var visualdelay: Int by optionProperty(IjOptions.visualdelay)
|
||||||
|
|
||||||
// Temporary options to control work-in-progress behaviour
|
// Temporary options to control work-in-progress behaviour
|
||||||
public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
|
var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
|
||||||
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
|
var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
|
||||||
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
|
var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
||||||
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
|
||||||
public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
|
var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
|
||||||
public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
|
|
||||||
public 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.
|
* 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
|
// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
|
||||||
public var breakindent: Boolean by optionProperty(IjOptions.breakindent)
|
var breakindent: Boolean by optionProperty(IjOptions.breakindent)
|
||||||
public val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
|
val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
|
||||||
public var cursorline: Boolean by optionProperty(IjOptions.cursorline)
|
var cursorline: Boolean by optionProperty(IjOptions.cursorline)
|
||||||
public var fileformat: String by optionProperty(IjOptions.fileformat)
|
var fileformat: String by optionProperty(IjOptions.fileformat)
|
||||||
public var list: Boolean by optionProperty(IjOptions.list)
|
var list: Boolean by optionProperty(IjOptions.list)
|
||||||
public var number: Boolean by optionProperty(IjOptions.number)
|
var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
|
||||||
public var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
|
var textwidth: Int by optionProperty(IjOptions.textwidth)
|
||||||
public var textwidth: Int by optionProperty(IjOptions.textwidth)
|
var wrap: Boolean by optionProperty(IjOptions.wrap)
|
||||||
public var wrap: Boolean by optionProperty(IjOptions.wrap)
|
|
||||||
|
|
||||||
// IntelliJ specific options
|
// IntelliJ specific options
|
||||||
public var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
|
var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
|
||||||
public var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
|
var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
|
||||||
public var idearefactormode: String by optionProperty(IjOptions.idearefactormode)
|
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
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
|
|
||||||
@Suppress("SpellCheckingInspection")
|
@Suppress("SpellCheckingInspection")
|
||||||
public object IjOptions {
|
object IjOptions {
|
||||||
|
|
||||||
public fun initialise() {
|
fun initialise() {
|
||||||
// Calling this method allows for deterministic initialisation of IjOptions, specifically initialising the
|
// 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
|
// 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
|
// 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
|
// 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))
|
val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
|
||||||
public val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") {
|
val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") {
|
||||||
override fun checkIfValueValid(value: VimDataType, token: String) {
|
override fun checkIfValueValid(value: VimDataType, token: String) {
|
||||||
super.checkIfValueValid(value, token)
|
super.checkIfValueValid(value, token)
|
||||||
if (value != VimString.EMPTY) {
|
if (value != VimString.EMPTY) {
|
||||||
@ -55,19 +55,18 @@ public object IjOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
public val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
|
val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
|
||||||
public val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
|
val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
|
||||||
public val number: ToggleOption = addOption(ToggleOption("number", LOCAL_TO_WINDOW, "nu", false))
|
val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
|
||||||
public val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
|
val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
|
||||||
public val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
|
val wrap: ToggleOption = addOption(ToggleOption("wrap", LOCAL_TO_WINDOW, "wrap", true))
|
||||||
public 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,
|
// 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'`
|
// 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))
|
addOption(ToggleOption("bomb", LOCAL_TO_BUFFER, "bomb", false, isLocalNoGlobal = true))
|
||||||
public val fileencoding: StringOption = addOption(
|
val fileencoding: StringOption = addOption(
|
||||||
StringOption(
|
StringOption(
|
||||||
"fileencoding",
|
"fileencoding",
|
||||||
LOCAL_TO_BUFFER,
|
LOCAL_TO_BUFFER,
|
||||||
@ -76,7 +75,7 @@ public object IjOptions {
|
|||||||
isLocalNoGlobal = true
|
isLocalNoGlobal = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
public val fileformat: StringOption = addOption(
|
val fileformat: StringOption = addOption(
|
||||||
StringOption(
|
StringOption(
|
||||||
"fileformat",
|
"fileformat",
|
||||||
LOCAL_TO_BUFFER,
|
LOCAL_TO_BUFFER,
|
||||||
@ -88,15 +87,15 @@ public object IjOptions {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// IntelliJ specific functionality - custom options
|
// IntelliJ specific functionality - custom options
|
||||||
public val ide: StringOption = addOption(
|
val ide: StringOption = addOption(
|
||||||
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
|
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)
|
ToggleOption("ideacopypreprocess", GLOBAL_OR_LOCAL_TO_BUFFER, "ideacopypreprocess", false)
|
||||||
)
|
)
|
||||||
public val ideajoin: ToggleOption = addOption(ToggleOption("ideajoin", GLOBAL_OR_LOCAL_TO_BUFFER, "ideajoin", false))
|
val ideajoin: ToggleOption = addOption(ToggleOption("ideajoin", GLOBAL_OR_LOCAL_TO_BUFFER, "ideajoin", false))
|
||||||
public val ideamarks: ToggleOption = addOption(ToggleOption("ideamarks", GLOBAL, "ideamarks", true))
|
val ideamarks: ToggleOption = addOption(ToggleOption("ideamarks", GLOBAL, "ideamarks", true))
|
||||||
public val idearefactormode: StringOption = addOption(
|
val idearefactormode: StringOption = addOption(
|
||||||
StringOption(
|
StringOption(
|
||||||
"idearefactormode",
|
"idearefactormode",
|
||||||
GLOBAL_OR_LOCAL_TO_BUFFER,
|
GLOBAL_OR_LOCAL_TO_BUFFER,
|
||||||
@ -105,7 +104,7 @@ public object IjOptions {
|
|||||||
IjOptionConstants.ideaRefactorModeValues
|
IjOptionConstants.ideaRefactorModeValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
public val ideastatusicon: StringOption = addOption(
|
val ideastatusicon: StringOption = addOption(
|
||||||
StringOption(
|
StringOption(
|
||||||
"ideastatusicon",
|
"ideastatusicon",
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
@ -114,7 +113,7 @@ public object IjOptions {
|
|||||||
IjOptionConstants.ideaStatusIconValues
|
IjOptionConstants.ideaStatusIconValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
public val ideavimsupport: StringListOption = addOption(
|
val ideavimsupport: StringListOption = addOption(
|
||||||
StringListOption(
|
StringListOption(
|
||||||
"ideavimsupport",
|
"ideavimsupport",
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
@ -123,27 +122,26 @@ public object IjOptions {
|
|||||||
IjOptionConstants.ideavimsupportValues
|
IjOptionConstants.ideavimsupportValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@JvmField public val ideawrite: StringOption = addOption(
|
@JvmField
|
||||||
|
val ideawrite: StringOption = addOption(
|
||||||
StringOption("ideawrite", GLOBAL, "ideawrite", "all", IjOptionConstants.ideaWriteValues)
|
StringOption("ideawrite", GLOBAL, "ideawrite", "all", IjOptionConstants.ideaWriteValues)
|
||||||
)
|
)
|
||||||
public val lookupkeys: StringListOption = addOption(
|
val lookupkeys: StringListOption = addOption(
|
||||||
StringListOption(
|
StringListOption(
|
||||||
"lookupkeys",
|
"lookupkeys",
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
"lookupkeys",
|
"lookupkeys",
|
||||||
"<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
|
"<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))
|
val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
||||||
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
||||||
|
|
||||||
// Temporary feature flags during development, not really intended for external use
|
// Temporary feature flags during development, not really intended for external use
|
||||||
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
|
val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
|
||||||
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
|
val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
|
||||||
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true))
|
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
|
||||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
|
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
|
||||||
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
|
val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
|
||||||
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
|
|
||||||
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
|
|
||||||
|
|
||||||
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
|
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
|
||||||
// derives from Option<VimInt>
|
// derives from Option<VimInt>
|
||||||
|
@ -21,7 +21,7 @@ import com.maddyhome.idea.vim.newapi.ij
|
|||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class IjVimPsiService: VimPsiService {
|
class IjVimPsiService: VimPsiService {
|
||||||
override fun getCommentAtPos(editor: VimEditor, pos: Int): Pair<TextRange, Pair<String, String>?>? {
|
override fun getCommentAtPos(editor: VimEditor, pos: Int): Pair<TextRange, Pair<String, String>?>? {
|
||||||
val psiFile = PsiHelper.getFile(editor.ij) ?: return null
|
val psiFile = PsiHelper.getFile(editor.ij) ?: return null
|
||||||
val psiElement = psiFile.findElementAt(pos) ?: 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.VimRedrawService
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
|
||||||
public class IjVimRedrawService : VimRedrawService {
|
class IjVimRedrawService : VimRedrawService {
|
||||||
override fun redraw() {
|
override fun redraw() {
|
||||||
// The only thing IntelliJ needs to redraw is the status line. Everything else is handled automatically.
|
// The only thing IntelliJ needs to redraw is the status line. Everything else is handled automatically.
|
||||||
redrawStatusLine()
|
redrawStatusLine()
|
||||||
@ -25,11 +25,11 @@ public class IjVimRedrawService : VimRedrawService {
|
|||||||
injector.messages.clearStatusBarMessage()
|
injector.messages.clearStatusBarMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Simulate Vim's redraw when the current editor changes
|
* Simulate Vim's redraw when the current editor changes
|
||||||
*/
|
*/
|
||||||
public fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
||||||
injector.redrawService.redraw()
|
injector.redrawService.redraw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ public class IjVimRedrawService : VimRedrawService {
|
|||||||
*
|
*
|
||||||
* Only redraw if lines are added/removed.
|
* Only redraw if lines are added/removed.
|
||||||
*/
|
*/
|
||||||
public object RedrawListener : DocumentListener {
|
object RedrawListener : DocumentListener {
|
||||||
override fun documentChanged(event: DocumentEvent) {
|
override fun documentChanged(event: DocumentEvent) {
|
||||||
if (VimPlugin.isNotEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
if (event.newFragment.contains("\n") || event.oldFragment.contains("\n")) {
|
if (event.newFragment.contains("\n") || event.oldFragment.contains("\n")) {
|
||||||
|
@ -314,7 +314,7 @@ internal class MotionGroup : VimMotionGroupBase() {
|
|||||||
}
|
}
|
||||||
is Mode.CMD_LINE -> {
|
is Mode.CMD_LINE -> {
|
||||||
injector.processGroup.cancelExEntry(vimEditor, false)
|
injector.processGroup.cancelExEntry(vimEditor, false)
|
||||||
ExOutputModel.getInstance(editor).clear()
|
ExOutputModel.tryGetInstance(editor)?.close()
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,12 @@ import com.intellij.application.options.CodeStyle
|
|||||||
import com.intellij.codeStyle.AbstractConvertLineSeparatorsAction
|
import com.intellij.codeStyle.AbstractConvertLineSeparatorsAction
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.editor.EditorKind
|
||||||
import com.intellij.openapi.editor.EditorSettings.LineNumerationType
|
import com.intellij.openapi.editor.EditorSettings.LineNumerationType
|
||||||
import com.intellij.openapi.editor.ScrollPositionCalculator
|
import com.intellij.openapi.editor.ScrollPositionCalculator
|
||||||
import com.intellij.openapi.editor.ex.EditorEx
|
import com.intellij.openapi.editor.ex.EditorEx
|
||||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
|
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.FileDocumentManager
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||||
import com.intellij.openapi.fileEditor.TextEditor
|
import com.intellij.openapi.fileEditor.TextEditor
|
||||||
@ -94,12 +96,12 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup, InternalOpt
|
|||||||
addOptionValueOverride(IjOptions.fileencoding, FileEncodingOptionMapper())
|
addOptionValueOverride(IjOptions.fileencoding, FileEncodingOptionMapper())
|
||||||
addOptionValueOverride(IjOptions.fileformat, FileFormatOptionMapper())
|
addOptionValueOverride(IjOptions.fileformat, FileFormatOptionMapper())
|
||||||
addOptionValueOverride(IjOptions.list, ListOptionMapper(IjOptions.list, this))
|
addOptionValueOverride(IjOptions.list, ListOptionMapper(IjOptions.list, this))
|
||||||
addOptionValueOverride(IjOptions.number, NumberOptionMapper(IjOptions.number, this))
|
|
||||||
addOptionValueOverride(IjOptions.relativenumber, RelativeNumberOptionMapper(IjOptions.relativenumber, this))
|
addOptionValueOverride(IjOptions.relativenumber, RelativeNumberOptionMapper(IjOptions.relativenumber, this))
|
||||||
addOptionValueOverride(IjOptions.textwidth, TextWidthOptionMapper(IjOptions.textwidth))
|
addOptionValueOverride(IjOptions.textwidth, TextWidthOptionMapper(IjOptions.textwidth))
|
||||||
addOptionValueOverride(IjOptions.wrap, WrapOptionMapper(IjOptions.wrap, this))
|
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
|
// 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.scrolljump, ScrollJumpOptionMapper(Options.scrolljump, this))
|
||||||
addOptionValueOverride(Options.sidescroll, SideScrollOptionMapper(Options.sidescroll, this))
|
addOptionValueOverride(Options.sidescroll, SideScrollOptionMapper(Options.sidescroll, this))
|
||||||
addOptionValueOverride(Options.scrolloff, ScrollOffOptionMapper(Options.scrolloff, 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 getEffectiveExternalValue(editor: VimEditor) = editor.ij.settings.verticalScrollJump.asVimInt()
|
||||||
|
|
||||||
override fun setLocalExternalValue(editor: VimEditor, value: VimInt) {
|
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) {
|
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
|
* 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
|
* takes precedence over the global, persistent setting until the option is reset with either `:set scrolloff&` or
|
||||||
* `:setlocal scrolloff<`.
|
* `: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)
|
private class ScrollOffOptionMapper(
|
||||||
: GlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(option, internalOptionValueAccessor) {
|
scrollOffOption: NumberOption,
|
||||||
|
internalOptionValueAccessor: InternalOptionValueAccessor,
|
||||||
|
) : OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(scrollOffOption, internalOptionValueAccessor) {
|
||||||
|
|
||||||
override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_VERTICAL_SCROLL_OFFSET
|
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 fun getExternalGlobalValue() =
|
||||||
override val canUserModifyExternalLocalValue: Boolean = false
|
EditorSettingsExternalizable.getInstance().verticalScrollOffset.asVimInt()
|
||||||
|
|
||||||
override fun getGlobalExternalValue() = EditorSettingsExternalizable.getInstance().verticalScrollOffset.asVimInt()
|
override fun suppressExternalLocalValue(editor: VimEditor) {
|
||||||
override fun getEffectiveExternalValue(editor: VimEditor) = editor.ij.settings.verticalScrollOffset.asVimInt()
|
editor.ij.settings.verticalScrollOffset = 0
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
* 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
|
* IntelliJ supports horizontal scroll offset in a similar manner to Vim. However, the implementation calculates offsets
|
||||||
* horizontal scroll offset value when the user explicitly sets the Vim value, so that IntelliJ could also use the
|
* using integer font sizes, which can lead to minor inaccuracies when compared to the IdeaVim implementation, such as
|
||||||
* value. Unfortunately, IntelliJ's scrolling calculation logic is based on integer font width maths, which causes
|
* differences running tests on a Mac.
|
||||||
* problems with fractional font widths (such as on a Mac when running tests).
|
|
||||||
*
|
*
|
||||||
* For example, given a `'sidescrolloff'` value of `10`, and a fractional font width of `7.8`, IntelliJ will scroll `80`
|
* 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
|
* 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`,
|
* need to scroll, which in turn can cause issues with `'sidescroll'` (jump), because IntelliJ doesn't support
|
||||||
* which would scroll to position the caret in the middle of the display.
|
* `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
|
* 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
|
* 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.
|
* vim-engine so it can also work in Fleet.
|
||||||
*/
|
*/
|
||||||
private class SideScrollOffOptionMapper(
|
private class SideScrollOffOptionMapper(
|
||||||
private val sideScrollOffOption: NumberOption,
|
sideScrollOffOption: NumberOption,
|
||||||
private val internalOptionValueAccessor: InternalOptionValueAccessor,
|
internalOptionValueAccessor: InternalOptionValueAccessor,
|
||||||
) : GlobalOptionValueOverride<VimInt>, LocalOptionValueOverride<VimInt>, IdeaBackedOptionValueOverride {
|
) : OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(sideScrollOffOption, internalOptionValueAccessor) {
|
||||||
|
|
||||||
override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_HORIZONTAL_SCROLL_OFFSET
|
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) {
|
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
|
return storedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setGlobalValue(
|
override fun setGlobalValue(storedValue: OptionValue<T>, newValue: OptionValue<T>, editor: VimEditor?): Boolean {
|
||||||
storedValue: OptionValue<VimInt>,
|
// The user is updating the global Vim value, via `:setglobal`. IdeaVim scrolling will be using this value. Make
|
||||||
newValue: OptionValue<VimInt>,
|
// sure the IntelliJ values won't interfere
|
||||||
editor: VimEditor?,
|
// Note that we don't reset the local IntelliJ value for `:set {option}&` or `:set {option}<` because the current
|
||||||
): Boolean {
|
// global IntelliJ value might still interfere with IdeaVim's implementation. We continue to suppress the IntelliJ
|
||||||
// The user has typed `:setlocal`. Just make sure that the IntelliJ value doesn't interfere with the Vim value
|
// value.
|
||||||
injector.editorGroup.getEditors().forEach { it.ij.settings.horizontalScrollOffset = 0 }
|
injector.editorGroup.getEditors().forEach { suppressExternalLocalValue(it) }
|
||||||
return storedValue.value != newValue.value
|
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) {
|
if (storedValue == null) {
|
||||||
// Initialisation. Report the global value of the setting. We ignore the local value because the user doesn't have
|
// 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
|
// a way to set it. If it has been changed (unlikely if stored value hasn't been set yet), then it would be 0
|
||||||
// handle sidescroll=0 to mean half a page)
|
return OptionValue.Default(getExternalGlobalValue())
|
||||||
return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storedValue is OptionValue.Default && storedValue.value != sideScrollOffOption.unsetValue) {
|
if (storedValue is OptionValue.Default && storedValue.value != option.unsetValue) {
|
||||||
// The local value is set to the default value (as a copy of the global value), so return the global external
|
// The local value has been reset to Default. It's not the Vim default of "unset", but a copy of the global value.
|
||||||
// value as a default
|
// Return the current value of the global external value
|
||||||
return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
|
return OptionValue.Default(getExternalGlobalValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whatever is left is either explicitly set by the user, or option.unsetValue
|
// Whatever is left is either explicitly set by the user, or option.unsetValue
|
||||||
return storedValue
|
return storedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setLocalValue(
|
override fun setLocalValue(storedValue: OptionValue<T>?, newValue: OptionValue<T>, editor: VimEditor): Boolean {
|
||||||
storedValue: OptionValue<VimInt>?,
|
// Vim local value is being set. We do nothing but set the local IntelliJ value to 0, so IntelliJ's scrolling
|
||||||
newValue: OptionValue<VimInt>,
|
// doesn't affect IdeaVim's scrolling
|
||||||
editor: VimEditor,
|
suppressExternalLocalValue(editor)
|
||||||
): 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
|
|
||||||
return storedValue?.value != newValue.value
|
return storedValue?.value != newValue.value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGlobalIdeaValueChanged(propertyName: String) {
|
override fun onGlobalIdeaValueChanged(propertyName: String) {
|
||||||
if (propertyName == ideaPropertyName) {
|
if (propertyName == ideaPropertyName) {
|
||||||
// Again, just make sure the IntelliJ local value is 0
|
// The IntelliJ global value has changed. We want to use this as the Vim global value. Since we control scrolling,
|
||||||
injector.editorGroup.getEditors().forEach { it.ij.settings.horizontalScrollOffset = 0 }
|
// 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 globalScope = OptionAccessScope.GLOBAL(null)
|
||||||
val storedValue = internalOptionValueAccessor.getOptionValueInternal(sideScrollOffOption, globalScope)
|
val storedValue = internalOptionValueAccessor.getOptionValueInternal(option, globalScope)
|
||||||
if (storedValue !is OptionValue.Default) {
|
if (storedValue !is OptionValue.Default) {
|
||||||
val externalGlobalValue = EditorSettingsExternalizable.getInstance().horizontalScrollOffset
|
|
||||||
internalOptionValueAccessor.setOptionValueInternal(
|
internalOptionValueAccessor.setOptionValueInternal(
|
||||||
sideScrollOffOption,
|
option,
|
||||||
globalScope,
|
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())
|
setIsUseSoftWraps(editor, value.asBoolean())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGlobalIsUseSoftWraps(editor: VimEditor): Boolean {
|
override fun canInitialiseOptionFrom(sourceEditor: VimEditor, targetEditor: VimEditor): Boolean {
|
||||||
val settings = EditorSettingsExternalizable.getInstance()
|
// IntelliJ's soft-wrap settings are based on editor kind, so there can be different wrap options for consoles,
|
||||||
if (settings.isUseSoftWraps) {
|
// main editors, etc. This is particularly noticeable in the console when running an application. The main editor
|
||||||
val masks = settings.softWrapFileMasks
|
// might have the Vim default with line wrap enabled. Initialising the run console will also have a default value,
|
||||||
if (masks.trim() == "*") return true
|
// 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 ->
|
val sourceKind = editorKindToSoftWrapAppliancesPlace(sourceEditor.ij.editorKind)
|
||||||
masks.split(";").forEach { mask ->
|
val targetKind = editorKindToSoftWrapAppliancesPlace(targetEditor.ij.editorKind)
|
||||||
val trimmed = mask.trim()
|
return sourceKind == targetKind
|
||||||
if (trimmed.isNotEmpty() && PatternUtil.fromMask(trimmed).matcher(file.name).matches()) {
|
}
|
||||||
return true
|
|
||||||
|
|
||||||
|
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
|
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")
|
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
|
||||||
public companion object {
|
companion object {
|
||||||
|
|
||||||
public const val idearefactormode_keep: String = "keep"
|
const val idearefactormode_keep: String = "keep"
|
||||||
public const val idearefactormode_select: String = "select"
|
const val idearefactormode_select: String = "select"
|
||||||
public const val idearefactormode_visual: String = "visual"
|
const val idearefactormode_visual: String = "visual"
|
||||||
|
|
||||||
public const val ideastatusicon_enabled: String = "enabled"
|
const val ideastatusicon_enabled: String = "enabled"
|
||||||
public const val ideastatusicon_gray: String = "gray"
|
const val ideastatusicon_gray: String = "gray"
|
||||||
public const val ideastatusicon_disabled: String = "disabled"
|
const val ideastatusicon_disabled: String = "disabled"
|
||||||
|
|
||||||
public const val ideavimsupport_dialog: String = "dialog"
|
const val ideavimsupport_dialog: String = "dialog"
|
||||||
public const val ideavimsupport_singleline: String = "singleline"
|
const val ideavimsupport_singleline: String = "singleline"
|
||||||
public const val ideavimsupport_dialoglegacy: String = "dialoglegacy"
|
const val ideavimsupport_dialoglegacy: String = "dialoglegacy"
|
||||||
|
|
||||||
public const val ideawrite_all: String = "all"
|
const val ideawrite_all: String = "all"
|
||||||
public const val ideawrite_file: String = "file"
|
const val ideawrite_file: String = "file"
|
||||||
|
|
||||||
public val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
|
val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
|
||||||
public val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
|
val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
|
||||||
public val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
|
val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
|
||||||
public val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
|
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.openapi.progress.ProgressManager
|
||||||
import com.intellij.util.execution.ParametersListUtil
|
import com.intellij.util.execution.ParametersListUtil
|
||||||
import com.intellij.util.text.CharSequenceReader
|
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.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimProcessGroupBase
|
import com.maddyhome.idea.vim.api.VimProcessGroupBase
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.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.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.BufferedWriter
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStreamWriter
|
import java.io.OutputStreamWriter
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
import java.io.Writer
|
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)
|
@Throws(ExecutionException::class, ProcessCanceledException::class)
|
||||||
public override fun executeCommand(
|
override fun executeCommand(
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
command: String,
|
command: String,
|
||||||
input: CharSequence?,
|
input: CharSequence?,
|
||||||
@ -221,7 +132,7 @@ public class ProcessGroup : VimProcessGroupBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
companion object {
|
||||||
private val logger = logger<ProcessGroup>()
|
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("line", jump.line.toString())
|
||||||
jumpElem.setAttribute("column", jump.col.toString())
|
jumpElem.setAttribute("column", jump.col.toString())
|
||||||
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
|
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
|
||||||
|
jumpElem.setAttribute("protocol", StringUtil.notNullize(jump.protocol))
|
||||||
projectElement.addContent(jumpElem)
|
projectElement.addContent(jumpElem)
|
||||||
if (logger.isDebug()) {
|
if (logger.isDebug()) {
|
||||||
logger.debug("saved jump = $jump")
|
logger.debug("saved jump = $jump")
|
||||||
@ -73,6 +74,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
|
|||||||
Integer.parseInt(jumpElement.getAttributeValue("line")),
|
Integer.parseInt(jumpElement.getAttributeValue("line")),
|
||||||
Integer.parseInt(jumpElement.getAttributeValue("column")),
|
Integer.parseInt(jumpElement.getAttributeValue("column")),
|
||||||
jumpElement.getAttributeValue("filename"),
|
jumpElement.getAttributeValue("filename"),
|
||||||
|
jumpElement.getAttributeValue("protocol", "file"),
|
||||||
)
|
)
|
||||||
jumps.add(jump)
|
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
|
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
|
||||||
// we do not want jumps that were processed before
|
// we do not want jumps that were processed before
|
||||||
val jump = buildJump(changePlace) ?: return
|
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
|
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
|
||||||
// we do not want jumps that were processed before
|
// we do not want jumps that were processed before
|
||||||
val jump = buildJump(changePlace) ?: return
|
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
|
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 editor = (vimEditor as IjVimEditor).editor
|
||||||
val context = vimContext.context as DataContext
|
val context = vimContext.context as DataContext
|
||||||
val carets: MutableMap<Caret, RangeMarker> = mutableMapOf()
|
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 ->
|
EditorHelper.getOrderedCaretsList(editor).forEach { caret ->
|
||||||
val startOffset =
|
val startOffset =
|
||||||
prepareDocumentAndGetStartOffsets(
|
prepareDocumentAndGetStartOffsets(
|
||||||
|
@ -53,12 +53,12 @@ import javax.swing.Timer
|
|||||||
* no adjustment gets performed and IdeaVim stays in insert mode.
|
* no adjustment gets performed and IdeaVim stays in insert mode.
|
||||||
*/
|
*/
|
||||||
// Do not remove until it's used in EasyMotion plugin in tests
|
// Do not remove until it's used in EasyMotion plugin in tests
|
||||||
public object VimVisualTimer {
|
object VimVisualTimer {
|
||||||
|
|
||||||
public var swingTimer: Timer? = null
|
var swingTimer: Timer? = null
|
||||||
public var mode: Mode? = 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()
|
swingTimer?.stop()
|
||||||
|
|
||||||
if (mode == null) mode = currentMode
|
if (mode == null) mode = currentMode
|
||||||
@ -70,7 +70,7 @@ public object VimVisualTimer {
|
|||||||
swingTimer = timer
|
swingTimer = timer
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun doNow() {
|
fun doNow() {
|
||||||
val swingTimer1 = swingTimer
|
val swingTimer1 = swingTimer
|
||||||
if (swingTimer1 != null) {
|
if (swingTimer1 != null) {
|
||||||
swingTimer1.stop()
|
swingTimer1.stop()
|
||||||
@ -80,12 +80,12 @@ public object VimVisualTimer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun drop() {
|
fun drop() {
|
||||||
swingTimer?.stop()
|
swingTimer?.stop()
|
||||||
swingTimer = null
|
swingTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
||||||
task(mode)
|
task(mode)
|
||||||
swingTimer = null
|
swingTimer = null
|
||||||
mode = null
|
mode = null
|
||||||
|
@ -9,13 +9,9 @@
|
|||||||
package com.maddyhome.idea.vim.group.visual
|
package com.maddyhome.idea.vim.group.visual
|
||||||
|
|
||||||
import com.intellij.find.FindManager
|
import com.intellij.find.FindManager
|
||||||
import com.intellij.openapi.editor.Editor
|
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimVisualMotionGroupBase
|
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.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,12 +27,4 @@ internal class VisualMotionGroup : VimVisualMotionGroupBase() {
|
|||||||
|
|
||||||
return super.autodetectVisualSubmode(editor)
|
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
|
@TestOnly
|
||||||
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
|
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
|
||||||
|
|
||||||
public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
|
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
|
||||||
override fun isReplaceCharChanged(editor: VimEditor) {
|
override fun isReplaceCharChanged(editor: VimEditor) {
|
||||||
updateCaretsVisual(editor)
|
updateCaretsVisual(editor)
|
||||||
}
|
}
|
||||||
@ -163,7 +163,7 @@ public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun updateAllEditorsCaretsVisual() {
|
fun updateAllEditorsCaretsVisual() {
|
||||||
injector.editorGroup.getEditors().forEach { editor ->
|
injector.editorGroup.getEditors().forEach { editor ->
|
||||||
val ijEditor = (editor as IjVimEditor).editor
|
val ijEditor = (editor as IjVimEditor).editor
|
||||||
ijEditor.updateCaretsVisualAttributes()
|
ijEditor.updateCaretsVisualAttributes()
|
||||||
|
@ -11,13 +11,7 @@
|
|||||||
package com.maddyhome.idea.vim.helper
|
package com.maddyhome.idea.vim.helper
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Editor
|
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.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.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
|
|
||||||
@ -27,55 +21,15 @@ internal val Mode.hasVisualSelection
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
val Mode.inNormalMode: Boolean
|
||||||
* 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
|
|
||||||
get() = this is Mode.NORMAL
|
get() = this is Mode.NORMAL
|
||||||
|
|
||||||
@get:JvmName("inInsertMode")
|
@get:JvmName("inInsertMode")
|
||||||
public val Editor.inInsertMode: Boolean
|
val Editor.inInsertMode: Boolean
|
||||||
get() = this.vim.mode == Mode.INSERT || this.vim.mode == Mode.REPLACE
|
get() = this.vim.mode == Mode.INSERT || this.vim.mode == Mode.REPLACE
|
||||||
|
|
||||||
@get:JvmName("inVisualMode")
|
@get:JvmName("inVisualMode")
|
||||||
public val Editor.inVisualMode: Boolean
|
val Editor.inVisualMode: Boolean
|
||||||
get() = this.vim.inVisualMode
|
get() = this.vim.inVisualMode
|
||||||
|
|
||||||
@get:JvmName("inExMode")
|
@get:JvmName("inExMode")
|
||||||
|
@ -100,15 +100,6 @@ public class EditorHelper {
|
|||||||
return EngineEditorHelperKt.normalizeVisualLine(new IjVimEditor(editor), line);
|
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.
|
* Best efforts to ensure that scroll offset doesn't overlap itself.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -28,7 +28,7 @@ import javax.swing.JComponent
|
|||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
|
|
||||||
@Deprecated("Use fileSize from VimEditor")
|
@Deprecated("Use fileSize from VimEditor")
|
||||||
public val Editor.fileSize: Int
|
val Editor.fileSize: Int
|
||||||
get() = document.textLength
|
get() = document.textLength
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,6 +15,7 @@ import com.intellij.openapi.editor.actionSystem.EditorActionManager
|
|||||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||||
import com.maddyhome.idea.vim.api.EngineEditorHelper
|
import com.maddyhome.idea.vim.api.EngineEditorHelper
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
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.api.VimVisualPosition
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
@ -57,4 +58,20 @@ internal class IjEditorHelper : EngineEditorHelper {
|
|||||||
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
|
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
|
||||||
return EditorUtil.inlayAwareOffsetToVisualPosition(editor.ij, offset).vim
|
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
|
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.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.Direction
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.helper.SearchHelper.findPositionOfFirstCharacter
|
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||||
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
private data class State(val position: Int, val trigger: Char, val inQuote: Boolean?, val lastOpenSingleQuotePos: Int)
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntComparator
|
||||||
// bounds are considered inside corresponding quotes
|
import it.unimi.dsi.fastutil.ints.IntIterator
|
||||||
internal fun checkInString(chars: CharSequence, currentPos: Int, str: Boolean): Boolean {
|
import it.unimi.dsi.fastutil.ints.IntRBTreeSet
|
||||||
val begin = findPositionOfFirstCharacter(chars, currentPos, setOf('\n'), false, Direction.BACKWARDS)?.second?.plus(1) ?: 0
|
import it.unimi.dsi.fastutil.ints.IntSortedSet
|
||||||
val changes = quoteChanges(chars, begin)
|
import java.util.*
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check ignorecase and smartcase options to see if a case insensitive search should be performed with the given pattern.
|
* 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
|
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
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
val sortedResults = results.sortedBy { it.startOffset }.let { if (!forwards) it.reversed() else it }
|
val sortedResults = if (forwards) {
|
||||||
val nextIndex = sortedResults.indexOfFirst {
|
results.sortedBy { it.startOffset }
|
||||||
if (forwards) it.startOffset > initialOffset else it.startOffset < initialOffset
|
} else {
|
||||||
|
results.sortedByDescending { it.startOffset }
|
||||||
}
|
}
|
||||||
val toDrop = (nextIndex + count - 1).let { if (injector.globalOptions().wrapscan) it % results.size else it }
|
val closestIndex = if (forwards) {
|
||||||
return sortedResults.drop(toDrop).firstOrNull()?.startOffset ?: -1
|
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) {
|
internal fun highlightSearchResults(editor: Editor, pattern: String, results: List<TextRange>, currentMatchOffset: Int) {
|
||||||
|
@ -8,31 +8,17 @@
|
|||||||
package com.maddyhome.idea.vim.helper
|
package com.maddyhome.idea.vim.helper
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import org.jetbrains.annotations.ApiStatus
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.stream.Collectors
|
import java.util.stream.Collectors
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
/**
|
object StringHelper {
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("Use injector.parser.parseKeys(string)")
|
@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() }
|
return Arrays.stream(string).flatMap { o: String -> injector.parser.parseKeys(o).stream() }
|
||||||
.collect(Collectors.toList())
|
.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
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
// Do not remove until it's used in EasyMotion plugin in tests
|
// 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()
|
private val myKeyStrokes: MutableList<KeyStroke> = Lists.newArrayList()
|
||||||
public fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
|
fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
|
||||||
myKeyStrokes.clear()
|
myKeyStrokes.clear()
|
||||||
myKeyStrokes.addAll(keyStrokes)
|
myKeyStrokes.addAll(keyStrokes)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun nextKeyStroke(): KeyStroke? {
|
fun nextKeyStroke(): KeyStroke? {
|
||||||
// Return key from the unfinished mapping
|
// Return key from the unfinished mapping
|
||||||
/*
|
/*
|
||||||
MappingStack mappingStack = KeyHandler.getInstance().getMappingStack();
|
MappingStack mappingStack = KeyHandler.getInstance().getMappingStack();
|
||||||
@ -34,9 +34,9 @@ if (mappingStack.hasStroke()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun getInstance(editor: Editor): TestInputModel {
|
fun getInstance(editor: Editor): TestInputModel {
|
||||||
var model = editor.vimTestInputModel
|
var model = editor.vimTestInputModel
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
model = TestInputModel()
|
model = TestInputModel()
|
||||||
|
@ -10,8 +10,13 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.helper
|
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.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.colors.EditorColorsManager
|
||||||
|
import com.intellij.openapi.editor.impl.EditorImpl
|
||||||
import com.intellij.openapi.wm.IdeFocusManager
|
import com.intellij.openapi.wm.IdeFocusManager
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
@ -32,11 +37,21 @@ internal fun runAfterGotFocus(runnable: Runnable) {
|
|||||||
IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable, ModalityState.defaultModalityState())
|
IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable, ModalityState.defaultModalityState())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun selectFont(forStr: String): Font {
|
internal fun selectEditorFont(editor: Editor?, forText: String): Font {
|
||||||
val scheme = EditorColorsManager.getInstance().globalScheme
|
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 {
|
val scheme = EditorColorsManager.getInstance().globalScheme
|
||||||
Font(it, Font.PLAIN, scheme.editorFontSize).canDisplayUpTo(forStr) == -1
|
scheme.fontPreferences.realFontFamilies.forEach { fontName ->
|
||||||
} ?: return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize)
|
val font = Font(fontName, Font.PLAIN, scheme.editorFontSize)
|
||||||
return 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.CommandProcessor
|
||||||
import com.intellij.openapi.command.undo.UndoManager
|
import com.intellij.openapi.command.undo.UndoManager
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.fileEditor.TextEditor
|
import com.intellij.openapi.fileEditor.TextEditor
|
||||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||||
import com.intellij.openapi.util.registry.Registry
|
import com.intellij.openapi.util.registry.Registry
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.ChangesListener
|
import com.maddyhome.idea.vim.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.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
@ -32,6 +36,10 @@ import com.maddyhome.idea.vim.undo.UndoRedoBase
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
internal class UndoRedoHelper : UndoRedoBase() {
|
internal class UndoRedoHelper : UndoRedoBase() {
|
||||||
|
companion object {
|
||||||
|
private val logger = logger<UndoRedoHelper>()
|
||||||
|
}
|
||||||
|
|
||||||
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
|
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||||
val ijContext = context.context as DataContext
|
val ijContext = context.context as DataContext
|
||||||
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
||||||
@ -63,7 +71,17 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
|
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 {
|
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||||
@ -89,6 +107,22 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
return false
|
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(
|
private fun performRedo(
|
||||||
undoManager: UndoManager,
|
undoManager: UndoManager,
|
||||||
fileEditor: TextEditor,
|
fileEditor: TextEditor,
|
||||||
@ -114,10 +148,23 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
|
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 {
|
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)
|
removeSelections(editor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ import com.intellij.openapi.editor.VisualPosition
|
|||||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.util.UserDataHolder
|
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.LocalMarkStorage
|
||||||
import com.maddyhome.idea.vim.api.SelectionInfo
|
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.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
import com.maddyhome.idea.vim.group.visual.VisualChange
|
||||||
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
|
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
|
||||||
@ -44,7 +44,7 @@ import kotlin.reflect.KProperty
|
|||||||
/**
|
/**
|
||||||
* Caret's offset when entering visual mode
|
* Caret's offset when entering visual mode
|
||||||
*/
|
*/
|
||||||
public var Caret.vimSelectionStart: Int
|
var Caret.vimSelectionStart: Int
|
||||||
get() {
|
get() {
|
||||||
val selectionStart = _vimSelectionStart
|
val selectionStart = _vimSelectionStart
|
||||||
if (selectionStart == null) {
|
if (selectionStart == null) {
|
||||||
@ -95,7 +95,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Data could be lost during visual block motion
|
// 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.markStorage: LocalMarkStorage? by userDataCaretToEditor()
|
||||||
internal var Caret.lastSelectionInfo: SelectionInfo? 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 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.
|
* Function for delegated properties.
|
||||||
* The property will be delegated to UserData and has nullable type.
|
* 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
|
import com.intellij.openapi.editor.Editor
|
||||||
|
|
||||||
public interface VimInsertListener {
|
interface VimInsertListener {
|
||||||
public fun insertModeStarted(editor: Editor)
|
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.MotionGroup
|
||||||
import com.maddyhome.idea.vim.group.OptionGroup
|
import com.maddyhome.idea.vim.group.OptionGroup
|
||||||
import com.maddyhome.idea.vim.group.ScrollGroup
|
import com.maddyhome.idea.vim.group.ScrollGroup
|
||||||
import com.maddyhome.idea.vim.group.SearchGroup
|
|
||||||
import com.maddyhome.idea.vim.group.VimMarkServiceImpl
|
import com.maddyhome.idea.vim.group.VimMarkServiceImpl
|
||||||
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
|
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
|
||||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
|
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.resetVimLastColumn
|
||||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
||||||
import com.maddyhome.idea.vim.helper.vimDisabled
|
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.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.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
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.state.mode.selectionType
|
||||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
||||||
import com.maddyhome.idea.vim.ui.ShowCmdWidgetUpdater
|
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.MacroWidgetListener
|
||||||
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
|
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
|
||||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
|
import com.maddyhome.idea.vim.ui.widgets.mode.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.
|
* 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 ->
|
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 {
|
internal object VimListenerManager {
|
||||||
|
|
||||||
@ -150,6 +166,9 @@ internal object VimListenerManager {
|
|||||||
injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
|
injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
|
||||||
caretVisualAttributesListener.updateAllEditorsCaretsVisual()
|
caretVisualAttributesListener.updateAllEditorsCaretsVisual()
|
||||||
|
|
||||||
|
val insertTimeRecorder = InsertTimeRecorder()
|
||||||
|
injector.listenersNotifier.modeChangeListeners.add(insertTimeRecorder)
|
||||||
|
|
||||||
val modeWidgetListener = ModeWidgetListener()
|
val modeWidgetListener = ModeWidgetListener()
|
||||||
injector.listenersNotifier.modeChangeListeners.add(modeWidgetListener)
|
injector.listenersNotifier.modeChangeListeners.add(modeWidgetListener)
|
||||||
injector.listenersNotifier.myEditorListeners.add(modeWidgetListener)
|
injector.listenersNotifier.myEditorListeners.add(modeWidgetListener)
|
||||||
@ -182,7 +201,7 @@ internal object VimListenerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val optionGroup = VimPlugin.getOptionGroup()
|
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(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
||||||
@ -214,7 +233,7 @@ internal object VimListenerManager {
|
|||||||
EventFacade.getInstance().restoreTypedActionHandler()
|
EventFacade.getInstance().restoreTypedActionHandler()
|
||||||
|
|
||||||
val optionGroup = VimPlugin.getOptionGroup()
|
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(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||||
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||||
@ -274,6 +293,11 @@ internal object VimListenerManager {
|
|||||||
val disposable =
|
val disposable =
|
||||||
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
|
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
|
||||||
|
|
||||||
|
// Protect against double initialisation
|
||||||
|
if (editor.getUserData(editorListenersDisposableKey) != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val listenersDisposable = Disposer.newDisposable(disposable)
|
val listenersDisposable = Disposer.newDisposable(disposable)
|
||||||
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
|
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
|
||||||
|
|
||||||
@ -343,13 +367,13 @@ internal object VimListenerManager {
|
|||||||
private object VimDocumentListener : DocumentListener {
|
private object VimDocumentListener : DocumentListener {
|
||||||
override fun beforeDocumentChange(event: DocumentEvent) {
|
override fun beforeDocumentChange(event: DocumentEvent) {
|
||||||
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
|
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
|
||||||
SearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
|
IjVimSearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
|
||||||
IjVimRedrawService.RedrawListener.beforeDocumentChange(event)
|
IjVimRedrawService.RedrawListener.beforeDocumentChange(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun documentChanged(event: DocumentEvent) {
|
override fun documentChanged(event: DocumentEvent) {
|
||||||
VimMarkServiceImpl.MarkUpdater.documentChanged(event)
|
VimMarkServiceImpl.MarkUpdater.documentChanged(event)
|
||||||
SearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
|
IjVimSearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
|
||||||
IjVimRedrawService.RedrawListener.documentChanged(event)
|
IjVimRedrawService.RedrawListener.documentChanged(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -473,6 +497,9 @@ internal object VimListenerManager {
|
|||||||
(it.fileEditor as? TextEditor)?.editor?.let { editor ->
|
(it.fileEditor as? TextEditor)?.editor?.let { editor ->
|
||||||
if (vimDisabled(editor)) return@let
|
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 openingEditor = editor.removeUserData(openingEditorKey)
|
||||||
val owningEditorWindow = getOwningEditorWindow(editor)
|
val owningEditorWindow = getOwningEditorWindow(editor)
|
||||||
val isInSameSplit = owningEditorWindow == openingEditor?.owningEditorWindow
|
val isInSameSplit = owningEditorWindow == openingEditor?.owningEditorWindow
|
||||||
@ -716,13 +743,13 @@ internal object VimListenerManager {
|
|||||||
logger.debug("Mouse clicked")
|
logger.debug("Mouse clicked")
|
||||||
|
|
||||||
if (event.area == EditorMouseEventArea.EDITING_AREA) {
|
if (event.area == EditorMouseEventArea.EDITING_AREA) {
|
||||||
VimPlugin.getMotion()
|
|
||||||
val editor = event.editor
|
val editor = event.editor
|
||||||
if (ExEntryPanel.getInstance().isActive) {
|
val commandLine = injector.commandLine.getActiveCommandLine()
|
||||||
VimPlugin.getProcess().cancelExEntry(editor.vim, false)
|
if (commandLine != null) {
|
||||||
|
injector.processGroup.cancelExEntry(editor.vim, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
ExOutputModel.getInstance(editor).clear()
|
ExOutputModel.tryGetInstance(editor)?.close()
|
||||||
|
|
||||||
val caretModel = editor.caretModel
|
val caretModel = editor.caretModel
|
||||||
if (editor.vim.mode.selectionType != null) {
|
if (editor.vim.mode.selectionType != null) {
|
||||||
@ -748,12 +775,12 @@ internal object VimListenerManager {
|
|||||||
event.area != EditorMouseEventArea.FOLDING_OUTLINE_AREA &&
|
event.area != EditorMouseEventArea.FOLDING_OUTLINE_AREA &&
|
||||||
event.mouseEvent.button != MouseEvent.BUTTON3
|
event.mouseEvent.button != MouseEvent.BUTTON3
|
||||||
) {
|
) {
|
||||||
VimPlugin.getMotion()
|
val commandLine = injector.commandLine.getActiveCommandLine()
|
||||||
if (ExEntryPanel.getInstance().isActive) {
|
if (commandLine != null) {
|
||||||
VimPlugin.getProcess().cancelExEntry(event.editor.vim, false)
|
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.BookmarksManager
|
||||||
import com.intellij.ide.bookmark.LineBookmark
|
import com.intellij.ide.bookmark.LineBookmark
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.vfs.VirtualFileManager
|
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
internal class IntellijMark(bookmark: LineBookmark, override val col: Int, project: Project?) : Mark {
|
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
|
get() = getMark()?.line ?: 0
|
||||||
override val filepath: String
|
override val filepath: String
|
||||||
get() = getMark()?.file?.path ?: ""
|
get() = getMark()?.file?.path ?: ""
|
||||||
override val protocol: String?
|
override val protocol: String
|
||||||
get() = getMark()?.file?.let { VirtualFileManager.extractProtocol(it.url) } ?: ""
|
get() = getMark()?.file?.fileSystem?.protocol ?: ""
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
val mark = getMark() ?: return
|
val mark = getMark() ?: return
|
||||||
|
@ -24,8 +24,8 @@ internal val runFromVimKey = Key.create<Boolean>("RunFromVim")
|
|||||||
internal val DataContext.actionStartedFromVim: Boolean
|
internal val DataContext.actionStartedFromVim: Boolean
|
||||||
get() = (this as? UserDataHolder)?.getUserData(runFromVimKey) ?: false
|
get() = (this as? UserDataHolder)?.getUserData(runFromVimKey) ?: false
|
||||||
|
|
||||||
public val DataContext.vim: ExecutionContext
|
val DataContext.vim: ExecutionContext
|
||||||
get() = IjEditorExecutionContext(this)
|
get() = IjEditorExecutionContext(this)
|
||||||
|
|
||||||
public val ExecutionContext.ij: DataContext
|
val ExecutionContext.ij: DataContext
|
||||||
get() = (this as IjEditorExecutionContext).context
|
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.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContextManagerBase
|
import com.maddyhome.idea.vim.api.ExecutionContextManagerBase
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.helper.EditorDataContext
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
internal class IjExecutionContextManager : ExecutionContextManagerBase() {
|
internal class IjExecutionContextManager : ExecutionContextManagerBase() {
|
||||||
|
@ -16,8 +16,8 @@ internal class IjLiveRange(val marker: RangeMarker) : LiveRange {
|
|||||||
get() = marker.startOffset
|
get() = marker.startOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
public val RangeMarker.vim: LiveRange
|
val RangeMarker.vim: LiveRange
|
||||||
get() = IjLiveRange(this)
|
get() = IjLiveRange(this)
|
||||||
|
|
||||||
public val LiveRange.ij: RangeMarker
|
val LiveRange.ij: RangeMarker
|
||||||
get() = (this as IjLiveRange).marker
|
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)
|
get() = IjNativeAction(this)
|
||||||
|
|
||||||
public class IjNativeAction(override val action: AnAction) : NativeAction {
|
class IjNativeAction(override val action: AnAction) : NativeAction {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "IjNativeAction(action=$action)"
|
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.LogicalPosition
|
||||||
import com.intellij.openapi.editor.VisualPosition
|
import com.intellij.openapi.editor.VisualPosition
|
||||||
import com.maddyhome.idea.vim.api.BufferPosition
|
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.ImmutableVimCaret
|
||||||
import com.maddyhome.idea.vim.api.LocalMarkStorage
|
import com.maddyhome.idea.vim.api.LocalMarkStorage
|
||||||
import com.maddyhome.idea.vim.api.SelectionInfo
|
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.VimCaretBase
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimVisualPosition
|
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.common.LiveRange
|
||||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
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.lastSelectionInfo
|
||||||
import com.maddyhome.idea.vim.helper.markStorage
|
import com.maddyhome.idea.vim.helper.markStorage
|
||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
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.resetVimLastColumn
|
||||||
import com.maddyhome.idea.vim.helper.vimInsertStart
|
import com.maddyhome.idea.vim.helper.vimInsertStart
|
||||||
import com.maddyhome.idea.vim.helper.vimLastColumn
|
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() {
|
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
|
override val markStorage: LocalMarkStorage
|
||||||
get() {
|
get() {
|
||||||
var storage = this.caret.markStorage
|
var storage = this.caret.markStorage
|
||||||
@ -169,15 +158,44 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
|
|||||||
caret.removeSelection()
|
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 equals(other: Any?): Boolean = this.caret == (other as? IjVimCaret)?.caret
|
||||||
|
|
||||||
override fun hashCode(): Int = this.caret.hashCode()
|
override fun hashCode(): Int = this.caret.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
public val VimCaret.ij: Caret
|
val VimCaret.ij: Caret
|
||||||
get() = (this as IjVimCaret).caret
|
get() = (this as IjVimCaret).caret
|
||||||
public val ImmutableVimCaret.ij: Caret
|
val ImmutableVimCaret.ij: Caret
|
||||||
get() = (this as IjVimCaret).caret
|
get() = (this as IjVimCaret).caret
|
||||||
|
|
||||||
public val Caret.vim: VimCaret
|
val Caret.vim: VimCaret
|
||||||
get() = IjVimCaret(this)
|
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.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.IndentConfig
|
import com.maddyhome.idea.vim.common.IndentConfig
|
||||||
import com.maddyhome.idea.vim.common.LiveRange
|
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.common.TextRange
|
||||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemBlockSelectionSilently
|
import com.maddyhome.idea.vim.group.visual.vimSetSystemBlockSelectionSilently
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
@ -117,7 +118,10 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
|||||||
return atPosition
|
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)
|
editor.document.insertString(atPosition, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,21 +151,40 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
|||||||
return editor.caretModel.allCarets.map { IjVimCaret(it) }
|
return editor.caretModel.allCarets.map { IjVimCaret(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var isFirstCaret = false
|
||||||
|
override var isReversingCarets = false
|
||||||
|
|
||||||
@Suppress("ideavimRunForEachCaret")
|
@Suppress("ideavimRunForEachCaret")
|
||||||
override fun forEachCaret(action: (VimCaret) -> Unit) {
|
override fun forEachCaret(action: (VimCaret) -> Unit) {
|
||||||
if (editor.vim.inBlockSelection) {
|
if (editor.vim.inBlockSelection) {
|
||||||
action(IjVimCaret(editor.caretModel.primaryCaret))
|
action(IjVimCaret(editor.caretModel.primaryCaret))
|
||||||
} else {
|
} else {
|
||||||
editor.caretModel.runForEachCaret({
|
isFirstCaret = true
|
||||||
if (it.isValid) {
|
try {
|
||||||
action(IjVimCaret(it))
|
editor.caretModel.runForEachCaret({
|
||||||
}
|
if (it.isValid) {
|
||||||
}, false)
|
action(IjVimCaret(it))
|
||||||
|
isFirstCaret = false
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
} finally {
|
||||||
|
isFirstCaret = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
|
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 {
|
override fun isInForEachCaretScope(): Boolean {
|
||||||
@ -256,7 +279,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
|||||||
val vf = EditorHelper.getVirtualFile(editor)
|
val vf = EditorHelper.getVirtualFile(editor)
|
||||||
return vf?.let {
|
return vf?.let {
|
||||||
object : VirtualFile {
|
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)
|
get() = IjVimEditor(this)
|
||||||
public val VimEditor.ij: Editor
|
val VimEditor.ij: Editor
|
||||||
get() = (this as IjVimEditor).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)
|
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.Editor
|
||||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
|
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
|
||||||
import com.maddyhome.idea.vim.api.EngineEditorHelper
|
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.ExecutionContextManager
|
||||||
import com.maddyhome.idea.vim.api.LocalOptionInitialisationScenario
|
import com.maddyhome.idea.vim.api.LocalOptionInitialisationScenario
|
||||||
import com.maddyhome.idea.vim.api.NativeActionManager
|
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.VimProcessGroup
|
||||||
import com.maddyhome.idea.vim.api.VimPsiService
|
import com.maddyhome.idea.vim.api.VimPsiService
|
||||||
import com.maddyhome.idea.vim.api.VimRedrawService
|
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.VimRegexpService
|
||||||
import com.maddyhome.idea.vim.api.VimScrollGroup
|
import com.maddyhome.idea.vim.api.VimScrollGroup
|
||||||
import com.maddyhome.idea.vim.api.VimSearchGroup
|
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.IjVimPsiService
|
||||||
import com.maddyhome.idea.vim.group.MacroGroup
|
import com.maddyhome.idea.vim.group.MacroGroup
|
||||||
import com.maddyhome.idea.vim.group.MotionGroup
|
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.TabService
|
||||||
import com.maddyhome.idea.vim.group.VimWindowGroup
|
import com.maddyhome.idea.vim.group.VimWindowGroup
|
||||||
import com.maddyhome.idea.vim.group.WindowGroup
|
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.ui.VimRcFileState
|
||||||
import com.maddyhome.idea.vim.undo.VimUndoRedo
|
import com.maddyhome.idea.vim.undo.VimUndoRedo
|
||||||
import com.maddyhome.idea.vim.vimscript.Executor
|
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.vimscript.services.VariableService
|
||||||
import com.maddyhome.idea.vim.yank.VimYankGroup
|
import com.maddyhome.idea.vim.yank.VimYankGroup
|
||||||
import com.maddyhome.idea.vim.yank.YankGroupBase
|
import com.maddyhome.idea.vim.yank.YankGroupBase
|
||||||
@ -107,8 +105,6 @@ internal class IjVimInjector : VimInjectorBase() {
|
|||||||
|
|
||||||
override val actionExecutor: VimActionExecutor
|
override val actionExecutor: VimActionExecutor
|
||||||
get() = service<IjActionExecutor>()
|
get() = service<IjActionExecutor>()
|
||||||
override val exEntryPanel: ExEntryPanel
|
|
||||||
get() = service<IjExEntryPanel>()
|
|
||||||
override val exOutputPanel: VimExOutputPanelService
|
override val exOutputPanel: VimExOutputPanelService
|
||||||
get() = object : VimExOutputPanelService {
|
get() = object : VimExOutputPanelService {
|
||||||
override fun getPanel(editor: VimEditor): VimExOutputPanel {
|
override fun getPanel(editor: VimEditor): VimExOutputPanel {
|
||||||
@ -122,7 +118,7 @@ internal class IjVimInjector : VimInjectorBase() {
|
|||||||
override val tabService: TabService
|
override val tabService: TabService
|
||||||
get() = service()
|
get() = service()
|
||||||
override val regexpService: VimRegexpService
|
override val regexpService: VimRegexpService
|
||||||
get() = PatternService
|
get() = VimRegexServiceBase()
|
||||||
override val clipboardManager: VimClipboardManager
|
override val clipboardManager: VimClipboardManager
|
||||||
get() = service<IjClipboardManager>()
|
get() = service<IjClipboardManager>()
|
||||||
override val searchHelper: VimSearchHelper
|
override val searchHelper: VimSearchHelper
|
||||||
@ -136,7 +132,7 @@ internal class IjVimInjector : VimInjectorBase() {
|
|||||||
override val templateManager: VimTemplateManager
|
override val templateManager: VimTemplateManager
|
||||||
get() = service<IjTemplateManager>()
|
get() = service<IjTemplateManager>()
|
||||||
override val searchGroup: VimSearchGroup
|
override val searchGroup: VimSearchGroup
|
||||||
get() = service<SearchGroup>()
|
get() = service<IjVimSearchGroup>()
|
||||||
override val put: VimPut
|
override val put: VimPut
|
||||||
get() = service<PutGroup>()
|
get() = service<PutGroup>()
|
||||||
override val window: VimWindowGroup
|
override val window: VimWindowGroup
|
||||||
@ -236,10 +232,10 @@ internal class IjVimInjector : VimInjectorBase() {
|
|||||||
/**
|
/**
|
||||||
* Convenience function to get the IntelliJ implementation specific global option accessor
|
* 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
|
* 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)
|
(this.optionGroup as IjVimOptionGroup).getEffectiveIjOptions(editor)
|
||||||
|
@ -9,8 +9,15 @@
|
|||||||
package com.maddyhome.idea.vim.newapi
|
package com.maddyhome.idea.vim.newapi
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
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.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.editor.markup.RangeHighlighter
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||||
import com.intellij.openapi.util.Ref
|
import com.intellij.openapi.util.Ref
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
@ -20,6 +27,9 @@ import com.maddyhome.idea.vim.api.VimEditor
|
|||||||
import com.maddyhome.idea.vim.api.VimSearchGroupBase
|
import com.maddyhome.idea.vim.api.VimSearchGroupBase
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.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.MessageHelper
|
||||||
import com.maddyhome.idea.vim.helper.TestInputModel.Companion.getInstance
|
import com.maddyhome.idea.vim.helper.TestInputModel.Companion.getInstance
|
||||||
import com.maddyhome.idea.vim.helper.addSubstitutionConfirmationHighlight
|
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.isCloseKeyStroke
|
||||||
import com.maddyhome.idea.vim.helper.shouldIgnoreCase
|
import com.maddyhome.idea.vim.helper.shouldIgnoreCase
|
||||||
import com.maddyhome.idea.vim.helper.updateSearchHighlights
|
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.options.GlobalOptionChangeListener
|
||||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||||
import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler
|
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 org.jetbrains.annotations.TestOnly
|
||||||
import javax.swing.KeyStroke
|
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 {
|
init {
|
||||||
// We use the global option listener instead of the effective listener that gets called for each affected editor
|
// 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 {
|
} else {
|
||||||
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
|
// 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 =
|
val exEntryPanel = injector.commandLine.createWithoutShortcuts(
|
||||||
com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstanceWithoutShortcuts()
|
editor,
|
||||||
exEntryPanel.activate(
|
context,
|
||||||
editor.ij,
|
|
||||||
(context as IjEditorExecutionContext).context,
|
|
||||||
MessageHelper.message("replace.with.0", match),
|
MessageHelper.message("replace.with.0", match),
|
||||||
"",
|
"",
|
||||||
1
|
|
||||||
)
|
)
|
||||||
caret.moveToOffset(startOffset)
|
caret.moveToOffset(startOffset)
|
||||||
ModalEntry.activate(editor, keyStrokeProcessor)
|
ModalEntry.activate(editor, keyStrokeProcessor)
|
||||||
exEntryPanel.deactivate(true, false)
|
exEntryPanel.deactivate(refocusOwningEditor = true, resetCaret = false)
|
||||||
}
|
}
|
||||||
return result.get()
|
return result.get()
|
||||||
}
|
}
|
||||||
@ -178,6 +195,104 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
|
|||||||
updateSearchHighlights(false)
|
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) :
|
private class IjSearchHighlight(private val editor: Editor, private val highlighter: RangeHighlighter) :
|
||||||
SearchHighlight() {
|
SearchHighlight() {
|
||||||
|
|
||||||
@ -185,4 +300,60 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
|
|||||||
editor.markupModel.removeHighlighter(highlighter)
|
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.ImmutableVimCaret
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimSearchHelperBase
|
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.PsiHelper
|
||||||
import com.maddyhome.idea.vim.helper.SearchHelper
|
import com.maddyhome.idea.vim.helper.findMisspelledWords
|
||||||
import com.maddyhome.idea.vim.helper.SearchOptions
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntComparator
|
import it.unimi.dsi.fastutil.ints.IntComparator
|
||||||
import it.unimi.dsi.fastutil.ints.IntComparators
|
import it.unimi.dsi.fastutil.ints.IntComparators
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
internal class IjVimSearchHelper : VimSearchHelperBase() {
|
internal class IjVimSearchHelper : VimSearchHelperBase() {
|
||||||
@ -38,28 +34,6 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
|||||||
return PsiHelper.findMethodStart(editor.ij, caret.ij.offset, count)
|
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 {
|
override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||||
val startOffset: Int
|
val startOffset: Int
|
||||||
val endOffset: Int
|
val endOffset: Int
|
||||||
@ -80,6 +54,6 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO add it to PsiService
|
// 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.api.injector
|
||||||
import com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory
|
import com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory
|
||||||
|
|
||||||
public class WidgetState : ApplicationUsagesCollector() {
|
class WidgetState : ApplicationUsagesCollector() {
|
||||||
override fun getGroup(): EventLogGroup = GROUP
|
override fun getGroup(): EventLogGroup = GROUP
|
||||||
|
|
||||||
override fun getMetrics(): Set<MetricEvent> {
|
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 GROUP = EventLogGroup("vim.widget", 1, "FUS")
|
||||||
|
|
||||||
private val IS_MODE_WIDGET_SHOWN = EventFields.Boolean("is-mode-widget-shown")
|
private val IS_MODE_WIDGET_SHOWN = EventFields.Boolean("is-mode-widget-shown")
|
||||||
|
@ -135,6 +135,9 @@ public class ExOutputPanel extends JPanel {
|
|||||||
myText.setBorder(null);
|
myText.setBorder(null);
|
||||||
myScrollPane.setBorder(null);
|
myScrollPane.setBorder(null);
|
||||||
myLabel.setForeground(myText.getForeground());
|
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.setText(data);
|
||||||
myText.setFont(UiHelper.selectFont(data));
|
myText.setFont(UiHelper.selectEditorFont(myEditor, data));
|
||||||
myText.setCaretPosition(0);
|
myText.setCaretPosition(0);
|
||||||
if (!data.isEmpty()) {
|
if (!data.isEmpty()) {
|
||||||
activate();
|
activate();
|
||||||
@ -213,8 +216,8 @@ public class ExOutputPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setFontForElements() {
|
private void setFontForElements() {
|
||||||
myText.setFont(UiHelper.selectFont(myText.getText()));
|
myText.setFont(UiHelper.selectEditorFont(myEditor, myText.getText()));
|
||||||
myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
|
myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scrollLine() {
|
private void scrollLine() {
|
||||||
@ -242,7 +245,7 @@ public class ExOutputPanel extends JPanel {
|
|||||||
|
|
||||||
private void badKey() {
|
private void badKey() {
|
||||||
myLabel.setText(MessageHelper.message("more.ret.line.space.page.d.half.page.q.quit"));
|
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) {
|
private void scrollOffset(int more) {
|
||||||
@ -258,12 +261,18 @@ public class ExOutputPanel extends JPanel {
|
|||||||
else {
|
else {
|
||||||
myLabel.setText(MessageHelper.message("ex.output.panel.more"));
|
myLabel.setText(MessageHelper.message("ex.output.panel.more"));
|
||||||
}
|
}
|
||||||
myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
|
myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void positionPanel() {
|
private void positionPanel() {
|
||||||
final JComponent contentComponent = myEditor.getContentComponent();
|
final JComponent contentComponent = myEditor.getContentComponent();
|
||||||
Container scroll = SwingUtilities.getAncestorOfClass(JScrollPane.class, contentComponent);
|
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());
|
setSize(scroll.getSize());
|
||||||
|
|
||||||
myLineHeight = myText.getFontMetrics(myText.getFont()).getHeight();
|
myLineHeight = myText.getFontMetrics(myText.getFont()).getHeight();
|
||||||
@ -277,8 +286,7 @@ public class ExOutputPanel extends JPanel {
|
|||||||
Rectangle bounds = scroll.getBounds();
|
Rectangle bounds = scroll.getBounds();
|
||||||
bounds.translate(0, scroll.getHeight() - height);
|
bounds.translate(0, scroll.getHeight() - height);
|
||||||
bounds.height = height;
|
bounds.height = height;
|
||||||
Point pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.getLocation(),
|
Point pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.getLocation(), rootPane.getGlassPane());
|
||||||
SwingUtilities.getRootPane(contentComponent).getGlassPane());
|
|
||||||
bounds.setLocation(pos);
|
bounds.setLocation(pos);
|
||||||
setBounds(bounds);
|
setBounds(bounds);
|
||||||
|
|
||||||
|
@ -24,11 +24,11 @@ import javax.swing.KeyStroke
|
|||||||
/**
|
/**
|
||||||
* @author dhleong
|
* @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
|
// Firstly we pull the unfinished keys of the current mapping
|
||||||
val mappingStack = KeyHandler.getInstance().keyStack
|
val mappingStack = KeyHandler.getInstance().keyStack
|
||||||
LOG.trace("Dumping key stack:")
|
LOG.trace("Dumping key stack:")
|
||||||
|
@ -19,6 +19,7 @@ import com.intellij.openapi.wm.StatusBarWidgetFactory
|
|||||||
import com.intellij.openapi.wm.WindowManager
|
import com.intellij.openapi.wm.WindowManager
|
||||||
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
|
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
|
||||||
import com.intellij.util.Consumer
|
import com.intellij.util.Consumer
|
||||||
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
@ -26,9 +27,7 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.common.EditorListener
|
import com.maddyhome.idea.vim.common.EditorListener
|
||||||
import com.maddyhome.idea.vim.helper.EngineStringHelper
|
import com.maddyhome.idea.vim.helper.EngineStringHelper
|
||||||
import com.maddyhome.idea.vim.helper.VimNlsSafe
|
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.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
|
||||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
|
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
@ -61,8 +60,8 @@ internal object ShowCmd {
|
|||||||
fun getFullText(editor: Editor?): String {
|
fun getFullText(editor: Editor?): String {
|
||||||
if (!injector.globalOptions().showcmd || editor == null || editor.isDisposed) return ""
|
if (!injector.globalOptions().showcmd || editor == null || editor.isDisposed) return ""
|
||||||
|
|
||||||
val editorState = editor.vim.vimStateMachine
|
val keyState = KeyHandler.getInstance().keyHandlerState
|
||||||
return EngineStringHelper.toPrintableCharacters(editorState.commandBuilder.keys + editorState.mappingState.keys)
|
return EngineStringHelper.toPrintableCharacters(keyState.commandBuilder.keys + keyState.mappingState.keys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,11 +86,20 @@ internal class VimEmulationConfigurable : Configurable {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
val shortcutConflictsTable = VimShortcutConflictsTable(model)
|
val shortcutConflictsTable = VimShortcutConflictsTable(model)
|
||||||
|
val rowsToBeChangedOnToggleAllHandlers = getRowsToBeToggled()
|
||||||
|
|
||||||
layout = BorderLayout()
|
layout = BorderLayout()
|
||||||
val decorator = ToolbarDecorator.createDecorator(shortcutConflictsTable)
|
val decorator = ToolbarDecorator.createDecorator(shortcutConflictsTable)
|
||||||
decorator.setToolbarPosition(ActionToolbarPosition.RIGHT)
|
decorator.setToolbarPosition(ActionToolbarPosition.RIGHT)
|
||||||
decorator.addExtraAction(CopyForRcAction(model))
|
decorator.addExtraAction(CopyForRcAction(model))
|
||||||
decorator.addExtraAction(ResetHandlersAction(model, shortcutConflictsTable))
|
decorator.addExtraAction(ResetHandlersAction(model, shortcutConflictsTable))
|
||||||
|
decorator.addExtraAction(
|
||||||
|
ToggleAllHandlersAction(
|
||||||
|
model,
|
||||||
|
shortcutConflictsTable,
|
||||||
|
rowsToBeChangedOnToggleAllHandlers
|
||||||
|
)
|
||||||
|
)
|
||||||
val scrollPane = decorator.createPanel()
|
val scrollPane = decorator.createPanel()
|
||||||
scrollPane.border = LineBorder(JBColor.border())
|
scrollPane.border = LineBorder(JBColor.border())
|
||||||
val conflictsPanel = JPanel(BorderLayout())
|
val conflictsPanel = JPanel(BorderLayout())
|
||||||
@ -101,6 +110,19 @@ internal class VimEmulationConfigurable : Configurable {
|
|||||||
addHelpLine(model)
|
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) {
|
fun addHelpLine(model: VimShortcutConflictsTable.Model) {
|
||||||
val firstPerMode = ContainerUtil.find(model.rows) { row: VimShortcutConflictsTable.Row ->
|
val firstPerMode = ContainerUtil.find(model.rows) { row: VimShortcutConflictsTable.Row ->
|
||||||
val owner: ShortcutOwnerInfo = row.owner
|
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(
|
class ResetHandlersAction(
|
||||||
private val myModel: VimShortcutConflictsTable.Model,
|
private val myModel: VimShortcutConflictsTable.Model,
|
||||||
private val myTable: VimShortcutConflictsTable,
|
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.diagnostic.logger
|
||||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
|
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
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.LocalOptionInitialisationScenario
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
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
|
// * The key handler routines get the chance to clean up and reset state
|
||||||
val entry = ExEntryPanel.getInstance().entry
|
val entry = ExEntryPanel.getInstance().entry
|
||||||
val keyHandler = KeyHandler.getInstance()
|
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 {
|
companion object {
|
||||||
@ -198,7 +197,7 @@ internal class DeletePreviousWordAction : TextAction(DefaultEditorKit.deletePrev
|
|||||||
target.saveLastEntry()
|
target.saveLastEntry()
|
||||||
val doc = target.document
|
val doc = target.document
|
||||||
val caret = target.caret
|
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
|
// 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
|
// 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
|
// 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
|
// share the command line entry actions between IdeaVim implementations
|
||||||
val editor = TextComponentEditorImpl(project, target).vim
|
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)
|
val offset = injector.searchHelper.findNextWord(editor, caret.dot, -1, bigWord = false, spaceWords = false)
|
||||||
if (logger.isDebugEnabled) logger.debug("offset=$offset")
|
if (logger.isDebugEnabled) logger.debug("offset=$offset")
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
package com.maddyhome.idea.vim.ui.ex;
|
package com.maddyhome.idea.vim.ui.ex;
|
||||||
|
|
||||||
import com.intellij.openapi.util.SystemInfo;
|
import com.intellij.openapi.util.SystemInfo;
|
||||||
|
import com.maddyhome.idea.vim.api.VimCommandLine;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.swing.text.*;
|
import javax.swing.text.*;
|
||||||
@ -18,6 +19,8 @@ import java.text.AttributedCharacterIterator;
|
|||||||
import java.text.AttributedString;
|
import java.text.AttributedString;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This document provides insert/overwrite mode
|
* This document provides insert/overwrite mode
|
||||||
* Note that PlainDocument will remove CRs from text for single line text fields
|
* 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
|
* Toggles the insert/overwrite state
|
||||||
*/
|
*/
|
||||||
void toggleInsertReplace() {
|
void toggleInsertReplace() {
|
||||||
|
VimCommandLine commandLine = injector.getCommandLine().getActiveCommandLine();
|
||||||
|
if (commandLine != null) {
|
||||||
|
commandLine.toggleReplaceMode();
|
||||||
|
}
|
||||||
overwrite = !overwrite;
|
overwrite = !overwrite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ internal object ExEditorKit : DefaultEditorKit() {
|
|||||||
val entry = ExEntryPanel.getInstance().entry
|
val entry = ExEntryPanel.getInstance().entry
|
||||||
val editor = entry.editor
|
val editor = entry.editor
|
||||||
val keyHandler = KeyHandler.getInstance()
|
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 {
|
} else {
|
||||||
val event = ActionEvent(e.source, e.id, c.toString(), e.getWhen(), e.modifiers)
|
val event = ActionEvent(e.source, e.id, c.toString(), e.getWhen(), e.modifiers)
|
||||||
super.actionPerformed(event)
|
super.actionPerformed(event)
|
||||||
|
@ -16,6 +16,7 @@ import com.intellij.openapi.diagnostic.Logger;
|
|||||||
import com.intellij.openapi.editor.Caret;
|
import com.intellij.openapi.editor.Caret;
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.openapi.editor.ScrollingModel;
|
import com.intellij.openapi.editor.ScrollingModel;
|
||||||
|
import com.intellij.openapi.wm.IdeFocusManager;
|
||||||
import com.intellij.ui.DocumentAdapter;
|
import com.intellij.ui.DocumentAdapter;
|
||||||
import com.intellij.util.IJSwingUtilities;
|
import com.intellij.util.IJSwingUtilities;
|
||||||
import com.maddyhome.idea.vim.EventFacade;
|
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.helper.UiHelper;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||||
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.ui.ExPanelBorder;
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.Command;
|
import com.maddyhome.idea.vim.vimscript.model.commands.Command;
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand;
|
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.ComponentAdapter;
|
||||||
import java.awt.event.ComponentEvent;
|
import java.awt.event.ComponentEvent;
|
||||||
import java.awt.event.ComponentListener;
|
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.globalOptions;
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
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 class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||||
public static ExEntryPanel instance;
|
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) {
|
private ExEntryPanel(boolean enableShortcuts) {
|
||||||
label = new JLabel(" ");
|
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
|
* 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 context The data context
|
||||||
* @param label The label for the ex entry (i.e. :, /, or ?)
|
* @param label The label for the ex entry (i.e. :, /, or ?)
|
||||||
* @param initText The initial text for the entry
|
* @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");
|
logger.info("Activate ex entry panel");
|
||||||
this.label.setText(label);
|
this.label.setText(label);
|
||||||
this.label.setFont(UiHelper.selectFont(label));
|
this.label.setFont(UiHelper.selectEditorFont(editor, label));
|
||||||
this.count = count;
|
|
||||||
entry.reset();
|
entry.reset();
|
||||||
entry.setEditor(editor, context);
|
entry.setEditor(editor, context);
|
||||||
entry.setText(initText);
|
entry.setText(initText);
|
||||||
entry.setFont(UiHelper.selectFont(initText));
|
entry.setFont(UiHelper.selectEditorFont(editor, initText));
|
||||||
entry.setType(label);
|
entry.setType(label);
|
||||||
parent = editor.getContentComponent();
|
parent = editor.getContentComponent();
|
||||||
|
|
||||||
@ -191,7 +203,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
public void deactivate(boolean refocusOwningEditor, boolean resetCaret) {
|
public void deactivate(boolean refocusOwningEditor, boolean resetCaret) {
|
||||||
logger.info("Deactivate ex entry panel");
|
logger.info("Deactivate ex entry panel");
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
active = false;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
entry.getDocument().removeDocumentListener(fontListener);
|
entry.getDocument().removeDocumentListener(fontListener);
|
||||||
@ -215,6 +226,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
VimPlugin.getSearch().resetIncsearchHighlights();
|
VimPlugin.getSearch().resetIncsearchHighlights();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isReplaceMode = false;
|
||||||
entry.deactivate();
|
entry.deactivate();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@ -234,6 +246,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
|
|
||||||
parent = null;
|
parent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have this in the end, because `entry.deactivate()` communicates with active panel during deactivation
|
||||||
|
active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
@ -257,8 +272,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
private final @NotNull DocumentListener fontListener = new DocumentAdapter() {
|
private final @NotNull DocumentListener fontListener = new DocumentAdapter() {
|
||||||
@Override
|
@Override
|
||||||
protected void textChanged(@NotNull DocumentEvent e) {
|
protected void textChanged(@NotNull DocumentEvent e) {
|
||||||
String text = entry.getActualText();
|
String text = entry.getText();
|
||||||
Font newFont = UiHelper.selectFont(text);
|
Font newFont = UiHelper.selectEditorFont(getEditor(), text);
|
||||||
if (newFont != entry.getFont()) {
|
if (newFont != entry.getFont()) {
|
||||||
entry.setFont(newFont);
|
entry.setFont(newFont);
|
||||||
}
|
}
|
||||||
@ -276,7 +291,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
boolean searchCommand = false;
|
boolean searchCommand = false;
|
||||||
LineRange searchRange = null;
|
LineRange searchRange = null;
|
||||||
char separator = labelText.charAt(0);
|
char separator = labelText.charAt(0);
|
||||||
String searchText = entry.getActualText();
|
String searchText = getActualText();
|
||||||
if (labelText.equals(":")) {
|
if (labelText.equals(":")) {
|
||||||
if (searchText.isEmpty()) return;
|
if (searchText.isEmpty()) return;
|
||||||
final Command command = getIncsearchCommand(searchText);
|
final Command command = getIncsearchCommand(searchText);
|
||||||
@ -313,10 +328,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
|
|
||||||
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
|
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
|
||||||
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
||||||
final String pattern;
|
int pattenEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
|
||||||
final CharPointer p = new CharPointer(searchText);
|
final String pattern = searchText.substring(0, pattenEnd);
|
||||||
final CharPointer end = RegExp.skip_regexp(new CharPointer(searchText), separator, true);
|
|
||||||
pattern = p.substring(end.pointer() - p.pointer());
|
|
||||||
|
|
||||||
VimPlugin.getEditor().closeEditorSearchSession(editor);
|
VimPlugin.getEditor().closeEditorSearchSession(editor);
|
||||||
final int matchOffset =
|
final int matchOffset =
|
||||||
@ -365,13 +378,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
return label.getText();
|
return label.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Gets the count given during activation
|
public void toggleReplaceMode() {
|
||||||
*
|
isReplaceMode = !isReplaceMode;
|
||||||
* @return The count
|
|
||||||
*/
|
|
||||||
public int getCount() {
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -383,19 +392,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
return active;
|
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
|
@Override
|
||||||
public @NotNull String getText() {
|
public @NotNull String getVisibleText() {
|
||||||
return entry.getActualText();
|
return entry.getText();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setText(@NotNull String s) {
|
|
||||||
entry.setText(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull ExTextField getEntry() {
|
public @NotNull ExTextField getEntry() {
|
||||||
@ -427,6 +426,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
|
|
||||||
// Label background is automatically picked up
|
// Label background is automatically picked up
|
||||||
label.setForeground(entry.getForeground());
|
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() {
|
private void setFontForElements() {
|
||||||
label.setFont(UiHelper.selectFont(label.getText()));
|
label.setFont(UiHelper.selectEditorFont(getEditor(), label.getText()));
|
||||||
entry.setFont(UiHelper.selectFont(entry.getActualText()));
|
entry.setFont(UiHelper.selectEditorFont(getEditor(), getVisibleText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void positionPanel() {
|
private void positionPanel() {
|
||||||
@ -469,7 +471,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean active;
|
private boolean active;
|
||||||
private int count;
|
|
||||||
|
|
||||||
// UI stuff
|
// UI stuff
|
||||||
private @Nullable JComponent parent;
|
private @Nullable JComponent parent;
|
||||||
@ -499,6 +500,41 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
return (VimCommandLineCaret) entry.getCaret();
|
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 {
|
public static class LafListener implements LafManagerListener {
|
||||||
@Override
|
@Override
|
||||||
public void lookAndFeelChanged(@NotNull LafManager source) {
|
public void lookAndFeelChanged(@NotNull LafManager source) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user