1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +02:00

Compare commits

..

17 Commits

Author SHA1 Message Date
57ddf2083e Set plugin version to chylex-35 2024-06-04 13:26:12 +02:00
aad2287433 Revert "Factor disposable objects on editor opening"
This reverts commit 1fa78935
2024-06-04 13:26:11 +02:00
a348428422 Fix(VIM-3364): Exception with mapped Generate action 2024-06-04 13:26:11 +02:00
c4c66c194a Apply scrolloff after executing native IDEA actions 2024-06-04 13:26:11 +02:00
e065783486 Stay on same line after reindenting 2024-06-04 13:26:11 +02:00
dae5e3a8fd Update search register when using f/t 2024-06-04 13:26:11 +02:00
bb3b67f611 Automatically add unambiguous imports after running a macro 2024-06-04 13:26:11 +02:00
bf69c8b4a6 Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-06-04 13:26:11 +02:00
49d1d2d270 Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-06-04 13:26:11 +02:00
96cdf1c26d Add support for count for visual and line motion surround 2024-06-04 13:20:29 +02:00
365bbce9a0 Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-06-04 13:20:29 +02:00
1f8a580b7f Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-06-04 13:20:27 +02:00
fa9529eaa5 Respect count with <Action> mappings 2024-06-04 13:18:11 +02:00
0b29a4704b Change matchit plugin to use HTML patterns in unrecognized files 2024-06-04 13:18:11 +02:00
92b9046d6c Reset insert mode when switching active editor 2024-06-04 13:18:11 +02:00
f5b120ac01 Remove update checker 2024-06-04 13:18:11 +02:00
70ea63c0ba Set custom plugin version 2024-06-04 13:18:11 +02:00
884 changed files with 16877 additions and 11451 deletions

View File

@@ -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 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
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 --no-configuration-cache updateYoutrackOnCommit run: ./gradlew updateYoutrackOnCommit
env: env:
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }} SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }} YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}

View File

@@ -18,16 +18,16 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 300 fetch-depth: 300
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
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: Run tests - name: Run tests
run: ./gradlew --no-configuration-cache integrationsTest run: ./gradlew integrationsTest
env: env:
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }} YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -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 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
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

View File

@@ -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 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
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 --no-configuration-cache updateMergedPr -PprId=${{ github.event.number }} run: ./gradlew updateMergedPr -PprId=${{ github.event.number }}
env: env:
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -16,14 +16,14 @@ jobs:
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
# - name: Setup Gradle - name: Setup Gradle
# uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log & gradle runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -37,7 +37,7 @@ jobs:
run: mv tests/ui-ij-tests/video build/reports run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -46,7 +46,7 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-ij-tests/build/reports tests/ui-ij-tests/build/reports
idea-sandbox-log sandbox-idea-log
# build-for-ui-test-linux: # build-for-ui-test-linux:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
@@ -78,4 +78,4 @@ jobs:
# with: # with:
# name: ui-test-fails-report-linux # name: ui-test-fails-report-linux
# path: | # path: |
# ui-test-example/build/reports # ui-test-example/build/reports

View File

@@ -19,14 +19,14 @@ jobs:
python-version: '3.10' python-version: '3.10'
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
# - name: Setup Gradle - name: Setup Gradle
# uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache :runIdeForUiTests -PideaType=PC > build/reports/idea.log & gradle :runIdeForUiTests -PideaType=PC > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -40,7 +40,7 @@ jobs:
run: mv tests/ui-py-tests/video build/reports run: mv tests/ui-py-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/PC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -49,4 +49,4 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-py-tests/build/reports tests/ui-py-tests/build/reports
idea-sandbox-log sandbox-idea-log

View File

@@ -16,14 +16,14 @@ jobs:
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
# - name: Setup Gradle - name: Setup Gradle
# uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache runIdeForUiTests > build/reports/idea.log & gradle runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -37,7 +37,7 @@ jobs:
run: mv tests/ui-ij-tests/video build/reports run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -46,7 +46,7 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-ij-tests/build/reports tests/ui-ij-tests/build/reports
idea-sandbox-log sandbox-idea-log
# build-for-ui-test-linux: # build-for-ui-test-linux:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
@@ -78,4 +78,4 @@ jobs:
# with: # with:
# name: ui-test-fails-report-linux # name: ui-test-fails-report-linux
# path: | # path: |
# ui-test-example/build/reports # ui-test-example/build/reports

View File

@@ -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 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
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 --no-configuration-cache updateAuthors --stacktrace run: ./gradlew updateAuthors --stacktrace
env: env:
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }} SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -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 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
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 --no-configuration-cache updateChangelog run: ./gradlew updateChangelog
env: env:
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }} SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
@@ -60,4 +60,4 @@ jobs:
# dependabot updates. See mergeDependatobPR.yml file. # dependabot updates. See mergeDependatobPR.yml file.
# However, it turned out that GitHub accepts pushes from the actions as a PR and requires checks, that are always # However, it turned out that GitHub accepts pushes from the actions as a PR and requires checks, that are always
# false for pushing from actions. # false for pushing from actions.
# This secret is created to implement the workaround described in https://stackoverflow.com/a/76135647/3124227 # This secret is created to implement the workaround described in https://stackoverflow.com/a/76135647/3124227

View File

@@ -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 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
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

7
.gitignore vendored
View File

@@ -1,6 +1,5 @@
*.swp *.swp
/.gradle/ /.gradle/
/.intellijPlatform/
/.idea/ /.idea/
!/.idea/scopes !/.idea/scopes
@@ -11,8 +10,6 @@
!/.idea/runConfigurations !/.idea/runConfigurations
!/.idea/codeStyles !/.idea/codeStyles
!/.idea/vcs.xml !/.idea/vcs.xml
!/.idea/misc.xml
!/.idea/.name
**/build/ **/build/
**/out/ **/out/
@@ -25,7 +22,7 @@
.teamcity/*.iml .teamcity/*.iml
# Generated by gradle task "generateGrammarSource" # Generated by gradle task "generateGrammarSource"
vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated src/main/java/com/maddyhome/idea/vim/vimscript/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
@@ -33,5 +30,3 @@ vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
# Created by github automation # Created by github automation
settings.xml settings.xml
.kotlin

1
.idea/.name generated
View File

@@ -1 +0,0 @@
IdeaVim

22
.idea/misc.xml generated
View File

@@ -1,22 +0,0 @@
<?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>

View File

@@ -12,7 +12,7 @@
<option name="taskNames"> <option name="taskNames">
<list> <list>
<option value="check" /> <option value="check" />
<option value="verifyPlugin" /> <option value="runPluginVerifier" />
</list> </list>
</option> </option>
<option name="vmOptions" value="" /> <option name="vmOptions" value="" />
@@ -20,7 +20,6 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled> <DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,25 +0,0 @@
<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>

View File

@@ -37,7 +37,7 @@ object Compatibility : IdeaVimBuildType({
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}eu.theblob42.idea.whichkey' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}eu.theblob42.idea.whichkey' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city
# Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city # Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city
# java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.copilot' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.copilot' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.dankinsoid.multicursor' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.dankinsoid.multicursor' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.joshestein.ideavim-quickscope' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.joshestein.ideavim-quickscope' [latest-IU] -team-city
""".trimIndent() """.trimIndent()

View File

@@ -22,7 +22,7 @@ object PluginVerifier : IdeaVimBuildType({
steps { steps {
gradle { gradle {
tasks = "clean verifyPlugin" tasks = "clean runPluginVerifier"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
} }

View File

@@ -19,6 +19,8 @@ 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")
@@ -144,7 +146,6 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
gradle { gradle {
name = "Run Integrations" name = "Run Integrations"
tasks = "releaseActions" tasks = "releaseActions"
gradleParams = "--no-configuration-cache"
} }
// gradle { // gradle {
// name = "Slack Notification" // name = "Slack Notification"
@@ -157,4 +158,16 @@ 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()
}
}
}
}) })

View File

@@ -511,18 +511,6 @@ Contributors:
[![icon][github]](https://github.com/Aisper) [![icon][github]](https://github.com/Aisper)
&nbsp; &nbsp;
Egor Nikolaevsky Egor Nikolaevsky
* [![icon][mail]](mailto:77796630+throwaway69420-69420@users.noreply.github.com)
[![icon][github]](https://github.com/kun-codes)
&nbsp;
Bishwa Saha,
* [![icon][mail]](mailto:alexfu@fastmail.com)
[![icon][github]](https://github.com/alexfu)
&nbsp;
Alex Fu
* [![icon][mail]](mailto:jakepeters199@hotmail.com)
[![icon][github]](https://github.com/LazyScaper)
&nbsp;
Jake
Previous contributors: Previous contributors:

View File

@@ -222,13 +222,13 @@ Ex commands or via `:map` command mappings:
* Execute an action by `{action_id}`. Works from Ex command line. * Execute an action by `{action_id}`. Works from Ex command line.
* Please don't use `:action` in mappings. Use `<Action>` instead. * Please don't use `:action` in mappings. Use `<Action>` instead.
### Finding action IDs: ### Finding action ids:
* IJ provides `IdeaVim: track action IDs` command to show the id of the executed actions. * IJ provides `IdeaVim: track action Ids` command to show the id of the executed actions.
This command can be found in "Search everywhere" (double `shift`). This command can be found in "Search everywhere" (double `shift`).
<details> <details>
<summary><strong>"Track action IDs" Details</strong> (click to see)</summary> <summary><strong>"Track action Ids" Details</strong> (click to see)</summary>
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif"> <source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif">
<img src="assets/readme/track_action_light.gif" alt="track action ids"/> <img src="assets/readme/track_action_light.gif" alt="track action ids"/>
@@ -369,8 +369,6 @@ is the full list of synonyms.
- Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27): - Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27):
> The local value is set to -123456 when the global value is to be used. > The local value is set to -123456 when the global value is to be used.
- Vi (not Vim) is a POSIX standard, and [has a spec](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html)! Vim is mostly POSIX compliant when Vi compatibility is selected with the `'compatible'` option, but there are still some differences that can be changed with `'copoptions'`. The spec is interesting because it documents the behaviour of different commands in a stricter style than the user documentation, describing the current line and column after the command, for example. [More details can be found by reading `:help posix`](https://vimhelp.org/vi_diff.txt.html#posix).
License License
------- -------

View File

@@ -1,5 +1,6 @@
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.

View File

@@ -8,7 +8,7 @@
plugins { plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.serialization") version "2.0.0" kotlin("plugin.serialization") version "1.9.22"
} }
val kotlinxSerializationVersion: String by project val kotlinxSerializationVersion: String by project
@@ -21,7 +21,7 @@ repositories {
} }
dependencies { dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.24") compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.24-1.0.20")
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")

View File

@@ -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
class VimscriptFunctionProcessorProvider : SymbolProcessorProvider { public class VimscriptFunctionProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return VimscriptFunctionProcessor(environment) return VimscriptFunctionProcessor(environment)
} }

View File

@@ -32,8 +32,6 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
import org.kohsuke.github.GHUser import org.kohsuke.github.GHUser
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@@ -45,19 +43,19 @@ buildscript {
} }
dependencies { dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2") classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2")
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.10.0.202406032230-r") classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.12") classpath("io.ktor:ktor-client-core:2.3.11")
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.12") classpath("io.ktor:ktor-client-auth:2.3.11")
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.12") classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
// 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")
@@ -65,27 +63,45 @@ buildscript {
} }
plugins { plugins {
antlr
java java
kotlin("jvm") version "2.0.0" kotlin("jvm") version "1.9.22"
application application
id("java-test-fixtures") id("java-test-fixtures")
id("org.jetbrains.intellij.platform") version "2.0.0-rc2"
id("org.jetbrains.changelog") version "2.2.1" id("org.jetbrains.intellij") version "1.17.3"
id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.6.1" id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "4.0.0" id("com.dorongold.task-tree") version "3.0.0"
id("com.google.devtools.ksp") version "2.0.0-1.0.23"
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
} }
val moduleSources by configurations.registering 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
val ideaVersion: String by project val ideaVersion: String by project
val ideaType: String by project val ideaType: 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
@@ -93,45 +109,25 @@ 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()
intellijPlatform { maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
defaultRepositories()
}
} }
dependencies { dependencies {
api(project(":vim-engine")) api(project(":vim-engine"))
ksp(project(":annotation-processors")) ksp(project(":annotation-processors"))
compileOnly(project(":annotation-processors")) implementation(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")
intellijPlatform { antlr("org.antlr:antlr4:$antlrVersion")
// Note that it is also possible to use local("...") to compile against a locally installed IDE
// E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
// Or something like: intellijIdeaUltimate(ideaVersion)
create(ideaType, ideaVersion)
pluginVerifier()
zipSigner()
instrumentationTools()
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
// AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "242.20224.159")
}
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
// --------- Test dependencies ---------- // --------- Test dependencies ----------
testImplementation(testFixtures(project(":")))
testApi("com.squareup.okhttp3:okhttp:4.12.0") testApi("com.squareup.okhttp3:okhttp:4.12.0")
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
@@ -145,20 +141,14 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// 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.4.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
} }
configurations { configurations {
@@ -169,8 +159,6 @@ 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
@@ -183,9 +171,6 @@ 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
@@ -198,94 +183,34 @@ tasks {
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library // See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions // For the list of bundled versions
apiVersion = "1.9" apiVersion = "1.9"
freeCompilerArgs = listOf( freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
compileTestKotlin { compileTestKotlin {
enabled = false
kotlinOptions { kotlinOptions {
jvmTarget = javaVersion jvmTarget = javaVersion
apiVersion = "1.9" apiVersion = "1.9"
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
freeCompilerArgs += listOf("-Xskip-prerelease-check", "-Xallow-unstable-dependencies")
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
// Note that this will run the plugin installed in the IDE specified in dependencies. To run in a different IDE, use downloadRobotServerPlugin {
// a custom task (see below) version.set(remoteRobotVersion)
runIde { }
runIdeForUiTests {
systemProperty("robot-server.port", "8082")
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.show.tips.on.startup.default.value", "false")
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true) systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
} }
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies runIde {
// Note that the version must be greater than the plugin's target version, for obvious reasons systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
// val runIdeCustom by intellijPlatformTesting.runIde.registering {
// type = IntelliJPlatformType.Rider
// version = "2024.1.2"
// }
// Uncomment to run the plugin in a locally installed IDE
// val runIdeLocal by intellijPlatformTesting.runIde.registering {
// localPath = file("/Users/{user}/Applications/WebStorm.app")
// }
val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Drobot-server.port=8082",
"-Dide.mac.message.dialogs.as.sheets=false",
"-Djb.privacy.policy.text=<!--999.999-->",
"-Djb.consents.confirmation.enabled=false",
"-Dide.show.tips.on.startup.default.value=false",
"-Doctopus.handler=" + (System.getProperty("octopus.handler") ?: true),
)
}
}
plugins {
robotServerPlugin(remoteRobotVersion)
}
}
val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.FRONTEND
// Frontend split mode support requires 242+
// TODO: Remove this once IdeaVim targets 242, as the task will naturally use the target version to run
version.set(splitModeVersion)
}
// Add plugin open API sources to the plugin ZIP
val sourcesJar by registering(Jar::class) {
dependsOn(moduleSources)
destinationDirectory.set(layout.buildDirectory.dir("libs"))
archiveClassifier.set(DocsType.SOURCES)
from(sourceSets.main.map { it.kotlin })
from(provider {
moduleSources.map {
it.map { jarFile -> zipTree(jarFile) }
}
})
}
buildPlugin {
dependsOn(sourcesJar)
from(sourcesJar) { into("lib/src") }
} }
} }
@@ -296,6 +221,7 @@ java {
} }
kotlin { kotlin {
explicitApi()
jvmToolchain { jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion)) languageVersion.set(JavaLanguageVersion.of(javaVersion))
} }
@@ -310,68 +236,96 @@ gradle.projectsEvaluated {
// --- Intellij plugin // --- Intellij plugin
intellijPlatform { intellij {
pluginConfiguration { version.set(ideaVersion)
name = "IdeaVim" type.set(ideaType)
changeNotes.set( pluginName.set("IdeaVim")
"""
Undo in IdeaVim now works like in Vim<br/>
Caret movement is no longer a separate undo step, and full insert is undoable in one step.<br/>
<a href="https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks">Share Feedback</a>
<br/>
<br/>
<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>
""".trimIndent()
)
ideaVersion { updateSinceUntilBuild.set(false)
// Let the Gradle plugin set the since-build version. It defaults to the version of the IDE we're building against
// specified as two components, `{branch}.{build}` (e.g., "241.15989"). There is no third component specified.
// The until-build version defaults to `{branch}.*`, but we want to support _all_ future versions, so we set it
// with a null provider (the provider is important).
// By letting the Gradle plugin handle this, the Plugin DevKit IntelliJ plugin cannot help us with the "Usage of
// IntelliJ API not available in older IDEs" inspection. However, since our since-build is the version we compile
// against, we can never get an API that's newer - it would be an unresolved symbol.
untilBuild.set(provider { null })
}
}
publishing { downloadSources.set(downloadIdeaSources.toBoolean())
instrumentCode.set(instrumentPluginCode.toBoolean())
intellijRepository.set("https://www.jetbrains.com/intellij-repository")
plugins.set(listOf("AceJump:3.8.11"))
}
tasks {
publishPlugin {
channels.set(publishChannels.split(",")) channels.set(publishChannels.split(","))
token.set(publishToken) token.set(publishToken)
} }
signing { signPlugin {
certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN")) certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN"))
privateKey.set(providers.environmentVariable("PRIVATE_KEY")) privateKey.set(providers.environmentVariable("PRIVATE_KEY"))
password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD")) password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
} }
verifyPlugin { runPluginVerifier {
teamCityOutputFormat = true downloadDir.set("${project.buildDir}/pluginVerifier/ides")
ides { teamCityOutputFormat.set(true)
recommended()
}
} }
instrumentCode.set(instrumentPluginCode.toBoolean()) generateGrammarSource {
maxHeapSize = "128m"
arguments.addAll(listOf("-package", "com.maddyhome.idea.vim.vimscript.parser.generated", "-visitor"))
outputDirectory = file("src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated")
}
named("compileKotlin") {
dependsOn("generateGrammarSource")
}
named("compileTestKotlin") {
dependsOn("generateTestGrammarSource")
}
named("compileTestFixturesKotlin") {
dependsOn("generateTestFixturesGrammarSource")
}
// Add plugin open API sources to the plugin ZIP
val createOpenApiSourceJar by registering(Jar::class) {
dependsOn("generateGrammarSource")
// Java sources
from(sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
from(project(":vim-engine").sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
// Kotlin sources
from(kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
destinationDirectory.set(layout.buildDirectory.dir("libs"))
archiveClassifier.set("src")
}
buildPlugin {
dependsOn(createOpenApiSourceJar)
from(createOpenApiSourceJar) { into("lib/src") }
}
patchPluginXml {
// Don't forget to update plugin.xml
sinceBuild.set("241.15989.150")
changeNotes.set(
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
)
}
} }
ksp { // --- Tests
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 {
// tasks.named("kspKotlin").configure { dependsOn("clean") } test {
tasks.named("kspTestFixturesKotlin").configure { enabled = false } useJUnitPlatform()
tasks.named("kspTestFixturesKotlin").configure { enabled = false } }
tasks.named("kspTestKotlin").configure { enabled = false }
} }
// --- Changelog // --- Changelog
changelog { changelog {
@@ -497,8 +451,6 @@ 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")
@@ -958,12 +910,12 @@ fun changes(): List<Change> {
println("Start changes processing") println("Start changes processing")
for (message in messages) { for (message in messages) {
println("Processing '$message'...") println("Processing '$message'...")
val lowercaseMessage = message.lowercase() val lowercaseMessage = message.toLowerCase()
val regex = "^fix\\((vim-\\d+)\\):".toRegex() val regex = "^fix\\((vim-\\d+)\\):".toRegex()
val findResult = regex.find(lowercaseMessage) val findResult = regex.find(lowercaseMessage)
if (findResult != null) { if (findResult != null) {
println("Message matches") println("Message matches")
val value = findResult.groups[1]!!.value.uppercase() val value = findResult.groups[1]!!.value.toUpperCase()
val shortMessage = message.drop(findResult.range.last + 1).trim() val shortMessage = message.drop(findResult.range.last + 1).trim()
newFixes += Change(value, shortMessage) newFixes += Change(value, shortMessage)
} else { } else {

View File

@@ -129,26 +129,8 @@ Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-
</details> </details>
### Instructions ### Instructions
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... https://github.com/terryma/vim-multiple-cursors/blob/master/doc/multiple_cursors.txt
```
" 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>

View File

@@ -40,33 +40,30 @@ 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

View File

@@ -5,9 +5,9 @@ Using actions from external plugins is the same, as tracking and reusing any IDE
1. Install the plugin via Marketplace 1. Install the plugin via Marketplace
2. Enable action tracking. You can enable it by one of the following ways: 2. Enable action tracking. You can enable it by one of the following ways:
* Execute `:set trackactionids` ex command or just `:set tai` * Execute `:set trackactionids` ex command or just `:set tai`
* Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action IDs" to find the toggle that enables and disables action tracking * Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action Ids" to find the toggle that enables and disables action tracking
3. Execute the plugin action the way intended by plugin author "See Edit menu or use ⇧ + ⌥ + U / Shift + Alt + U" or just find the `Toggle Camel Case` action in the "Find actions" window (`Ctrl-Shift-A`). If you action tracking is on, you will see this notification in your right bottom corner: 3. Execute the plugin action the way intended by plugin author "See Edit menu or use ⇧ + ⌥ + U / Shift + Alt + U" or just find the `Toggle Camel Case` action in the "Find actions" window (`Ctrl-Shift-A`). If you action tracking is on, you will see this notification in your right bottom corner:
<img alt="Notification" src="images/action-id-notification.png"/> <img alt="Notification" src="images/action-id-notification.png"/>
4. Copy the action id from the notification to create the following mapping in your .ideavimrc 4. Copy the action id from the notification to create the following mapping in your .ideavimrc
```map <leader>t <Action>(de.netnexus.CamelCasePlugin.ToggleCamelCase)``` ```map <leader>t <Action>(de.netnexus.CamelCasePlugin.ToggleCamelCase)```

View File

@@ -8,33 +8,22 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
# ideaVersion is the version of the IDE that will be added as a compile-time dependency. The format can be either #ideaVersion=LATEST-EAP-SNAPSHOT
# product version (e.g. 2024.1, 2024.1.1) or build (e.g. 241.15989.150, 241-EAP-SNAPSHOT). The dependency will be ideaVersion=2024.1.1
# 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.2
# 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
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-39 version=chylex-35
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.23 remoteRobotVersion=0.11.22
antlrVersion=4.10.1 antlrVersion=4.10.1
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2 kotlin.incremental.useClasspathSnapshot=false
# 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
kotlinVersion=2.0.0 kotlinVersion=1.9.22
publishToken=token publishToken=token
publishChannels=eap publishChannels=eap
@@ -47,7 +36,6 @@ 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

View File

@@ -19,9 +19,9 @@ exclude:
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
- src/main/java/com/maddyhome/idea/vim/package-info.java - src/main/java/com/maddyhome/idea/vim/package-info.java
- vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated - vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
- vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated
- src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java - src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java
- tests/ui-fixtures - tests/ui-fixtures
dependencyIgnores: dependencyIgnores:

View File

@@ -20,17 +20,17 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.25") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
implementation("io.ktor:ktor-client-core:2.3.12") implementation("io.ktor:ktor-client-core:2.3.11")
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.12") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
implementation("io.ktor:ktor-client-auth:2.3.12") implementation("io.ktor:ktor-client-auth:2.3.11")
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.10.0.202406032230-r") implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
implementation("com.vdurmont:semver4j:3.1.0") implementation("com.vdurmont:semver4j:3.1.0")
} }

20
settings.gradle Normal file
View File

@@ -0,0 +1,20 @@
// 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'

View File

@@ -1,21 +0,0 @@
// 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")

View File

@@ -119,7 +119,6 @@ command:
| PROMPT_REPLACE | P_LOWERCASE | P_UPPERCASE | PRINT | PREVIOUS_TAB | N_UPPERCASE | PREVIOUS_FILE | PLUG | PROMPT_REPLACE | P_LOWERCASE | P_UPPERCASE | PRINT | PREVIOUS_TAB | N_UPPERCASE | PREVIOUS_FILE | PLUG
| ONLY | NO_HL_SEARCH | NEXT_TAB | N_LOWERCASE | NEXT_FILE | M_LOWERCASE | MOVE_TEXT | MARKS | K_LOWERCASE | ONLY | NO_HL_SEARCH | NEXT_TAB | N_LOWERCASE | NEXT_FILE | M_LOWERCASE | MOVE_TEXT | MARKS | K_LOWERCASE
| MARK_COMMAND | JUMPS | J_LOWERCASE | JOIN_LINES | HISTORY | GO_TO_CHAR | SYMBOL | FIND | CLASS | F_LOWERCASE | MARK_COMMAND | JUMPS | J_LOWERCASE | JOIN_LINES | HISTORY | GO_TO_CHAR | SYMBOL | FIND | CLASS | F_LOWERCASE
| CLEARJUMPS
| FILE | EXIT | E_LOWERCASE | EDIT_FILE | DUMP_LINE | DIGRAPH | DEL_MARKS | D_LOWERCASE | DEL_LINES | DELCMD | FILE | EXIT | E_LOWERCASE | EDIT_FILE | DUMP_LINE | DIGRAPH | DEL_MARKS | D_LOWERCASE | DEL_LINES | DELCMD
| T_LOWERCASE | COPY | CMD_CLEAR | BUFFER_LIST | BUFFER_CLOSE | B_LOWERCASE | BUFFER | ASCII | T_LOWERCASE | COPY | CMD_CLEAR | BUFFER_LIST | BUFFER_CLOSE | B_LOWERCASE | BUFFER | ASCII
| ACTIONLIST | ACTION | LOCKVAR | UNLOCKVAR | PACKADD | TABMOVE | ACTIONLIST | ACTION | LOCKVAR | UNLOCKVAR | PACKADD | TABMOVE
@@ -619,7 +618,6 @@ BUFFER_CLOSE: 'bd' | 'bde' | 'bdel' | 'bdele' | 'bdelet' | 'bdelete';
BUFFER_LIST: 'buffers' | 'ls' | 'files'; BUFFER_LIST: 'buffers' | 'ls' | 'files';
CALL: 'cal' | 'call'; CALL: 'cal' | 'call';
CLASS: 'cla' | 'clas' | 'class'; CLASS: 'cla' | 'clas' | 'class';
CLEARJUMPS: 'cle' | 'clea' | 'clear' | 'clearj' | 'clearju' | 'clearjum' | 'clearjump' | 'clearjumps';
CMD: 'com' | 'comm' | 'comma' | 'comman' | 'command'; CMD: 'com' | 'comm' | 'comma' | 'comman' | 'command';
CMD_CLEAR: 'comc' | 'comcl' | 'comcle' | 'comclea' | 'comclear'; CMD_CLEAR: 'comc' | 'comcl' | 'comcle' | 'comclea' | 'comclear';
COPY: 'co' | 'cop' | 'copy'; COPY: 'co' | 'cop' | 'copy';

View File

@@ -8,19 +8,14 @@
package com.maddyhome.idea.vim package com.maddyhome.idea.vim
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.updateSettings.impl.UpdateSettings
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.ui.JoinEap
import com.maddyhome.idea.vim.ui.JoinEap.EAP_LINK
/** /**
* @author Alex Plate * @author Alex Plate
@@ -33,11 +28,6 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
if (firstInitializationOccurred) return if (firstInitializationOccurred) return
firstInitializationOccurred = true firstInitializationOccurred = true
if (!VimPlugin.getVimState().wasSubscibedToEAPAutomatically && ApplicationManager.getApplication().isEAP && !JoinEap.eapActive()) {
VimPlugin.getVimState().wasSubscibedToEAPAutomatically = true
UpdateSettings.getInstance().storedPluginHosts += EAP_LINK
}
// This code should be executed once // This code should be executed once
VimPlugin.getInstance().initialize() VimPlugin.getInstance().initialize()
} }
@@ -46,7 +36,6 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
// This is a temporal workaround for VIM-2487 // This is a temporal workaround for VIM-2487
internal class PyNotebooksCloseWorkaround : ProjectManagerListener { internal class PyNotebooksCloseWorkaround : ProjectManagerListener {
override fun projectClosingBeforeSave(project: Project) { override fun projectClosingBeforeSave(project: Project) {
initInjector()
// TODO: Confirm context in CWM scenario // TODO: Confirm context in CWM scenario
if (injector.globalIjOptions().closenotebooks) { if (injector.globalIjOptions().closenotebooks) {
injector.editorGroup.getEditors().forEach { vimEditor -> injector.editorGroup.getEditors().forEach { vimEditor ->

View File

@@ -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
object RegisterActions { public object RegisterActions {
/** /**
* Register all the key/action mappings for the plugin. * Register all the key/action mappings for the plugin.
*/ */
@JvmStatic @JvmStatic
fun registerActions() { public fun registerActions() {
registerVimCommandActions() registerVimCommandActions()
registerShortcutsWithoutActions() registerShortcutsWithoutActions()
} }
fun findAction(id: String): EditorActionHandlerBase? { public fun findAction(id: String): EditorActionHandlerBase? {
val commandBean = IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
?: EngineCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null ?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
return commandBean.instance return commandBean.instance
} }
fun findActionOrDie(id: String): EditorActionHandlerBase { public 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
fun unregisterActions() { public 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()
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) } EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
} }
private fun registerShortcutsWithoutActions() { private fun registerShortcutsWithoutActions() {

View File

@@ -37,9 +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.IjVimInjectorKt;
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;
@@ -47,7 +46,6 @@ 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;
@@ -68,7 +66,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
private static final Logger LOG = Logger.getInstance(VimPlugin.class); private static final Logger LOG = Logger.getInstance(VimPlugin.class);
static { static {
IjVimInjectorKt.initInjector(); VimInjectorKt.setInjector(new IjVimInjector());
} }
private final @NotNull VimState state = new VimState(); private final @NotNull VimState state = new VimState();
@@ -125,12 +123,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return (FileGroup)VimInjectorKt.getInjector().getFile(); return (FileGroup)VimInjectorKt.getInjector().getFile();
} }
public static @NotNull IjVimSearchGroup getSearch() { public static @NotNull SearchGroup getSearch() {
return ApplicationManager.getApplication().getService(IjVimSearchGroup.class); return ApplicationManager.getApplication().getService(SearchGroup.class);
} }
public static @Nullable IjVimSearchGroup getSearchIfCreated() { public static @Nullable SearchGroup getSearchIfCreated() {
return ApplicationManager.getApplication().getServiceIfCreated(IjVimSearchGroup.class); return ApplicationManager.getApplication().getServiceIfCreated(SearchGroup.class);
} }
public static @NotNull ProcessGroup getProcess() { public static @NotNull ProcessGroup getProcess() {
@@ -285,11 +283,11 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (!ApplicationManager.getApplication().isUnitTestMode()) { if (!ApplicationManager.getApplication().isUnitTestMode()) {
try { try {
injector.getOptionGroup().startInitVimRc(); VimInjectorKt.injector.getOptionGroup().startInitVimRc();
executeIdeaVimRc(editor); executeIdeaVimRc(editor);
} }
finally { finally {
injector.getOptionGroup().endInitVimRc(); VimInjectorKt.injector.getOptionGroup().endInitVimRc();
} }
} }
} }
@@ -347,14 +345,14 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
} }
private void turnOffPlugin(boolean unsubscribe) { private void turnOffPlugin(boolean unsubscribe) {
IjVimSearchGroup searchGroup = getSearchIfCreated(); SearchGroup searchGroup = getSearchIfCreated();
if (searchGroup != null) { if (searchGroup != null) {
searchGroup.turnOff(); searchGroup.turnOff();
} }
if (unsubscribe) { if (unsubscribe) {
VimListenerManager.INSTANCE.turnOff(); VimListenerManager.INSTANCE.turnOff();
} }
injector.getCommandLine().fullReset(); ExEntryPanel.fullReset();
// Unregister vim actions in command mode // Unregister vim actions in command mode
RegisterActions.unregisterActions(); RegisterActions.unregisterActions();

View File

@@ -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.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.group.EditorHolderService
@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
ExEntryPanel.getInstance().setEditor(null) EditorHolderService.getInstance().editor = null
} }
companion object { companion object {

View File

@@ -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
*/ */
class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx { public 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

View File

@@ -8,6 +8,6 @@
package com.maddyhome.idea.vim.action package com.maddyhome.idea.vim.action
object IntellijCommandProvider : CommandProvider { public object IntellijCommandProvider : CommandProvider {
override val commandListFileName: String = "intellij_commands.json" override val commandListFileName: String = "intellij_commands.json"
} }

View File

@@ -1,52 +0,0 @@
package com.maddyhome.idea.vim.action
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
class VimRunLastMacroInOpenFiles : DumbAwareAction() {
override fun update(e: AnActionEvent) {
val lastRegister = injector.macro.lastRegister
val isEnabled = lastRegister != 0.toChar()
e.presentation.isEnabled = isEnabled
e.presentation.text = if (isEnabled) "Run Macro '${lastRegister}' in Open Files" else "Run Last Macro in Open Files"
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return
val editors = fileEditorManager.allEditors.filterIsInstance<TextEditor>()
WriteCommandAction.writeCommandAction(project)
.withName(e.presentation.text)
.withGlobalUndo()
.withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION)
.run<RuntimeException> {
val reg = injector.macro.lastRegister
for (editor in editors) {
fileEditorManager.openFile(editor.file, true)
val vimEditor = editor.editor.vim
vimEditor.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(vimEditor)
injector.macro.playbackRegister(vimEditor, IjEditorExecutionContext(e.dataContext), reg, 1)
}
}
}
}

View File

@@ -26,6 +26,7 @@ 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
@@ -43,9 +44,7 @@ import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.listener.AceJumpService 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.initInjector
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
@@ -61,14 +60,11 @@ 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
*/ */
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ { public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
init {
initInjector()
}
private val traceTime: Boolean private val traceTime: Boolean
get() { get() {
// Make sure the injector is initialized
VimPlugin.getInstance()
return injector.globalOptions().ideatracetime return injector.globalOptions().ideatracetime
} }
@@ -261,7 +257,7 @@ 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) {
ExEntryPanel.getInstance().ijEditor EditorHolderService.getInstance().editor
} else { } else {
null null
} }

View File

@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.inRepeatMode 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.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
@@ -102,7 +102,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val argument = cmd.argument ?: return false val argument = cmd.argument ?: return false
if (!editor.inRepeatMode) { if (!editor.vimStateMachine.isDotRepeatInProgress) {
argumentCaptured = argument argumentCaptured = argument
} }
val range = getMotionRange(editor, context, argument, operatorArguments) val range = getMotionRange(editor, context, argument, operatorArguments)

View File

@@ -17,6 +17,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
@CommandOrMotion(keys = ["."], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["."], modes = [Mode.NORMAL])
@@ -24,7 +25,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_WRITABLE override val type: Command.Type = Command.Type.OTHER_WRITABLE
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val state = injector.vimState val state = editor.vimStateMachine
val lastCommand = VimRepeater.lastChangeCommand val lastCommand = VimRepeater.lastChangeCommand
if (lastCommand == null && Extension.lastExtensionHandler == null) return false if (lastCommand == null && Extension.lastExtensionHandler == null) return false

View File

@@ -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])
class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() { public 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,

View File

@@ -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])
class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() { public 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(

View File

@@ -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])
class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() { public 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(

View File

@@ -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])
class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() { public 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(

View File

@@ -36,18 +36,6 @@ 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])
@@ -60,18 +48,6 @@ 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])
@@ -79,7 +55,7 @@ internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(editor, IdeActions.ACTION_QUICK_JAVADOC, context) injector.actionExecutor.executeAction(IdeActions.ACTION_QUICK_JAVADOC, context)
return true return true
} }
} }

View File

@@ -9,18 +9,21 @@
package com.maddyhome.idea.vim.command package com.maddyhome.idea.vim.command
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.SelectionType
/** /**
* COMPATIBILITY-LAYER: Additional class * COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r * Please see: https://jb.gg/zo8n0r
*/ */
@Deprecated("Use `injector.vimState`") public class CommandState(private val machine: VimStateMachine) {
class CommandState(private val machine: VimStateMachine) {
val mode: Mode public val isOperatorPending: Boolean
get() = machine.isOperatorPending(machine.mode)
public val mode: Mode
get() { get() {
val myMode = machine.mode val myMode = machine.mode
return when (myMode) { return when (myMode) {
@@ -34,23 +37,13 @@ class CommandState(private val machine: VimStateMachine) {
} }
} }
@Deprecated("Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith( public val commandBuilder: CommandBuilder
"KeyHandler.getInstance().keyHandlerState.commandBuilder", get() = machine.commandBuilder
"com.maddyhome.idea.vim.KeyHandler"
)
)
val commandBuilder: CommandBuilder
get() = KeyHandler.getInstance().keyHandlerState.commandBuilder
@Deprecated("Use `KeyHandler.keyHandlerState.mappingState", ReplaceWith( public val mappingState: MappingState
"KeyHandler.getInstance().keyHandlerState.mappingState", get() = machine.mappingState
"com.maddyhome.idea.vim.KeyHandler"
)
)
val mappingState: MappingState
get() = KeyHandler.getInstance().keyHandlerState.mappingState
enum class Mode { public enum class Mode {
// Basic modes // Basic modes
COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/ COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
@@ -58,15 +51,22 @@ class CommandState(private val machine: VimStateMachine) {
OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
} }
enum class SubMode { public enum class SubMode {
NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
} }
companion object { public companion object {
@JvmStatic @JvmStatic
@Deprecated("Use `injector.vimState`") public fun getInstance(editor: Editor): CommandState {
fun getInstance(editor: Editor): CommandState { return CommandState(editor.vim.vimStateMachine)
return CommandState(injector.vimState)
} }
} }
} }
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
}

View File

@@ -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
class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) { public class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) {
fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column public fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column
companion object { public companion object {
fun fromOffset(editor: Editor, offset: Int): CharacterPosition { public 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)
} }
fun atCaret(editor: Editor): CharacterPosition { public fun atCaret(editor: Editor): CharacterPosition {
return fromOffset(editor, editor.caretModel.offset) return fromOffset(editor, editor.caretModel.offset)
} }
} }

View File

@@ -12,17 +12,17 @@ import com.intellij.application.options.CodeStyle
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
import com.maddyhome.idea.vim.api.VimIndentConfig
internal class IndentConfig private constructor(indentOptions: IndentOptions): VimIndentConfig { internal class IndentConfig private constructor(indentOptions: IndentOptions) {
private val indentSize = indentOptions.INDENT_SIZE private val indentSize = indentOptions.INDENT_SIZE
private val tabSize = indentOptions.TAB_SIZE private val tabSize = indentOptions.TAB_SIZE
private val isUseTabs = indentOptions.USE_TAB_CHARACTER private val isUseTabs = indentOptions.USE_TAB_CHARACTER
override fun getIndentSize(depth: Int): Int = indentSize * depth fun getTotalIndent(count: Int): Int = indentSize * count
override fun createIndentByDepth(depth: Int): String = createIndentBySize(getIndentSize(depth))
override fun createIndentBySize(size: Int): String { fun createIndentByCount(count: Int): String = createIndentBySize(getTotalIndent(count))
fun createIndentBySize(size: Int): String {
val tabCount: Int val tabCount: Int
val spaceCount: Int val spaceCount: Int
if (isUseTabs) { if (isUseTabs) {

View File

@@ -18,8 +18,6 @@ import kotlin.reflect.KProperty
internal class VimState { internal class VimState {
var isIdeaJoinNotified by StateProperty("idea-join") var isIdeaJoinNotified by StateProperty("idea-join")
var isIdeaPutNotified by StateProperty("idea-put") var isIdeaPutNotified by StateProperty("idea-put")
var isNewUndoNotified by StateProperty("idea-undo")
var wasSubscibedToEAPAutomatically by StateProperty("was-automatically-subscribed-to-eap")
fun readData(element: Element) { fun readData(element: Element) {
val notifications = element.getChild("notifications") val notifications = element.getChild("notifications")

View File

@@ -9,142 +9,62 @@ package com.maddyhome.idea.vim.ex
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimOutputPanelBase import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimExOutput import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel import com.maddyhome.idea.vim.ui.ExOutputPanel
import java.lang.ref.WeakReference
import javax.swing.KeyStroke
// 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
class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanelBase() { public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
private var isActiveInTestMode = false private var isActiveInTestMode = false
val editor get() = myEditor.get() override val isActive: Boolean
val isActive: Boolean
get() = if (!ApplicationManager.getApplication().isUnitTestMode) { get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getNullablePanel(it) }?.myActive ?: false ExOutputPanel.isPanelActive(myEditor)
} else { } else {
isActiveInTestMode isActiveInTestMode
} }
override fun addText(text: String, isNewLine: Boolean) { override var text: String? = null
if (this.text.isNotEmpty() && isNewLine) this.text += "\n$text" else this.text += text
}
override fun show() {
if (editor == null) return
val currentPanel = injector.outputPanel.getCurrentOutputPanel()
if (currentPanel != null && currentPanel != this) currentPanel.close()
editor!!.vimExOutput = this
val exOutputPanel = ExOutputPanel.getInstance(editor!!)
if (!exOutputPanel.myActive) {
if (ApplicationManager.getApplication().isUnitTestMode) {
isActiveInTestMode = true
} else {
exOutputPanel.activate()
}
}
}
override fun scrollPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollPage()
}
override fun scrollHalfPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollHalfPage()
}
override fun scrollLine() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollLine()
}
override var text: String = ""
get() = if (!ApplicationManager.getApplication().isUnitTestMode) { get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).text } ?: "" ExOutputPanel.getInstance(myEditor).text
} else { } else {
// ExOutputPanel always returns a non-null string
field 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) {
editor?.let { ExOutputPanel.getInstance(it).setText(newValue) } ExOutputPanel.getInstance(myEditor).setText(value ?: "")
} else { } else {
field = newValue field = value
isActiveInTestMode = newValue.isNotEmpty() isActiveInTestMode = !value.isNullOrEmpty()
} }
} }
override var label: String
get() {
val notNullEditor = editor ?: return ""
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return ""
return panel.myLabel.text
}
set(value) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.myLabel.text = value
}
fun output(text: String) { override fun output(text: String) {
this.text = text this.text = text
} }
fun clear() { override fun clear() {
text = "" text = null
}
override val atEnd: Boolean
get() {
val notNullEditor = editor ?: return false
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return false
return panel.isAtEnd()
}
override fun onBadKey() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.onBadKey()
}
override fun close(key: KeyStroke?) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.close(key)
} }
override fun close() { override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) { if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).close() } ExOutputPanel.getInstance(myEditor).close()
} }
else { else {
isActiveInTestMode = false isActiveInTestMode = false
} }
} }
companion object { public companion object {
@JvmStatic @JvmStatic
fun getInstance(editor: Editor): ExOutputModel { public fun getInstance(editor: Editor): ExOutputModel {
var model = editor.vimExOutput var model = editor.vimExOutput
if (model == null) { if (model == null) {
model = ExOutputModel(WeakReference(editor)) model = ExOutputModel(editor)
editor.vimExOutput = model editor.vimExOutput = model
} }
return model return model
} }
@JvmStatic
fun tryGetInstance(editor: Editor) = editor.vimExOutput
} }
} }

View File

@@ -9,6 +9,7 @@ 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
@@ -24,14 +25,13 @@ import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.TestInputModel import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.helper.noneOfEnum import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.MappingOwner import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.ui.ex.ExEntryPanelService
import com.maddyhome.idea.vim.vimscript.model.Executable import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimLContext import com.maddyhome.idea.vim.vimscript.model.VimLContext
@@ -51,13 +51,13 @@ import javax.swing.KeyStroke
* *
* @author vlan * @author vlan
*/ */
object VimExtensionFacade { public 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
fun putExtensionHandlerMapping( public fun putExtensionHandlerMapping(
modes: Set<MappingMode>, modes: Set<MappingMode>,
fromKeys: List<KeyStroke>, fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner, pluginOwner: MappingOwner,
@@ -68,15 +68,13 @@ 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
@Deprecated( public fun putExtensionHandlerMapping(
"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,
@@ -88,7 +86,7 @@ object VimExtensionFacade {
/** The 'map' command for mapping keys to other keys. */ /** The 'map' command for mapping keys to other keys. */
@JvmStatic @JvmStatic
fun putKeyMapping( public fun putKeyMapping(
modes: Set<MappingMode>, modes: Set<MappingMode>,
fromKeys: List<KeyStroke>, fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner, pluginOwner: MappingOwner,
@@ -100,7 +98,7 @@ 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
fun putKeyMappingIfMissing( public fun putKeyMappingIfMissing(
modes: Set<MappingMode>, modes: Set<MappingMode>,
fromKeys: List<KeyStroke>, fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner, pluginOwner: MappingOwner,
@@ -114,7 +112,7 @@ 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
*/ */
fun addCommand( public fun addCommand(
name: String, name: String,
handler: CommandAliasHandler, handler: CommandAliasHandler,
) { ) {
@@ -125,7 +123,7 @@ 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
fun addCommand( public fun addCommand(
name: String, name: String,
minimumNumberOfArguments: Int, minimumNumberOfArguments: Int,
maximumNumberOfArguments: Int, maximumNumberOfArguments: Int,
@@ -143,7 +141,7 @@ 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
fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) { public 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) }
@@ -151,8 +149,8 @@ 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
fun inputKeyStroke(editor: Editor): KeyStroke { public fun inputKeyStroke(editor: Editor): KeyStroke {
if (editor.vim.inRepeatMode) { 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")
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}") return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
@@ -183,43 +181,43 @@ 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
fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String { public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
return (injector.commandLine as ExEntryPanelService).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
fun getRegister(register: Char): List<KeyStroke>? { public 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
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? { public fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
val reg = injector.registerGroup.getRegister(register) ?: return null val reg = caret.registerStorage.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
fun setRegister(register: Char, keys: List<KeyStroke?>?) { public 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
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) { public fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList()) caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
} }
/** Set the current contents of the given register */ /** Set the current contents of the given register */
@JvmStatic @JvmStatic
fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) { public 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
fun exportScriptFunction( public fun exportScriptFunction(
scope: Scope?, scope: Scope?,
name: String, name: String,
args: List<String>, args: List<String>,
@@ -255,7 +253,7 @@ object VimExtensionFacade {
} }
} }
fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) { public 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 ->
@@ -276,6 +274,6 @@ fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFu
} }
} }
fun interface ScriptFunction { public fun interface ScriptFunction {
fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult public fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
} }

View File

@@ -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
*/ */
interface VimExtensionHandler : ExtensionHandler { public 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)
} }
fun execute(editor: Editor, context: DataContext) public fun execute(editor: Editor, context: DataContext)
abstract class WithCallback : ExtensionHandler.WithCallback(), VimExtensionHandler public abstract class WithCallback : ExtensionHandler.WithCallback(), VimExtensionHandler
} }

View File

@@ -9,7 +9,6 @@
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.*;
@@ -24,7 +23,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.KeyHandlerState; import com.maddyhome.idea.vim.state.VimStateMachine;
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;
@@ -245,18 +244,19 @@ 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();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); IjVimEditor vimEditor = (IjVimEditor) editor;
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount()); @NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
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 (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!vimStateMachine.isOperatorPending(editor.getMode())) {
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 (editor.getMode() instanceof Mode.VISUAL) { if (vimStateMachine.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 {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class)))); textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class))));
} }
} }

View File

@@ -18,7 +18,6 @@ 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
@@ -46,6 +45,7 @@ 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.handler.TextObjectActionHandler import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.PsiHelper import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.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
@@ -64,7 +64,7 @@ internal class CommentaryExtension : VimExtension {
selectionType: SelectionType, selectionType: SelectionType,
resetCaret: Boolean = true, resetCaret: Boolean = true,
): Boolean { ): Boolean {
val mode = editor.mode val mode = editor.vimStateMachine.mode
if (mode !is Mode.VISUAL) { if (mode !is Mode.VISUAL) {
editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset) editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset)
} }
@@ -79,12 +79,11 @@ internal class CommentaryExtension : VimExtension {
val project = editor.ij.project!! val project = editor.ij.project!!
val callback = { afterCommenting(mode, editor, resetCaret, range) } val callback = { afterCommenting(mode, editor, resetCaret, range) }
actions.any { executeActionWithCallbackOnSuccess(editor, it, project, context, callback) } actions.any { executeActionWithCallbackOnSuccess(it, project, context, callback) }
} }
} }
private fun executeActionWithCallbackOnSuccess( private fun executeActionWithCallbackOnSuccess(
editor: VimEditor,
action: String, action: String,
project: Project, project: Project,
context: ExecutionContext, context: ExecutionContext,
@@ -93,7 +92,7 @@ internal class CommentaryExtension : VimExtension {
val res = Ref.create<Boolean>(false) val res = Ref.create<Boolean>(false)
AsyncActionExecutionService.getInstance(project).withExecutionAfterAction( AsyncActionExecutionService.getInstance(project).withExecutionAfterAction(
action, action,
{ res.set(injector.actionExecutor.executeAction(editor, name = action, context = context)) }, { res.set(injector.actionExecutor.executeAction(action, context)) },
{ if (res.get()) callback() }) { if (res.get()) callback() })
return res.get() return res.get()
} }
@@ -184,10 +183,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 command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java)) val commandState = editor.vimStateMachine
val keyState = KeyHandler.getInstance().keyHandlerState val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
keyState.commandBuilder.completeCommandPart(Argument(command)) commandState.commandBuilder.completeCommandPart(Argument(command))
} }
} }

View File

@@ -23,15 +23,13 @@ import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse import com.intellij.util.Alarm.ThreadToUse
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.injector
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.common.VimYankListener
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.VimNlsSafe import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.listener.VimYankListener
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.vimscript.model.datatypes.VimDataType 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
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
@@ -77,7 +75,7 @@ internal class HighlightColorResetter : LafManagerListener {
* *
* When a new text is yanked or user starts editing, the old highlighting would be deleted. * When a new text is yanked or user starts editing, the old highlighting would be deleted.
*/ */
internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeListener { internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
private val highlightHandler = HighlightHandler() private val highlightHandler = HighlightHandler()
private var initialised = false private var initialised = false
@@ -85,8 +83,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
override fun init() { override fun init() {
// Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called // Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called
injector.listenersNotifier.modeChangeListeners.add(this) VimPlugin.getYank().addListener(this)
injector.listenersNotifier.yankListeners.add(this) VimPlugin.getChange().addInsertListener(this)
// Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when // Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when
// IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the // IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the
@@ -107,8 +105,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
override fun dispose() { override fun dispose() {
// Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads // Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads
injector.listenersNotifier.modeChangeListeners.remove(this) VimPlugin.getYank().removeListener(this)
injector.listenersNotifier.yankListeners.remove(this) VimPlugin.getChange().removeInsertListener(this)
highlightHandler.clearYankHighlighters() highlightHandler.clearYankHighlighters()
initialised = false initialised = false
@@ -119,8 +117,7 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
highlightHandler.highlightYankRange(editor.ij, range) highlightHandler.highlightYankRange(editor.ij, range)
} }
override fun modeChanged(editor: VimEditor, oldMode: Mode) { override fun insertModeStarted(editor: Editor) {
if (editor.mode !is Mode.INSERT) return
ensureInitialised() ensureInitialised()
highlightHandler.clearYankHighlighters() highlightHandler.clearYankHighlighters()
} }

View File

@@ -14,7 +14,6 @@ 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
@@ -41,6 +40,7 @@ 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,23 +91,22 @@ 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 keyHandler = KeyHandler.getInstance() val commandState = editor.vimStateMachine
val keyState = keyHandler.keyHandlerState val count = commandState.commandBuilder.count
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.
keyState.commandBuilder.resetCount() editor.vimStateMachine.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 = keyHandler.isOperatorPending(editor.mode, keyState) val isInOpPending = commandState.isOperatorPending(editor.mode)
if (isInOpPending) { if (isInOpPending) {
val matchitAction = MatchitAction() val matchitAction = MatchitAction()
matchitAction.reverse = reverse matchitAction.reverse = reverse
matchitAction.isInOpPending = true matchitAction.isInOpPending = true
keyState.commandBuilder.completeCommandPart( commandState.commandBuilder.completeCommandPart(
Argument( Argument(
Command( Command(
count, count,

View File

@@ -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 = findWordUnderCursor(editor, primaryCaret) ?: return val range = SearchHelper.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 = findWordUnderCursor(editor, caret) ?: return null val range = SearchHelper.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" + if (whole) "\\<$text\\>" else text return "\\V\\C" + SearchHelper.makeSearchPattern(text, whole)
} }
} }

View File

@@ -478,12 +478,6 @@ 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("NERDTreeMapCopy", "y", NerdAction.ToIj("\$Copy"))
registerCommand("NERDTreeMapPaste", "v", NerdAction.ToIj("\$Paste"))
registerCommand("NERDTreeMapRename", "<C-r>", NerdAction.ToIj("RenameElement"))
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"))

View File

@@ -10,7 +10,6 @@ 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
@@ -29,6 +28,7 @@ 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 = registerGroup.getRegister(lastRegisterChar) ?: return val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
var usedType = savedRegister.type var usedType = savedRegister.type
var usedText = savedRegister.text var usedText = savedRegister.text
@@ -166,18 +166,17 @@ 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(
keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState), editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,
0, 0,
editor.vim.mode, editor.vim.mode,
), ),
saveToRegister = false saveToRegister = false
) )
} }
} }

View File

@@ -36,7 +36,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
@@ -51,7 +50,6 @@ import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
import com.maddyhome.idea.vim.state.mode.returnTo
/** /**
* Port of vim-surround. * Port of vim-surround.
@@ -117,9 +115,6 @@ internal class VimSurroundExtension : VimExtension {
// it.moveToOffset(lineStartOffset) // it.moveToOffset(lineStartOffset)
} }
// Jump back to start // Jump back to start
if (editor.mode !is Mode.NORMAL) {
editor.mode = Mode.NORMAL()
}
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
} }
@@ -141,7 +136,7 @@ internal class VimSurroundExtension : VimExtension {
} }
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
editor.exitVisualMode() executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
} }
} }
} }
@@ -311,10 +306,8 @@ internal class VimSurroundExtension : VimExtension {
private fun getSurroundRange(caret: VimCaret): TextRange? { private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor val editor = caret.editor
if (editor.mode is Mode.CMD_LINE) { val ijEditor = editor.ij
editor.mode = (editor.mode as Mode.CMD_LINE).returnTo() return when (ijEditor.vim.mode) {
}
return when (editor.mode) {
is Mode.NORMAL -> injector.markService.getChangeMarks(caret) is Mode.NORMAL -> injector.markService.getChangeMarks(caret)
is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) } is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) }
else -> null else -> null
@@ -357,9 +350,6 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? { private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
val tagInput = inputString(editor, context, "<", '>') val tagInput = inputString(editor, context, "<", '>')
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput) val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) { return if (matcher.find()) {
val tagName = matcher.group(1) val tagName = matcher.group(1)
@@ -376,9 +366,6 @@ private fun inputFunctionName(
withInternalSpaces: Boolean, withInternalSpaces: Boolean,
): Pair<String, String>? { ): Pair<String, String>? {
val functionNameInput = inputString(editor, context, "function: ", null) val functionNameInput = inputString(editor, context, "function: ", null)
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
if (functionNameInput.isEmpty()) return null if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")" return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
} }

View File

@@ -9,7 +9,6 @@
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;
@@ -24,7 +23,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.KeyHandlerState; import com.maddyhome.idea.vim.state.VimStateMachine;
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;
@@ -134,18 +133,17 @@ 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 KeyHandler keyHandler = KeyHandler.getInstance(); @NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(editor);
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
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 (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!vimStateMachine.isOperatorPending(editor.getMode())) {
((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 (editor.getMode() instanceof Mode.VISUAL) { if (vimStateMachine.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());
@@ -155,7 +153,7 @@ public class VimTextObjEntireExtension implements VimExtension {
}); });
} else { } else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION, textObjectHandler, Command.Type.MOTION,
EnumSet.noneOf(CommandFlags.class)))); EnumSet.noneOf(CommandFlags.class))));
} }

View File

@@ -9,7 +9,6 @@
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;
@@ -25,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.KeyHandlerState; import com.maddyhome.idea.vim.state.VimStateMachine;
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;
@@ -264,18 +263,17 @@ 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 KeyHandler keyHandler = KeyHandler.getInstance(); @NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow); final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!vimStateMachine.isOperatorPending(editor.getMode())) {
((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 (editor.getMode() instanceof Mode.VISUAL) { if (vimStateMachine.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());
@@ -285,7 +283,7 @@ public class VimIndentObject implements VimExtension {
}); });
} else { } else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION, textObjectHandler, Command.Type.MOTION,
EnumSet.noneOf(CommandFlags.class)))); EnumSet.noneOf(CommandFlags.class))));
} }

View File

@@ -15,35 +15,76 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
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.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actions.EnterAction import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.EditorMouseEvent import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener import com.intellij.openapi.editor.event.EditorMouseListener
import com.intellij.openapi.editor.impl.TextRangeInterval
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiUtilBase import com.intellij.psi.util.PsiUtilBase
import com.intellij.util.containers.ContainerUtil
import com.maddyhome.idea.vim.EventFacade import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.api.BufferPosition
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.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroupBase import com.maddyhome.idea.vim.api.VimChangeGroupBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimMotionGroupBase
import com.maddyhome.idea.vim.api.anyNonWhitespace
import com.maddyhome.idea.vim.api.getLineEndForOffset import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.lineLength
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.group.MotionGroup.Companion.getMotionRange2
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.handler.commandContinuation import com.maddyhome.idea.vim.handler.commandContinuation
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.NumberType
import com.maddyhome.idea.vim.helper.SearchHelper
import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.inInsertMode import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
import com.maddyhome.idea.vim.listener.VimInsertListener import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
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.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly
import java.math.BigInteger
import java.util.*
import java.util.function.Consumer
import kotlin.math.max
/** /**
* Provides all the insert/replace related functionality * Provides all the insert/replace related functionality
*/ */
class ChangeGroup : VimChangeGroupBase() { public class ChangeGroup : VimChangeGroupBase() {
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) {
val editor = event.editor val editor = event.editor
@@ -53,7 +94,7 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
fun editorCreated(editor: Editor?, disposable: Disposable) { public fun editorCreated(editor: Editor?, disposable: Disposable) {
EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable) EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
} }
@@ -61,9 +102,6 @@ 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()
@@ -103,6 +141,163 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
override fun getDeleteRangeAndType2(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
isChange: Boolean,
operatorArguments: OperatorArguments,
): Pair<TextRange, SelectionType>? {
val range = getMotionRange2(
(editor as IjVimEditor).editor,
(caret as IjVimCaret).caret,
(context as IjEditorExecutionContext).context,
argument,
operatorArguments
)
?: return null
// Delete motion commands that are not linewise become linewise if all the following are true:
// 1) The range is across multiple lines
// 2) There is only whitespace before the start of the range
// 3) There is only whitespace after the end of the range
var type: SelectionType
type = if (argument.motion.isLinewiseMotion()) {
SelectionType.LINE_WISE
} else {
SelectionType.CHARACTER_WISE
}
val motion = argument.motion
if (!isChange && !motion.isLinewiseMotion()) {
val start = editor.offsetToBufferPosition(range.startOffset)
val end = editor.offsetToBufferPosition(range.endOffset)
if (start.line != end.line) {
val offset1 = range.startOffset
if (!editor.anyNonWhitespace(offset1, -1)) {
val offset = range.endOffset
if (!editor.anyNonWhitespace(offset, 1)) {
type = SelectionType.LINE_WISE
}
}
}
}
return Pair(range, type)
}
/**
* Toggles the case of count characters
*
* @param editor The editor to change
* @param caret The caret on which the operation is performed
* @param count The number of characters to change
* @return true if able to change count characters
*/
override fun changeCaseToggleCharacter(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
val allowWrap = injector.options(editor).whichwrap.contains("~")
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
if (motion is Motion.Error) return false
changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
motion = injector.motion.getHorizontalMotion(
editor,
caret,
count,
false,
allowWrap
) // same but without allow end because we can change till end, but can't move caret there
if (motion is AbsoluteOffset) {
caret.moveToOffset(editor.normalizeOffset(motion.offset, false))
}
return true
}
override fun blockInsert(
editor: VimEditor,
context: ExecutionContext,
range: TextRange,
append: Boolean,
operatorArguments: OperatorArguments,
): Boolean {
val lines = getLinesCountInVisualBlock(editor, range)
val startPosition = editor.offsetToBufferPosition(range.startOffset)
val mode = operatorArguments.mode
val visualBlockMode = mode is VISUAL && mode.selectionType === SelectionType.BLOCK_WISE
for (caret in editor.carets()) {
val line = startPosition.line
var column = startPosition.column
if (!visualBlockMode) {
column = 0
} else if (append) {
column += range.maxLength
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
column = VimMotionGroupBase.LAST_COLUMN
}
}
val lineLength = editor.lineLength(line)
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column)
val offset = editor.getLineEndOffset(line)
insertText(editor, caret, offset, pad)
}
if (visualBlockMode || !append) {
(caret as IjVimCaret).caret.moveToInlayAwareLogicalPosition(LogicalPosition(line, column))
}
if (visualBlockMode) {
setInsertRepeat(lines, column, append)
}
}
if (visualBlockMode || !append) {
insertBeforeCursor(editor, context)
} else {
insertAfterCursor(editor, context)
}
return true
}
/**
* Changes the case of all the characters in the range
*
* @param editor The editor to change
* @param caret The caret to be moved
* @param range The range to change
* @param type The case change type (TOGGLE, UPPER, LOWER)
* @return true if able to delete the text, false if not
*/
override fun changeCaseRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: Char): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
for (i in ends.indices.reversed()) {
changeCase(editor, caret, starts[i], ends[i], type)
}
caret.moveToOffset(range.startOffset)
return true
}
/**
* This performs the actual case change.
*
* @param editor The editor to change
* @param start The start offset to change
* @param end The end offset to change
* @param type The type of change (TOGGLE, UPPER, LOWER)
*/
private fun changeCase(editor: VimEditor, caret: VimCaret, start: Int, end: Int, type: Char) {
var start = start
var end = end
if (start > end) {
val t = end
end = start
start = t
}
end = editor.normalizeOffset(end, true)
val chars = editor.text()
val sb = StringBuilder()
for (i in start until end) {
sb.append(changeCase(chars[i], type))
}
replaceText(editor, caret, start, end, sb.toString())
}
private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) { private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) {
if (caret != editor.primaryCaret()) { if (caret != editor.primaryCaret()) {
(editor as IjVimEditor).editor.caretModel.addCaret( (editor as IjVimEditor).editor.caretModel.addCaret(
@@ -111,13 +306,88 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
override fun reformatCode(editor: VimEditor, start: Int, end: Int) { /**
* Changes the case of all the character moved over by the motion argument.
*
* @param editor The editor to change
* @param caret The caret on which motion pretends to be performed
* @param context The data context
* @param type The case change type (TOGGLE, UPPER, LOWER)
* @param argument The motion command
* @return true if able to delete the text, false if not
*/
override fun changeCaseMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext?,
type: Char,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
val range = injector.motion.getMotionRange(
editor, caret, context!!, argument,
operatorArguments
)
return range != null && changeCaseRange(editor, caret, range, type)
}
override fun reformatCodeMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
val range = injector.motion.getMotionRange(
editor, caret, context, argument,
operatorArguments
)
return range != null && reformatCodeRange(editor, caret, range)
}
override fun reformatCodeSelection(editor: VimEditor, caret: VimCaret, range: VimSelection) {
val textRange = range.toVimTextRange(true)
reformatCodeRange(editor, caret, textRange)
}
private fun reformatCodeRange(editor: VimEditor, caret: VimCaret, range: TextRange): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
val firstLine = editor.offsetToBufferPosition(range.startOffset).line
for (i in ends.indices.reversed()) {
val startOffset = editor.getLineStartForOffset(starts[i])
val offset = ends[i] - if (startOffset == ends[i]) 0 else 1
val endOffset = editor.getLineEndForOffset(offset)
reformatCode(editor, startOffset, endOffset)
}
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
return true
}
private fun reformatCode(editor: VimEditor, start: Int, end: Int) {
val project = (editor as IjVimEditor).editor.project ?: return val project = (editor as IjVimEditor).editor.project ?: return
val file = PsiUtilBase.getPsiFileInEditor(editor.editor, project) ?: return val file = PsiUtilBase.getPsiFileInEditor(editor.editor, project) ?: return
val textRange = com.intellij.openapi.util.TextRange.create(start, end) val textRange = com.intellij.openapi.util.TextRange.create(start, end)
CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange)) CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange))
} }
override fun autoIndentMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
operatorArguments: OperatorArguments,
) {
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
if (range != null) {
autoIndentRange(
editor, caret, context,
TextRange(range.startOffset, range.endOffsetInclusive)
)
}
}
override fun autoIndentRange( override fun autoIndentRange(
editor: VimEditor, editor: VimEditor,
caret: VimCaret, caret: VimCaret,
@@ -172,14 +442,362 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
@Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.add", imports = ["import com.maddyhome.idea.vim.api.injector"])) override fun indentLines(
fun addInsertListener(listener: VimInsertListener) { editor: VimEditor,
injector.listenersNotifier.modeChangeListeners.add(listener) caret: VimCaret,
context: ExecutionContext,
lines: Int,
dir: Int,
operatorArguments: OperatorArguments,
) {
val start = caret.offset
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
} }
@Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.remove", imports = ["import com.maddyhome.idea.vim.api.injector"])) override fun indentMotion(
fun removeInsertListener(listener: VimInsertListener) { editor: VimEditor,
injector.listenersNotifier.modeChangeListeners.remove(listener) caret: VimCaret,
context: ExecutionContext,
argument: Argument,
dir: Int,
operatorArguments: OperatorArguments,
) {
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
if (range != null) {
indentRange(editor, caret, context, range, 1, dir, operatorArguments)
}
}
override fun indentRange(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
range: TextRange,
count: Int,
dir: Int,
operatorArguments: OperatorArguments,
) {
if (logger.isDebugEnabled) {
logger.debug("count=$count")
}
// Remember the current caret column
val intendedColumn = caret.vimLastColumn
val indentConfig = create((editor as IjVimEditor).editor)
val sline = editor.offsetToBufferPosition(range.startOffset).line
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
.toInt() else endLogicalPosition.line
if (range.isMultiple) {
val from = editor.offsetToBufferPosition(range.startOffset).column
if (dir == 1) {
// Right shift blockwise selection
val indent = indentConfig.createIndentByCount(count)
for (l in sline..eline) {
val len = editor.lineLength(l)
if (len > from) {
val spos = BufferPosition(l, from, false)
insertText(editor, caret, spos, indent)
}
}
} else {
// Left shift blockwise selection
val chars = editor.text()
for (l in sline..eline) {
val len = editor.lineLength(l)
if (len > from) {
val spos = BufferPosition(l, from, false)
val epos = BufferPosition(l, from + indentConfig.getTotalIndent(count) - 1, false)
val wsoff = editor.bufferPositionToOffset(spos)
val weoff = editor.bufferPositionToOffset(epos)
var pos: Int
pos = wsoff
while (pos <= weoff) {
if (charType(editor, chars[pos], false) !== CharacterHelper.CharacterType.WHITESPACE) {
break
}
pos++
}
if (pos > wsoff) {
deleteText(editor, TextRange(wsoff, pos), null, caret, operatorArguments, true)
}
}
}
}
} else {
// Shift non-blockwise selection
for (l in sline..eline) {
val soff = editor.getLineStartOffset(l)
val eoff = editor.getLineEndOffset(l, true)
val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l)
val col = editor.offsetToBufferPosition(woff).column
val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble())
.toInt()
if (col > 0 || soff != eoff) {
val indent = indentConfig.createIndentBySize(limit)
replaceText(editor, caret, soff, woff, indent)
}
}
}
if (!editor.editor.inInsertMode) {
if (!range.isMultiple) {
// The caret has moved, so reset the intended column before trying to get the expected offset
val newCaret = caret.setVimLastColumnAndGetCaret(intendedColumn)
val offset = injector.motion.moveCaretToLineWithStartOfLineOption(editor, sline, caret)
newCaret.moveToOffset(offset)
} else {
caret.moveToOffset(range.startOffset)
}
}
}
/**
* Sort range of text with a given comparator
*
* @param editor The editor to replace text in
* @param range The range to sort
* @param lineComparator The comparator to use to sort
* @param sortOptions The option to sort the range
* @return true if able to sort the text, false if not
*/
override fun sortRange(
editor: VimEditor, caret: VimCaret, range: LineRange, lineComparator: Comparator<String>,
sortOptions: SortOption,
): Boolean {
val startLine = range.startLine
val endLine = range.endLine
val count = range.size
if (count < 2) {
return false
}
val startOffset = editor.getLineStartOffset(startLine)
val endOffset = editor.getLineEndOffset(endLine)
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(startOffset, endOffset))
val lines = selectedText.split("\n")
val modifiedLines = sortOptions.pattern?.let {
if (sortOptions.sortOnPattern) {
extractPatternFromLines(editor, lines, startLine, it)
} else {
deletePatternFromLines(editor, lines, startLine, it)
}
} ?: lines
val sortedLines = lines.zip(modifiedLines)
.sortedWith { l1, l2 -> lineComparator.compare(l1.second, l2.second) }
.map {it.first}
.toMutableList()
if (sortOptions.unique) {
val iterator = sortedLines.iterator()
var previous: String? = null
while (iterator.hasNext()) {
val current = iterator.next()
if (current == previous || sortOptions.ignoreCase && current.equals(previous, ignoreCase = true)) {
iterator.remove()
} else {
previous = current
}
}
}
if (sortedLines.isEmpty()) {
return false
}
replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n"))
return true
}
private fun extractPatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> result.value
is VimMatchResult.Failure -> line
}
}
}
private fun deletePatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> line.substring(result.value.length, line.length)
is VimMatchResult.Failure -> line
}
}
}
/**
* Perform increment and decrement for numbers in visual mode
*
*
* Flag [avalanche] marks if increment (or decrement) should be performed in avalanche mode
* (for v_g_Ctrl-A and v_g_Ctrl-X commands)
*
* @return true
*/
override fun changeNumberVisualMode(
editor: VimEditor,
caret: VimCaret,
selectedRange: TextRange,
count: Int,
avalanche: Boolean,
): Boolean {
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val numberRanges = SearchHelper.findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
val newNumbers: MutableList<String?> = ArrayList()
for (i in numberRanges.indices) {
val numberRange = numberRanges[i]
val iCount = if (avalanche) (i + 1) * count else count
val newNumber = changeNumberInRange(editor, numberRange, iCount, alpha, hex, octal)
newNumbers.add(newNumber)
}
for (i in newNumbers.indices.reversed()) {
// Replace text bottom up. In other direction ranges will be desynchronized after inc numbers like 99
val (first) = numberRanges[i]
val newNumber = newNumbers[i]
replaceText(editor, caret, first.startOffset, first.endOffset, newNumber!!)
}
(caret as IjVimCaret).caret.moveToInlayAwareOffset(selectedRange.startOffset)
return true
}
override fun changeNumber(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val range =
SearchHelper.findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
if (range == null) {
logger.debug("no number on line")
return false
}
val newNumber = changeNumberInRange(editor, range, count, alpha, hex, octal)
return if (newNumber == null) {
false
} else {
replaceText(editor, caret, range.first.startOffset, range.first.endOffset, newNumber)
caret.caret.moveToInlayAwareOffset(range.first.startOffset + newNumber.length - 1)
true
}
}
override fun reset() {
strokes.clear()
repeatCharsCount = 0
if (lastStrokes != null) {
lastStrokes!!.clear()
}
}
override fun saveStrokes(newStrokes: String?) {
val chars = newStrokes!!.toCharArray()
strokes.add(chars)
}
private fun changeNumberInRange(
editor: VimEditor,
range: Pair<TextRange, NumberType>,
count: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): String? {
val text = editor.getText(range.first)
val numberType = range.second
if (logger.isDebugEnabled) {
logger.debug("found range $range")
logger.debug("text=$text")
}
var number = text
if (text.isEmpty()) {
return null
}
var ch = text[0]
if (hex && NumberType.HEX == numberType) {
if (!text.lowercase(Locale.getDefault()).startsWith(HEX_START)) {
throw RuntimeException("Hex number should start with 0x: $text")
}
for (i in text.length - 1 downTo 2) {
val index = "abcdefABCDEF".indexOf(text[i])
if (index >= 0) {
lastLower = index < 6
break
}
}
var num = BigInteger(text.substring(2), 16)
num = num.add(BigInteger.valueOf(count.toLong()))
if (num.compareTo(BigInteger.ZERO) < 0) {
num = BigInteger(MAX_HEX_INTEGER, 16).add(BigInteger.ONE).add(num)
}
number = num.toString(16)
number = number.padStart(text.length - 2, '0')
if (!lastLower) {
number = number.uppercase(Locale.getDefault())
}
number = text.substring(0, 2) + number
} else if (octal && NumberType.OCT == numberType && text.length > 1) {
if (!text.startsWith("0")) throw RuntimeException("Oct number should start with 0: $text")
var num = BigInteger(text, 8).add(BigInteger.valueOf(count.toLong()))
if (num.compareTo(BigInteger.ZERO) < 0) {
num = BigInteger("1777777777777777777777", 8).add(BigInteger.ONE).add(num)
}
number = num.toString(8)
number = "0" + number.padStart(text.length - 1, '0')
} else if (alpha && NumberType.ALPHA == numberType) {
if (!Character.isLetter(ch)) throw RuntimeException("Not alpha number : $text")
ch += count.toChar().code
if (Character.isLetter(ch)) {
number = ch.toString()
}
} else if (NumberType.DEC == numberType) {
if (ch != '-' && !Character.isDigit(ch)) throw RuntimeException("Not dec number : $text")
var pad = ch == '0'
var len = text.length
if (ch == '-' && text[1] == '0') {
pad = true
len--
}
var num = BigInteger(text)
num = num.add(BigInteger.valueOf(count.toLong()))
number = num.toString()
if (!octal && pad) {
var neg = false
if (number[0] == '-') {
neg = true
number = number.substring(1)
}
number = number.padStart(len, '0')
if (neg) {
number = "-$number"
}
}
}
return number
}
public fun addInsertListener(listener: VimInsertListener) {
insertListeners.add(listener)
}
public fun removeInsertListener(listener: VimInsertListener) {
insertListeners.remove(listener)
}
override fun notifyListeners(editor: VimEditor) {
insertListeners.forEach(Consumer { listener: VimInsertListener -> listener.insertModeStarted((editor as IjVimEditor).editor) })
}
@TestOnly
override fun resetRepeat() {
setInsertRepeat(0, 0, false)
} }
private companion object { private companion object {

View File

@@ -8,7 +8,9 @@
package com.maddyhome.idea.vim.group; package com.maddyhome.idea.vim.group;
import com.intellij.execution.impl.ConsoleViewImpl;
import com.intellij.find.EditorSearchSession; import com.intellij.find.EditorSearchSession;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.client.ClientAppSession; import com.intellij.openapi.client.ClientAppSession;
import com.intellij.openapi.client.ClientKind; import com.intellij.openapi.client.ClientKind;
import com.intellij.openapi.client.ClientSessionsManager; import com.intellij.openapi.client.ClientSessionsManager;
@@ -18,13 +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.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.EditorHelper; import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.UserDataManager; import com.maddyhome.idea.vim.helper.UserDataManager;
import com.maddyhome.idea.vim.newapi.IjVimDocument; import com.maddyhome.idea.vim.newapi.IjVimDocument;
@@ -36,8 +38,6 @@ 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,7 +45,6 @@ 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;
/** /**
@@ -209,14 +208,44 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
initLineNumbers(editor); initLineNumbers(editor);
// Listen for changes to the font size, so we can hide the ex text field/output panel // We add Vim bindings to all opened editors, even read-only editors. We also add bindings to editors that are used
if (editor instanceof EditorEx editorEx) { // elsewhere in the IDE, rather than just for editing project files. This includes editors used as part of the UI,
editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE); // 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.
if (injector.getApplication().isUnitTest()) { // We want to provide an intuitive experience for working with these additional editors, so we automatically switch
updateCaretsVisualAttributes(new IjVimEditor(editor)); // to INSERT mode for interactive editors. Recognising these can be a bit tricky.
// These additional interactive editors are not file-based, but must have a writable document. However, log output
// documents are also writable (the IDE is writing new content as it becomes available) just not user-editable. So
// we must also check that the editor is not in read-only "viewer" mode (this includes "rendered" mode, which is
// read-only and also hides the caret).
// Furthermore, the interactive stdin/stdout console output is hosted in a read-only editor, but it can still be
// edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's `isViewer` property and
// allows typing if the associated process (if any) is still running. We can get the editor's console view and check
// this ourselves, but we have to wait until the editor has finished initialising before it's available in user
// data.
// Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
Runnable switchToInsertMode = () -> {
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor));
VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
KeyHandler.getInstance().reset(new IjVimEditor(editor));
};
if (!editor.isViewer() &&
!EditorHelper.isFileEditor(editor) &&
editor.getDocument().isWritable() &&
!CommandStateHelper.inInsertMode(editor)) {
switchToInsertMode.run();
} }
ApplicationManager.getApplication().invokeLater(
() -> {
if (editor.isDisposed()) return;
ConsoleViewImpl consoleView = editor.getUserData(ConsoleViewImpl.CONSOLE_VIEW_IN_EDITOR_VIEW);
if (consoleView != null && consoleView.isRunning() && !CommandStateHelper.inInsertMode(editor)) {
switchToInsertMode.run();
}
});
updateCaretsVisualAttributes(new IjVimEditor(editor));
} }
public void editorDeinit(@NotNull Editor editor) { public void editorDeinit(@NotNull Editor editor) {
@@ -224,9 +253,6 @@ 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) {
@@ -238,8 +264,9 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
VimPlugin.getNotifications(project).notifyAboutIdeaJoin(editor); VimPlugin.getNotifications(project).notifyAboutIdeaJoin(editor);
} }
@Nullable
@Override @Override
public @Nullable Element getState() { public Element getState() {
Element element = new Element("editor"); Element element = new Element("editor");
saveData(element); saveData(element);
return element; return element;
@@ -288,7 +315,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 = options(injector, ijVimEditor).getNumber(); final boolean number = ijOptions(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
@@ -315,16 +342,18 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@NotNull
@Override @Override
public @NotNull Collection<VimEditor> getEditors() { public Collection<VimEditor> getEditors() {
return getLocalEditors() return getLocalEditors()
.filter(UserDataManager::getVimInitialised) .filter(UserDataManager::getVimInitialised)
.map(IjVimEditor::new) .map(IjVimEditor::new)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@NotNull
@Override @Override
public @NotNull Collection<VimEditor> getEditors(@NotNull VimDocument buffer) { public Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument(); final Document document = ((IjVimDocument)buffer).getDocument();
return getLocalEditors() return getLocalEditors()
.filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document)) .filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
@@ -344,7 +373,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// events such as document change (to update search highlights), and these can come from CWM guests, and we'd get // events such as document change (to update search highlights), and these can come from CWM guests, and we'd get
// the remote editors. // the remote editors.
// This invocation will always get local editors, regardless of the current context. // This invocation will always get local editors, regardless of the current context.
List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.LOCAL); List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.ALL);
if (!appSessions.isEmpty()) { if (!appSessions.isEmpty()) {
ClientAppSession localSession = appSessions.get(0); ClientAppSession localSession = appSessions.get(0);
return localSession.getService(ClientEditorManager.class).editors(); return localSession.getService(ClientEditorManager.class).editors();
@@ -353,37 +382,4 @@ 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) {
activeCommandLine.close(true, false);
}
ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor);
if (exOutputModel != null) {
exOutputModel.close();
}
VimModalInput modalInput = injector.getModalInput().getCurrentModalInput();
if (modalInput != null) {
modalInput.deactivate(true, false);
}
}
}
}
}
} }

View File

@@ -0,0 +1,23 @@
/*
* 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()
}
}

View File

@@ -0,0 +1,476 @@
/*
* 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();
}
}

View File

@@ -1,444 +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.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(editor: VimEditor, context: ExecutionContext) {
val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) {
injector.nativeActionManager.saveAll
} else {
injector.nativeActionManager.saveCurrent
}
action.execute(editor, context)
}
/**
* Saves all files in the project.
*/
override fun saveFiles(editor: VimEditor, context: ExecutionContext) {
injector.nativeActionManager.saveAll.execute(editor, context)
}
/**
* Selects then next or previous editor.
*/
override fun selectFile(count: Int, context: ExecutionContext): Boolean {
var count = count
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return false
val fem = FileEditorManager.getInstance(project) // API change - don't merge
val editors = fem.openFiles
if (count == 99) {
count = editors.size - 1
}
if (count < 0 || count >= editors.size) {
return false
}
fem.openFile(editors[count], true)
return true
}
/**
* Selects then next or previous editor.
*/
override fun selectNextFile(count: Int, context: ExecutionContext) {
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return
val fem = FileEditorManager.getInstance(project) // API change - don't merge
val editors = fem.openFiles
val current = fem.selectedFiles[0]
for (i in editors.indices) {
if (editors[i] == current) {
val pos = (i + (count % editors.size) + editors.size) % editors.size
fem.openFile(editors[pos], true)
}
}
}
/**
* Selects previous editor tab.
*/
override fun selectPreviousTab(context: ExecutionContext) {
val project = PlatformDataKeys.PROJECT.getData((context.context as DataContext)) ?: return
val vf = getInstance(project).lastTab
if (vf != null && vf.isValid) {
FileEditorManager.getInstance(project).openFile(vf, true)
} else {
VimPlugin.indicateError()
}
}
/**
* Returns the previous tab.
*/
fun getPreviousTab(context: DataContext): VirtualFile? {
val project = PlatformDataKeys.PROJECT.getData(context) ?: return null
val vf = getInstance(project).lastTab
if (vf != null && vf.isValid) {
return vf
}
return null
}
fun selectEditor(project: Project, file: VirtualFile): Editor? {
val fMgr = FileEditorManager.getInstance(project)
val feditors = fMgr.openFile(file, true)
if (feditors.size > 0) {
if (feditors[0] is TextEditor) {
val editor = (feditors[0] as TextEditor).editor
if (!editor.isDisposed) {
return editor
}
}
}
return null
}
override fun displayLocationInfo(vimEditor: VimEditor) {
val editor = (vimEditor as IjVimEditor).editor
val msg = StringBuilder()
val doc = editor.document
if (injector.vimState.mode !is VISUAL) {
val lp = editor.caretModel.logicalPosition
val col = editor.caretModel.offset - doc.getLineStartOffset(lp.line)
var endoff = doc.getLineEndOffset(lp.line)
if (endoff < editor.fileSize && doc.charsSequence[endoff] == '\n') {
endoff--
}
val ecol = endoff - doc.getLineStartOffset(lp.line)
val elp = editor.offsetToLogicalPosition(endoff)
msg.append("Col ").append(col + 1)
if (col != lp.column) {
msg.append("-").append(lp.column + 1)
}
msg.append(" of ").append(ecol + 1)
if (ecol != elp.column) {
msg.append("-").append(elp.column + 1)
}
val lline = editor.caretModel.logicalPosition.line
val total = IjVimEditor(editor).lineCount()
msg.append("; Line ").append(lline + 1).append(" of ").append(total)
val cp = countWords(vimEditor)
msg.append("; Word ").append(cp.position).append(" of ").append(cp.count)
val offset = editor.caretModel.offset
val size = editor.fileSize
msg.append("; Character ").append(offset + 1).append(" of ").append(size)
} else {
msg.append("Selected ")
val vr = TextRange(
editor.selectionModel.blockSelectionStarts,
editor.selectionModel.blockSelectionEnds
)
vr.normalize()
val lines: Int
var cp = countWords(vimEditor)
val words = cp.count
var word = 0
if (vr.isMultiple) {
lines = vr.size()
val cols = vr.maxLength
msg.append(cols).append(" Cols; ")
for (i in 0 until vr.size()) {
cp = countWords(vimEditor, vr.startOffsets[i], (vr.endOffsets[i] - 1).toLong())
word += cp.count
}
} else {
val slp = editor.offsetToLogicalPosition(vr.startOffset)
val elp = editor.offsetToLogicalPosition(vr.endOffset)
lines = elp.line - slp.line + 1
cp = countWords(vimEditor, vr.startOffset, (vr.endOffset - 1).toLong())
word = cp.count
}
val total = IjVimEditor(editor).lineCount()
msg.append(lines).append(" of ").append(total).append(" Lines")
msg.append("; ").append(word).append(" of ").append(words).append(" Words")
val chars = vr.selectionCount
val size = editor.fileSize
msg.append("; ").append(chars).append(" of ").append(size).append(" Characters")
}
VimPlugin.showMessage(msg.toString())
}
override fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean) {
val editor = (vimEditor as IjVimEditor).editor
val msg = StringBuilder()
val vf = EditorHelper.getVirtualFile(editor)
if (vf != null) {
msg.append('"')
if (fullPath) {
msg.append(vf.path)
} else {
val project = editor.project
if (project != null) {
val root = ProjectRootManager.getInstance(project).fileIndex.getContentRootForFile(vf)
if (root != null) {
msg.append(vf.path.substring(root.path.length + 1))
} else {
msg.append(vf.path)
}
}
}
msg.append("\" ")
} else {
msg.append("\"[No File]\" ")
}
val doc = editor.document
if (!doc.isWritable) {
msg.append("[RO] ")
} else if (FileDocumentManager.getInstance().isDocumentUnsaved(doc)) {
msg.append("[+] ")
}
val lline = editor.caretModel.logicalPosition.line
val total = IjVimEditor(editor).lineCount()
val pct = (lline.toFloat() / total.toFloat() * 100f + 0.5).toInt()
msg.append("line ").append(lline + 1).append(" of ").append(total)
msg.append(" --").append(pct).append("%-- ")
val lp = editor.caretModel.logicalPosition
val col = editor.caretModel.offset - doc.getLineStartOffset(lline)
msg.append("col ").append(col + 1)
if (col != lp.column) {
msg.append("-").append(lp.column + 1)
}
VimPlugin.showMessage(msg.toString())
}
override fun selectEditor(projectId: String, documentPath: String, protocol: String?): VimEditor? {
val fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol) ?: return null
val virtualFile = fileSystem.findFileByPath(documentPath) ?: return null
val project = Arrays.stream(ProjectManager.getInstance().openProjects)
.filter { p: Project? -> injector.file.getProjectId(p!!) == projectId }
.findFirst().orElseThrow()
val editor = selectEditor(project, virtualFile) ?: return null
return IjVimEditor(editor)
}
override fun getProjectId(project: Any): String {
require(project is Project)
return project.name + "-" + project.locationHash
}
companion object {
private fun findByNameInProject(filename: String, project: Project): VirtualFile? {
val projectScope = ProjectScope.getProjectScope(project)
val names = FilenameIndex.getVirtualFilesByName(filename, projectScope)
if (!names.isEmpty()) {
return names.stream().findFirst().get()
}
return null
}
private val logger = Logger.getInstance(
FileGroup::class.java.name
)
/**
* Respond to editor tab selection and remember the last used tab
*/
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
if (event.oldFile != null) {
getInstance(event.manager.project).lastTab = event.oldFile
}
}
}
}

View File

@@ -19,22 +19,24 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
* options * options
*/ */
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) { public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
var ide: String by optionProperty(IjOptions.ide) public var ide: String by optionProperty(IjOptions.ide)
var ideamarks: Boolean by optionProperty(IjOptions.ideamarks) public var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon) public var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
val ideavimsupport: StringListOptionValue by optionProperty(IjOptions.ideavimsupport) public val ideavimsupport: StringListOptionValue by optionProperty(IjOptions.ideavimsupport)
var ideawrite: String by optionProperty(IjOptions.ideawrite) public var ideawrite: String by optionProperty(IjOptions.ideawrite)
val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys) public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
var trackactionids: Boolean by optionProperty(IjOptions.trackactionids) public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
var visualdelay: Int by optionProperty(IjOptions.visualdelay) public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
// Temporary options to control work-in-progress behaviour // Temporary options to control work-in-progress behaviour
var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks) public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation) public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
var oldundo: Boolean by optionProperty(IjOptions.oldundo) public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps) public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation) public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
} }
/** /**
@@ -42,19 +44,20 @@ open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(sco
* *
* 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.
*/ */
class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOptions(scope) { public 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
var breakindent: Boolean by optionProperty(IjOptions.breakindent) public var breakindent: Boolean by optionProperty(IjOptions.breakindent)
val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn) public val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
var cursorline: Boolean by optionProperty(IjOptions.cursorline) public var cursorline: Boolean by optionProperty(IjOptions.cursorline)
var fileformat: String by optionProperty(IjOptions.fileformat) public var fileformat: String by optionProperty(IjOptions.fileformat)
var list: Boolean by optionProperty(IjOptions.list) public var list: Boolean by optionProperty(IjOptions.list)
var relativenumber: Boolean by optionProperty(IjOptions.relativenumber) public var number: Boolean by optionProperty(IjOptions.number)
var textwidth: Int by optionProperty(IjOptions.textwidth) public var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
var wrap: Boolean by optionProperty(IjOptions.wrap) public var textwidth: Int by optionProperty(IjOptions.textwidth)
public var wrap: Boolean by optionProperty(IjOptions.wrap)
// IntelliJ specific options // IntelliJ specific options
var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess) public var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
var ideajoin: Boolean by optionProperty(IjOptions.ideajoin) public var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
var idearefactormode: String by optionProperty(IjOptions.idearefactormode) public var idearefactormode: String by optionProperty(IjOptions.idearefactormode)
} }

View File

@@ -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")
object IjOptions { public object IjOptions {
fun initialise() { public 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 @@ 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
val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false)) public val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") { public 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,18 +55,19 @@ object IjOptions {
} }
} }
}) })
val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false)) public val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false)) public val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false)) public val number: ToggleOption = addOption(ToggleOption("number", LOCAL_TO_WINDOW, "nu", false))
val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0)) public val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
val wrap: ToggleOption = addOption(ToggleOption("wrap", LOCAL_TO_WINDOW, "wrap", true)) public val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
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 conversion, we treat them as // based on the value of 'fileformats' or 'fileencodings'. To prevent unexpected file cnversion, 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'`
val bomb: ToggleOption = public val bomb: ToggleOption =
addOption(ToggleOption("bomb", LOCAL_TO_BUFFER, "bomb", false, isLocalNoGlobal = true)) addOption(ToggleOption("bomb", LOCAL_TO_BUFFER, "bomb", false, isLocalNoGlobal = true))
val fileencoding: StringOption = addOption( public val fileencoding: StringOption = addOption(
StringOption( StringOption(
"fileencoding", "fileencoding",
LOCAL_TO_BUFFER, LOCAL_TO_BUFFER,
@@ -75,7 +76,7 @@ object IjOptions {
isLocalNoGlobal = true isLocalNoGlobal = true
) )
) )
val fileformat: StringOption = addOption( public val fileformat: StringOption = addOption(
StringOption( StringOption(
"fileformat", "fileformat",
LOCAL_TO_BUFFER, LOCAL_TO_BUFFER,
@@ -87,15 +88,15 @@ object IjOptions {
) )
// IntelliJ specific functionality - custom options // IntelliJ specific functionality - custom options
val ide: StringOption = addOption( public val ide: StringOption = addOption(
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition) StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
) )
val ideacopypreprocess: ToggleOption = addOption( public val ideacopypreprocess: ToggleOption = addOption(
ToggleOption("ideacopypreprocess", GLOBAL_OR_LOCAL_TO_BUFFER, "ideacopypreprocess", false) ToggleOption("ideacopypreprocess", GLOBAL_OR_LOCAL_TO_BUFFER, "ideacopypreprocess", false)
) )
val ideajoin: ToggleOption = addOption(ToggleOption("ideajoin", GLOBAL_OR_LOCAL_TO_BUFFER, "ideajoin", false)) public val ideajoin: ToggleOption = addOption(ToggleOption("ideajoin", GLOBAL_OR_LOCAL_TO_BUFFER, "ideajoin", false))
val ideamarks: ToggleOption = addOption(ToggleOption("ideamarks", GLOBAL, "ideamarks", true)) public val ideamarks: ToggleOption = addOption(ToggleOption("ideamarks", GLOBAL, "ideamarks", true))
val idearefactormode: StringOption = addOption( public val idearefactormode: StringOption = addOption(
StringOption( StringOption(
"idearefactormode", "idearefactormode",
GLOBAL_OR_LOCAL_TO_BUFFER, GLOBAL_OR_LOCAL_TO_BUFFER,
@@ -104,7 +105,7 @@ object IjOptions {
IjOptionConstants.ideaRefactorModeValues IjOptionConstants.ideaRefactorModeValues
) )
) )
val ideastatusicon: StringOption = addOption( public val ideastatusicon: StringOption = addOption(
StringOption( StringOption(
"ideastatusicon", "ideastatusicon",
GLOBAL, GLOBAL,
@@ -113,7 +114,7 @@ object IjOptions {
IjOptionConstants.ideaStatusIconValues IjOptionConstants.ideaStatusIconValues
) )
) )
val ideavimsupport: StringListOption = addOption( public val ideavimsupport: StringListOption = addOption(
StringListOption( StringListOption(
"ideavimsupport", "ideavimsupport",
GLOBAL, GLOBAL,
@@ -122,26 +123,27 @@ object IjOptions {
IjOptionConstants.ideavimsupportValues IjOptionConstants.ideavimsupportValues
) )
) )
@JvmField @JvmField public val ideawrite: StringOption = addOption(
val ideawrite: StringOption = addOption(
StringOption("ideawrite", GLOBAL, "ideawrite", "all", IjOptionConstants.ideaWriteValues) StringOption("ideawrite", GLOBAL, "ideawrite", "all", IjOptionConstants.ideaWriteValues)
) )
val lookupkeys: StringListOption = addOption( public 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>")
) )
val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false)) public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100)) public 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
val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true)) public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true)) public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true)) public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true))
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true)) public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true)) public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which // 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>

View File

@@ -21,12 +21,12 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@Service @Service
class IjVimPsiService: VimPsiService { public 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
val language = psiElement.language val language = psiElement.language
val commenter = LanguageCommenters.INSTANCE.forLanguage(language) ?: return null val commenter = LanguageCommenters.INSTANCE.forLanguage(language)
val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null
val commentText = psiComment.text val commentText = psiComment.text

View File

@@ -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
class IjVimRedrawService : VimRedrawService { public 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 @@ class IjVimRedrawService : VimRedrawService {
injector.messages.clearStatusBarMessage() injector.messages.clearStatusBarMessage()
} }
companion object { public companion object {
/** /**
* Simulate Vim's redraw when the current editor changes * Simulate Vim's redraw when the current editor changes
*/ */
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) { public fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
injector.redrawService.redraw() injector.redrawService.redraw()
} }
} }
@@ -39,7 +39,7 @@ class IjVimRedrawService : VimRedrawService {
* *
* Only redraw if lines are added/removed. * Only redraw if lines are added/removed.
*/ */
object RedrawListener : DocumentListener { public 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")) {

View File

@@ -47,14 +47,12 @@ import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.listener.AppCodeTemplates import com.maddyhome.idea.vim.listener.AppCodeTemplates
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
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
import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.returnTo
import org.jetbrains.annotations.Range import org.jetbrains.annotations.Range
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@@ -309,32 +307,19 @@ internal class MotionGroup : VimMotionGroupBase() {
val editor = fileEditor.editor val editor = fileEditor.editor
if (!editor.isDisposed) { if (!editor.isDisposed) {
editor.vim.let { vimEditor -> editor.vim.let { vimEditor ->
when (vimEditor.mode) { when (vimEditor.vimStateMachine.mode) {
is Mode.VISUAL -> { is Mode.VISUAL -> {
vimEditor.exitVisualMode() vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor) KeyHandler.getInstance().reset(vimEditor)
} }
is Mode.CMD_LINE -> { is Mode.CMD_LINE -> {
val commandLine = injector.commandLine.getActiveCommandLine() ?: return injector.processGroup.cancelExEntry(vimEditor, false)
commandLine.close(refocusOwningEditor = false, resetCaret = false) ExOutputModel.getInstance(editor).clear()
ExOutputModel.tryGetInstance(editor)?.close()
} }
else -> {} else -> {}
} }
} }
} }
} else {
val state = injector.vimState as VimStateMachineImpl
if (state.mode is Mode.VISUAL) {
val returnTo = state.mode.returnTo
when (returnTo) {
ReturnTo.INSERT -> state.mode = Mode.INSERT
ReturnTo.REPLACE -> state.mode = Mode.REPLACE
null -> state.mode = Mode.NORMAL()
}
}
val keyHandler = KeyHandler.getInstance()
KeyHandler.getInstance().reset(keyHandler.keyHandlerState, state.mode)
} }
} }
} }

View File

@@ -62,25 +62,6 @@ internal class NotificationService(private val project: Project?) {
@Suppress("unused") @Suppress("unused")
constructor() : this(null) constructor() : this(null)
fun notifyAboutNewUndo() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
"Undo in IdeaVim now works like in Vim",
"""
Caret movement is no longer a separate undo step, and full insert is undoable in one step.
""".trimIndent(),
NotificationType.INFORMATION,
)
notification.addAction(object : DumbAwareAction("Share Feedback") {
override fun actionPerformed(p0: AnActionEvent) {
BrowserUtil.browse("https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks")
}
})
notification.notify(project)
}
fun notifyAboutIdeaPut() { fun notifyAboutIdeaPut() {
val notification = Notification( val notification = Notification(
IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_ID,
@@ -201,8 +182,8 @@ internal class NotificationService(private val project: Project?) {
).notify(project) ).notify(project)
} }
fun notifyActionId(id: String?, candidates: List<String>? = null) { fun notifyActionId(id: String?) {
ActionIdNotifier.notifyActionId(id, project, candidates) ActionIdNotifier.notifyActionId(id, project)
} }
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) { fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
@@ -278,31 +259,20 @@ internal class NotificationService(private val project: Project?) {
object ActionIdNotifier { object ActionIdNotifier {
private var notification: Notification? = null private var notification: Notification? = null
private const val NO_ID = "<i>Cannot detect action id</i>"
fun notifyActionId(id: String?, project: Project?, candidates: List<String>? = null) { fun notifyActionId(id: String?, project: Project?) {
notification?.expire() notification?.expire()
val possibleIDs = candidates?.distinct()?.sorted() val content = if (id != null) "Action id: $id" else NO_ID
val content = when { Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).let {
id != null -> "Action ID: <code>$id</code><br><br>" notification = it
possibleIDs.isNullOrEmpty() -> "<i>Cannot detect action ID</i><br><br>"
possibleIDs.size == 1 -> "Possible action ID: <code>${possibleIDs[0]}</code><br><br>"
else -> {
buildString {
append("<p>Multiple possible action IDs. Candidates include:<ul>")
possibleIDs.forEach { append("<li><code>$it</code></li>") }
append("</ul></p>")
}
}
} + "<small>See the ${ActionCenter.getToolwindowName()} tool window for previous IDs</small>"
notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also {
it.whenExpired { notification = null } it.whenExpired { notification = null }
it.setContent(it.content + "<br><br><small>Use ${ActionCenter.getToolwindowName()} to see previous ids</small>")
it.addAction(StopTracking()) it.addAction(StopTracking())
if (id != null || possibleIDs?.size == 1) { if (id != null) it.addAction(CopyActionId(id, project))
it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project))
}
it.notify(project) it.notify(project)
} }

View File

@@ -12,12 +12,10 @@ 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
@@ -96,12 +94,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))
@@ -928,9 +926,7 @@ 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) {
// Note that Vim supports -1 to -100 as a percentage value. IntelliJ does not have any validation, but does not editor.ij.settings.verticalScrollJump = value.value
// 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) {
@@ -979,35 +975,26 @@ 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( private class ScrollOffOptionMapper(option: NumberOption, internalOptionValueAccessor: InternalOptionValueAccessor)
scrollOffOption: NumberOption, : GlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(option, internalOptionValueAccessor) {
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
override fun getExternalGlobalValue() = // The IntelliJ setting is in practice global. The base implementation relies on this fact
EditorSettingsExternalizable.getInstance().verticalScrollOffset.asVimInt() override val canUserModifyExternalLocalValue: Boolean = false
override fun suppressExternalLocalValue(editor: VimEditor) { override fun getGlobalExternalValue() = EditorSettingsExternalizable.getInstance().verticalScrollOffset.asVimInt()
editor.ij.settings.verticalScrollOffset = 0 override fun getEffectiveExternalValue(editor: VimEditor) = editor.ij.settings.verticalScrollOffset.asVimInt()
override fun setLocalExternalValue(editor: VimEditor, value: VimInt) {
editor.ij.settings.verticalScrollOffset = value.value
}
override fun removeLocalExternalValue(editor: VimEditor) {
// Unexpectedly, verticalScrollOffset accepts `-1` as a value to clear any local overrides, and this will reset the
// effective value to return the global value
editor.ij.settings.verticalScrollOffset = -1
} }
} }
@@ -1015,14 +1002,15 @@ private class ScrollOffOptionMapper(
/** /**
* 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
* *
* IntelliJ supports horizontal scroll offset in a similar manner to Vim. However, the implementation calculates offsets * Ideally, we would implement this in a similar manner to [SideScrollOptionMapper], setting the external local
* using integer font sizes, which can lead to minor inaccuracies when compared to the IdeaVim implementation, such as * horizontal scroll offset value when the user explicitly sets the Vim value, so that IntelliJ could also use the
* differences running tests on a Mac. * value. Unfortunately, IntelliJ's scrolling calculation logic is based on integer font width maths, which causes
* problems with fractional font widths (such as on a Mac when running tests).
* *
* 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'` (jump), because IntelliJ doesn't support * need to scroll, which in turn can cause issues with `'sidescroll'`, because IntelliJ doesn't support `sidescroll=0`,
* `sidescroll=0`, which would scroll to position the caret in the middle of the display. * 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
@@ -1040,98 +1028,78 @@ private class ScrollOffOptionMapper(
* vim-engine so it can also work in Fleet. * vim-engine so it can also work in Fleet.
*/ */
private class SideScrollOffOptionMapper( private class SideScrollOffOptionMapper(
sideScrollOffOption: NumberOption, private val sideScrollOffOption: NumberOption,
internalOptionValueAccessor: InternalOptionValueAccessor, private val internalOptionValueAccessor: InternalOptionValueAccessor,
) : OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(sideScrollOffOption, internalOptionValueAccessor) { ) : GlobalOptionValueOverride<VimInt>, LocalOptionValueOverride<VimInt>, IdeaBackedOptionValueOverride {
override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_HORIZONTAL_SCROLL_OFFSET override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_HORIZONTAL_SCROLL_OFFSET
override fun getExternalGlobalValue() = override fun getGlobalValue(storedValue: OptionValue<VimInt>, editor: VimEditor?): OptionValue<VimInt> {
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(getExternalGlobalValue()) return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
} }
// If it's not the default value, it's got to be the stored value
return storedValue return storedValue
} }
override fun setGlobalValue(storedValue: OptionValue<T>, newValue: OptionValue<T>, editor: VimEditor?): Boolean { override fun setGlobalValue(
// The user is updating the global Vim value, via `:setglobal`. IdeaVim scrolling will be using this value. Make storedValue: OptionValue<VimInt>,
// sure the IntelliJ values won't interfere newValue: OptionValue<VimInt>,
// Note that we don't reset the local IntelliJ value for `:set {option}&` or `:set {option}<` because the current editor: VimEditor?,
// global IntelliJ value might still interfere with IdeaVim's implementation. We continue to suppress the IntelliJ ): Boolean {
// value. // The user has typed `:setlocal`. Just make sure that the IntelliJ value doesn't interfere with the Vim value
injector.editorGroup.getEditors().forEach { suppressExternalLocalValue(it) } injector.editorGroup.getEditors().forEach { it.ij.settings.horizontalScrollOffset = 0 }
return storedValue.value != newValue.value return storedValue.value != newValue.value
} }
override fun getLocalValue(storedValue: OptionValue<T>?, editor: VimEditor): OptionValue<T> { override fun getLocalValue(storedValue: OptionValue<VimInt>?, editor: VimEditor): OptionValue<VimInt> {
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. If it has been changed (unlikely if stored value hasn't been set yet), then it would be 0 // a way to set it, and we set it to 0 so that it doesn't affect our scroll calculations (because IntelliJ doesn't
return OptionValue.Default(getExternalGlobalValue()) // handle sidescroll=0 to mean half a page)
return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
} }
if (storedValue is OptionValue.Default && storedValue.value != option.unsetValue) { if (storedValue is OptionValue.Default && storedValue.value != sideScrollOffOption.unsetValue) {
// The local value has been reset to Default. It's not the Vim default of "unset", but a copy of the global value. // The local value is set to the default value (as a copy of the global value), so return the global external
// Return the current value of the global external value // value as a default
return OptionValue.Default(getExternalGlobalValue()) return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
} }
// 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(storedValue: OptionValue<T>?, newValue: OptionValue<T>, editor: VimEditor): Boolean { override fun setLocalValue(
// Vim local value is being set. We do nothing but set the local IntelliJ value to 0, so IntelliJ's scrolling storedValue: OptionValue<VimInt>?,
// doesn't affect IdeaVim's scrolling newValue: OptionValue<VimInt>,
suppressExternalLocalValue(editor) editor: VimEditor,
): Boolean {
// This is setting the Vim local value. We do nothing but reset the local horizontal scroll jump so IntelliJ's
// scrolling doesn't affect our scrolling
editor.ij.settings.horizontalScrollOffset = 0
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) {
// The IntelliJ global value has changed. We want to use this as the Vim global value. Since we control scrolling, // Again, just make sure the IntelliJ local value is 0
// set the local IntelliJ value to 0 injector.editorGroup.getEditors().forEach { it.ij.settings.horizontalScrollOffset = 0 }
injector.editorGroup.getEditors().forEach { suppressExternalLocalValue(it) }
// Now update the Vim global value to match the new IntelliJ global value. If the current Vim global value is // Update the stored Vim global value. This will not override any existing local values
// 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(option, globalScope) val storedValue = internalOptionValueAccessor.getOptionValueInternal(sideScrollOffOption, globalScope)
if (storedValue !is OptionValue.Default) { if (storedValue !is OptionValue.Default) {
val externalGlobalValue = EditorSettingsExternalizable.getInstance().horizontalScrollOffset
internalOptionValueAccessor.setOptionValueInternal( internalOptionValueAccessor.setOptionValueInternal(
option, sideScrollOffOption,
globalScope, globalScope,
OptionValue.External(getExternalGlobalValue()) OptionValue.External(VimInt(externalGlobalValue))
) )
} }
} }
} }
protected abstract fun getExternalGlobalValue(): T
protected abstract fun suppressExternalLocalValue(editor: VimEditor)
} }
@@ -1230,63 +1198,23 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
setIsUseSoftWraps(editor, value.asBoolean()) setIsUseSoftWraps(editor, value.asBoolean())
} }
override fun canInitialiseOptionFrom(sourceEditor: VimEditor, targetEditor: VimEditor): Boolean {
// IntelliJ's soft-wrap settings are based on editor kind, so there can be different wrap options for consoles,
// main editors, etc. This is particularly noticeable in the console when running an application. The main editor
// might have the Vim default with line wrap enabled. Initialising the run console will also have a default value,
// and won't be updated by the options subsystem. It might have wrap enabled or not. If the editors were the same
// kind, the same default value would be used.
// However, if the main editor has 'wrap' explicitly set, this value is copied to the console, so the behaviour is
// inconsistent. Furthermore, the run console has a soft-wraps toggle button that works at the global level, and
// IdeaVim only sets the local value, so the toggle button can be inconsistent too.
// By denying copying the main editor value during initialisation, the console gets the default value, and the IDE
// decides what it should be. The behaviour is now more consistent.
// We're happy to initialise diff editors from main editors, as there isn't a different soft wrap setting there.
// Preview tabs might also have different settings, but because they're a type of main editor, it doesn't matter
// so much
fun editorKindToSoftWrapAppliancesPlace(kind: EditorKind) = when (kind) {
EditorKind.UNTYPED,
EditorKind.DIFF,
EditorKind.MAIN_EDITOR -> SoftWrapAppliancePlaces.MAIN_EDITOR
EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE
// Treat PREVIEW as a kind of MAIN_EDITOR instead of SWAP.PREVIEW. There are fewer noticeable differences
EditorKind.PREVIEW -> SoftWrapAppliancePlaces.MAIN_EDITOR
}
val sourceKind = editorKindToSoftWrapAppliancesPlace(sourceEditor.ij.editorKind)
val targetKind = editorKindToSoftWrapAppliancesPlace(targetEditor.ij.editorKind)
return sourceKind == targetKind
}
private fun getGlobalIsUseSoftWraps(editor: VimEditor): Boolean { 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() val settings = EditorSettingsExternalizable.getInstance()
if (softWrapAppliancePlace == SoftWrapAppliancePlaces.MAIN_EDITOR) { if (settings.isUseSoftWraps) {
if (settings.isUseSoftWraps) { val masks = settings.softWrapFileMasks
val masks = settings.softWrapFileMasks if (masks.trim() == "*") return true
if (masks.trim() == "*") return true
editor.ij.virtualFile?.let { file -> editor.ij.virtualFile?.let { file ->
masks.split(";").forEach { mask -> masks.split(";").forEach { mask ->
val trimmed = mask.trim() val trimmed = mask.trim()
if (trimmed.isNotEmpty() && PatternUtil.fromMask(trimmed).matcher(file.name).matches()) { if (trimmed.isNotEmpty() && PatternUtil.fromMask(trimmed).matcher(file.name).matches()) {
return true return true
}
} }
} }
} }
return false
} }
return settings.isUseSoftWraps(softWrapAppliancePlace) return false
} }
private fun getEffectiveIsUseSoftWraps(editor: VimEditor) = editor.ij.settings.isUseSoftWraps private fun getEffectiveIsUseSoftWraps(editor: VimEditor) = editor.ij.settings.isUseSoftWraps
@@ -1313,28 +1241,28 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
} }
class IjOptionConstants { public class IjOptionConstants {
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName") @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
companion object { public companion object {
const val idearefactormode_keep: String = "keep" public const val idearefactormode_keep: String = "keep"
const val idearefactormode_select: String = "select" public const val idearefactormode_select: String = "select"
const val idearefactormode_visual: String = "visual" public const val idearefactormode_visual: String = "visual"
const val ideastatusicon_enabled: String = "enabled" public const val ideastatusicon_enabled: String = "enabled"
const val ideastatusicon_gray: String = "gray" public const val ideastatusicon_gray: String = "gray"
const val ideastatusicon_disabled: String = "disabled" public const val ideastatusicon_disabled: String = "disabled"
const val ideavimsupport_dialog: String = "dialog" public const val ideavimsupport_dialog: String = "dialog"
const val ideavimsupport_singleline: String = "singleline" public const val ideavimsupport_singleline: String = "singleline"
const val ideavimsupport_dialoglegacy: String = "dialoglegacy" public const val ideavimsupport_dialoglegacy: String = "dialoglegacy"
const val ideawrite_all: String = "all" public const val ideawrite_all: String = "all"
const val ideawrite_file: String = "file" public const val ideawrite_file: String = "file"
val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled) public val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual) public val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file) public val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy) public val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
} }
} }

View File

@@ -18,22 +18,111 @@ 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)
override fun executeCommand( public override fun executeCommand(
editor: VimEditor, editor: VimEditor,
command: String, command: String,
input: CharSequence?, input: CharSequence?,
@@ -95,6 +184,8 @@ class ProcessGroup : VimProcessGroupBase() {
val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
val output = handler.runProcessWithProgressIndicator(progressIndicator) val output = handler.runProcessWithProgressIndicator(progressIndicator)
lastCommand = command
if (output.isCancelled) { if (output.isCancelled) {
// TODO: Vim will use whatever text has already been written to stdout // TODO: Vim will use whatever text has already been written to stdout
// For whatever reason, we're not getting any here, so just throw an exception // For whatever reason, we're not getting any here, so just throw an exception
@@ -130,7 +221,7 @@ class ProcessGroup : VimProcessGroupBase() {
} }
} }
companion object { public companion object {
private val logger = logger<ProcessGroup>() private val logger = logger<ProcessGroup>()
} }
} }

View File

@@ -14,8 +14,6 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.register.Register; import com.maddyhome.idea.vim.register.Register;
import com.maddyhome.idea.vim.register.VimRegisterGroupBase; import com.maddyhome.idea.vim.register.VimRegisterGroupBase;
import com.maddyhome.idea.vim.state.mode.SelectionType; import com.maddyhome.idea.vim.state.mode.SelectionType;
@@ -37,10 +35,6 @@ import java.util.List;
}) })
public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> { public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> {
static {
IjVimInjectorKt.initInjector();
}
private static final Logger logger = Logger.getInstance(RegisterGroup.class); private static final Logger logger = Logger.getInstance(RegisterGroup.class);
public RegisterGroup() { public RegisterGroup() {

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,6 @@ import com.maddyhome.idea.vim.mark.Jump
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
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.newapi.initInjector
import org.jdom.Element import org.jdom.Element
@State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)]) @State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
@@ -54,7 +53,6 @@ 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")
@@ -66,7 +64,6 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
} }
override fun loadState(state: Element) { override fun loadState(state: Element) {
initInjector()
val projectElements = state.getChildren("project") val projectElements = state.getChildren("project")
for (projectElement in projectElements) { for (projectElement in projectElements) {
val jumps = mutableListOf<Jump>() val jumps = mutableListOf<Jump>()
@@ -76,7 +73,6 @@ 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)
} }
@@ -91,7 +87,6 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
internal class JumpsListener(val project: Project) : RecentPlacesListener { internal class JumpsListener(val project: Project) : RecentPlacesListener {
override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) { override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) {
initInjector()
if (!injector.globalIjOptions().unifyjumps) return if (!injector.globalIjOptions().unifyjumps) return
val jumpService = injector.jumpService val jumpService = injector.jumpService
@@ -99,7 +94,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(injector.file.getProjectId(project), jump, true) jumpService.addJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump, true)
} }
} }
@@ -111,7 +106,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(injector.file.getProjectId(project), jump) jumpService.removeJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump)
} }
} }
@@ -125,6 +120,6 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
val path = place.file.path val path = place.file.path
return Jump(line, col, path, place.file.fileSystem.protocol) return Jump(line, col, path)
} }
} }

View File

@@ -46,7 +46,6 @@ import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.put.VimPasteProvider import com.maddyhome.idea.vim.put.VimPasteProvider
import com.maddyhome.idea.vim.put.VimPutBase import com.maddyhome.idea.vim.put.VimPutBase
import com.maddyhome.idea.vim.register.RegisterConstants import com.maddyhome.idea.vim.register.RegisterConstants
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isBlock import com.maddyhome.idea.vim.state.mode.isBlock
import com.maddyhome.idea.vim.state.mode.isChar import com.maddyhome.idea.vim.state.mode.isChar
@@ -84,11 +83,6 @@ 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 (injector.vimState.mode is Mode.INSERT) {
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(

View File

@@ -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
object VimVisualTimer { public object VimVisualTimer {
var swingTimer: Timer? = null public var swingTimer: Timer? = null
var mode: Mode? = null public var mode: Mode? = null
inline fun singleTask(currentMode: Mode, crossinline task: (initialMode: Mode?) -> Unit) { public 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 @@ object VimVisualTimer {
swingTimer = timer swingTimer = timer
} }
fun doNow() { public fun doNow() {
val swingTimer1 = swingTimer val swingTimer1 = swingTimer
if (swingTimer1 != null) { if (swingTimer1 != null) {
swingTimer1.stop() swingTimer1.stop()
@@ -80,12 +80,12 @@ object VimVisualTimer {
} }
} }
fun drop() { public fun drop() {
swingTimer?.stop() swingTimer?.stop()
swingTimer = null swingTimer = null
} }
inline fun timerAction(task: (initialMode: Mode?) -> Unit) { public inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
task(mode) task(mode)
swingTimer = null swingTimer = null
mode = null mode = null

View File

@@ -9,9 +9,13 @@
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
/** /**
@@ -27,4 +31,12 @@ 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)
}
} }

View File

@@ -16,7 +16,6 @@ import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.key import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.newapi.initInjector
/** /**
* Logs the chain of handlers for esc and enter * Logs the chain of handlers for esc and enter
@@ -35,8 +34,6 @@ internal class EditorHandlersChainLogger : ProjectActivity {
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler") private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
override suspend fun execute(project: Project) { override suspend fun execute(project: Project) {
initInjector()
if (!enableOctopus) return if (!enableOctopus) return
val escHandlers = editorHandlers.extensionList val escHandlers = editorHandlers.extensionList

View File

@@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
*/ */
internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() { internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(editor, name = actionName, context = context) injector.actionExecutor.executeAction(actionName, context)
injector.scroll.scrollCaretIntoView(editor) injector.scroll.scrollCaretIntoView(editor)
return true return true
} }

View File

@@ -19,7 +19,6 @@ 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
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.common.IsReplaceCharListener import com.maddyhome.idea.vim.common.IsReplaceCharListener
import com.maddyhome.idea.vim.common.ModeChangeListener import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
@@ -74,7 +73,7 @@ internal object GuicursorChangeListener : EffectiveOptionValueChangeListener {
} }
private fun Editor.guicursorMode(): GuiCursorMode { private fun Editor.guicursorMode(): GuiCursorMode {
return GuiCursorMode.fromMode(vim.mode, injector.vimState.isReplaceCharacter) return GuiCursorMode.fromMode(vim.mode, vim.vimStateMachine.isReplaceCharacter)
} }
/** /**
@@ -87,7 +86,6 @@ private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance()
private fun Editor.updatePrimaryCaretVisualAttributes() { private fun Editor.updatePrimaryCaretVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled") if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (isIdeaVimDisabledHere) return
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this) caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
@@ -101,7 +99,6 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
private fun Editor.updateSecondaryCaretsVisualAttributes() { private fun Editor.updateSecondaryCaretsVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled") if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (isIdeaVimDisabledHere) return
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them // IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this) val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
this.caretModel.allCarets.forEach { this.caretModel.allCarets.forEach {
@@ -147,7 +144,7 @@ private object AttributesCache {
@TestOnly @TestOnly
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode() internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener, EditorListener { public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
override fun isReplaceCharChanged(editor: VimEditor) { override fun isReplaceCharChanged(editor: VimEditor) {
updateCaretsVisual(editor) updateCaretsVisual(editor)
} }
@@ -156,19 +153,21 @@ class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener,
updateCaretsVisual(editor) updateCaretsVisual(editor)
} }
override fun focusGained(editor: VimEditor) {
updateCaretsVisual(editor)
}
private fun updateCaretsVisual(editor: VimEditor) { private fun updateCaretsVisual(editor: VimEditor) {
val ijEditor = (editor as IjVimEditor).editor if (injector.globalOptions().ideaglobalmode) {
ijEditor.updateCaretsVisualAttributes() updateAllEditorsCaretsVisual()
ijEditor.updateCaretsVisualPosition() } else {
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
}
} }
fun updateAllEditorsCaretsVisual() { public fun updateAllEditorsCaretsVisual() {
injector.editorGroup.getEditors().forEach { editor -> injector.editorGroup.getEditors().forEach { editor ->
updateCaretsVisual(editor) val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
} }
} }
} }

View File

@@ -11,7 +11,13 @@
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
@@ -21,17 +27,57 @@ 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")
val Editor.inInsertMode: Boolean public 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")
val Editor.inVisualMode: Boolean public val Editor.inVisualMode: Boolean
get() = this.vim.inVisualMode get() = this.vim.inVisualMode
@get:JvmName("inExMode") @get:JvmName("inExMode")
internal val Editor.inExMode internal val Editor.inExMode
get() = this.vim.mode is Mode.CMD_LINE get() = this.vim.vimStateMachine.mode is Mode.CMD_LINE

View File

@@ -100,6 +100,15 @@ 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>

View File

@@ -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")
val Editor.fileSize: Int public val Editor.fileSize: Int
get() = document.textLength get() = document.textLength
/** /**

View File

@@ -13,23 +13,23 @@ import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult
import com.intellij.openapi.actionSystem.DataContextWrapper import com.intellij.openapi.actionSystem.DataContextWrapper
import com.intellij.openapi.actionSystem.EmptyAction import com.intellij.openapi.actionSystem.EmptyAction
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.actionSystem.impl.Utils import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
import com.intellij.openapi.progress.util.ProgressIndicatorUtils import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.util.NlsContexts
import com.intellij.util.SlowOperations
import com.maddyhome.idea.vim.RegisterActions import com.maddyhome.idea.vim.RegisterActions
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction import com.maddyhome.idea.vim.api.NativeAction
@@ -69,12 +69,6 @@ internal class IjActionExecutor : VimActionExecutor {
* @param context The context to run it in * @param context The context to run it in
*/ */
override fun executeAction(editor: VimEditor?, action: NativeAction, context: ExecutionContext): Boolean { override fun executeAction(editor: VimEditor?, action: NativeAction, context: ExecutionContext): Boolean {
val applicationEx = ApplicationManagerEx.getApplicationEx()
if (ProgressIndicatorUtils.isWriteActionRunningOrPending(applicationEx)) {
// This is needed for VIM-3376 and it should turn into error at soeme moment
thisLogger().warn(RuntimeException("Actions cannot be updated when write-action is running or pending", ))
}
val ijAction = (action as IjNativeAction).action val ijAction = (action as IjNativeAction).action
/** /**
@@ -127,15 +121,51 @@ internal class IjActionExecutor : VimActionExecutor {
} }
} }
// This is taken directly from ActionUtil.performActionDumbAwareWithCallbacks
// But with one check removed. With this check some actions (like `:w` doesn't work)
// https://youtrack.jetbrains.com/issue/VIM-2691/File-is-not-saved-on-w
private fun performDumbAwareWithCallbacks(
action: AnAction,
event: AnActionEvent,
performRunnable: Runnable,
) {
val project = event.project
var indexError: IndexNotReadyException? = null
val manager = ActionManagerEx.getInstanceEx()
manager.fireBeforeActionPerformed(action, event)
var result: AnActionResult? = null
try {
SlowOperations.allowSlowOperations(SlowOperations.ACTION_PERFORM).use {
performRunnable.run()
result = AnActionResult.PERFORMED
}
} catch (ex: IndexNotReadyException) {
indexError = ex
result = AnActionResult.failed(ex)
} catch (ex: RuntimeException) {
result = AnActionResult.failed(ex)
throw ex
} catch (ex: Error) {
result = AnActionResult.failed(ex)
throw ex
} finally {
if (result == null) result = AnActionResult.failed(Throwable())
manager.fireAfterActionPerformed(action, event, result!!)
}
if (indexError != null) {
ActionUtil.showDumbModeWarning(project, action, event)
}
}
/** /**
* Execute an action by name * Execute an action by name
* *
* @param name The name of the action to execute * @param name The name of the action to execute
* @param context The context to run it in * @param context The context to run it in
*/ */
override fun executeAction(editor: VimEditor, name: @NonNls String, context: ExecutionContext): Boolean { override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
val action = getAction(name, context) val action = getAction(name, context)
return action != null && executeAction(editor, IjNativeAction(action), context) return action != null && executeAction(null, IjNativeAction(action), context)
} }
private fun getAction(name: String, context: ExecutionContext): AnAction? { private fun getAction(name: String, context: ExecutionContext): AnAction? {
@@ -181,8 +211,8 @@ internal class IjActionExecutor : VimActionExecutor {
CommandProcessor.getInstance().executeCommand(editor?.ij?.project, runnable, name, groupId) CommandProcessor.getInstance().executeCommand(editor?.ij?.project, runnable, name, groupId)
} }
override fun executeEsc(editor: VimEditor, context: ExecutionContext): Boolean { override fun executeEsc(context: ExecutionContext): Boolean {
return executeAction(editor, IdeActions.ACTION_EDITOR_ESCAPE, context) return executeAction(IdeActions.ACTION_EDITOR_ESCAPE, context)
} }
override fun executeVimAction( override fun executeVimAction(

View File

@@ -14,16 +14,14 @@ import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.actionSystem.EditorActionManager 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.EngineEditorHelperBase
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
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@Service @Service
internal class IjEditorHelper : EngineEditorHelperBase() { internal class IjEditorHelper : EngineEditorHelper {
override fun amountOfInlaysBeforeVisualPosition(editor: VimEditor, pos: VimVisualPosition): Int { override fun amountOfInlaysBeforeVisualPosition(editor: VimEditor, pos: VimVisualPosition): Int {
return (editor as IjVimEditor).editor.amountOfInlaysBeforeVisualPosition( return (editor as IjVimEditor).editor.amountOfInlaysBeforeVisualPosition(
VisualPosition( VisualPosition(
@@ -52,23 +50,11 @@ internal class IjEditorHelper : EngineEditorHelperBase() {
return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij) return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij)
} }
override fun pad(editor: VimEditor, line: Int, to: Int): String {
return EditorHelper.pad(editor.ij, line, to)
}
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition { 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()
}
}
}
} }

View File

@@ -31,7 +31,7 @@ import com.maddyhome.idea.vim.state.mode.returnTo
internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) { internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
if (!this.vim.inSelectMode) return if (!this.vim.inSelectMode) return
val returnTo = this.vim.mode.returnTo val returnTo = this.vim.vimStateMachine.mode.returnTo
when (returnTo) { when (returnTo) {
ReturnTo.INSERT -> { ReturnTo.INSERT -> {
this.vim.mode = Mode.INSERT this.vim.mode = Mode.INSERT
@@ -64,7 +64,7 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) { internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
if (!this.inSelectMode) return if (!this.inSelectMode) return
val returnTo = this.mode.returnTo val returnTo = this.vimStateMachine.mode.returnTo
when (returnTo) { when (returnTo) {
ReturnTo.INSERT -> { ReturnTo.INSERT -> {
this.mode = Mode.INSERT this.mode = Mode.INSERT

View File

@@ -180,7 +180,7 @@ internal object ScrollViewHelper {
} }
private fun getScrollJump(editor: VimEditor, height: Int): Int { private fun getScrollJump(editor: VimEditor, height: Int): Int {
val flags = injector.vimState.executingCommandFlags val flags = VimStateMachine.getInstance(editor).executingCommandFlags
val scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) val scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP)
// Default value is 1. Zero is a valid value, but we normalise to 1 - we always want to scroll at least one line // Default value is 1. Zero is a valid value, but we normalise to 1 - we always want to scroll at least one line
@@ -203,7 +203,7 @@ internal object ScrollViewHelper {
val caretColumn = position.column val caretColumn = position.column
val halfWidth = getApproximateScreenWidth(editor) / 2 val halfWidth = getApproximateScreenWidth(editor) / 2
val scrollOffset = getNormalizedSideScrollOffset(editor) val scrollOffset = getNormalizedSideScrollOffset(editor)
val flags = injector.vimState.executingCommandFlags val flags = VimStateMachine.getInstance(vimEditor).executingCommandFlags
val allowSidescroll = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP) val allowSidescroll = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP)
val sidescroll = injector.options(vimEditor).sidescroll val sidescroll = injector.options(vimEditor).sidescroll
val offsetLeft = caretColumn - (currentVisualLeftColumn + scrollOffset) val offsetLeft = caretColumn - (currentVisualLeftColumn + scrollOffset)

File diff suppressed because it is too large Load Diff

View File

@@ -8,23 +8,152 @@
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.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.TextRange import com.maddyhome.idea.vim.common.Direction
import com.maddyhome.idea.vim.helper.CharacterHelper.charType import com.maddyhome.idea.vim.helper.SearchHelper.findPositionOfFirstCharacter
import com.maddyhome.idea.vim.newapi.IjVimEditor
import it.unimi.dsi.fastutil.ints.IntComparator private data class State(val position: Int, val trigger: Char, val inQuote: Boolean?, val lastOpenSingleQuotePos: Int)
import it.unimi.dsi.fastutil.ints.IntIterator
import it.unimi.dsi.fastutil.ints.IntRBTreeSet // bounds are considered inside corresponding quotes
import it.unimi.dsi.fastutil.ints.IntSortedSet internal fun checkInString(chars: CharSequence, currentPos: Int, str: Boolean): Boolean {
val begin = findPositionOfFirstCharacter(chars, currentPos, setOf('\n'), false, Direction.BACKWARDS)?.second?.plus(1) ?: 0
val changes = quoteChanges(chars, begin)
// TODO: here we need to keep only the latest element in beforePos (if any) and
// don't need atAndAfterPos to be eagerly collected
var (beforePos, atAndAfterPos) = changes.partition { it.position < currentPos }
var (atPos, afterPos) = atAndAfterPos.partition { it.position == currentPos }
assert(atPos.size <= 1) { "Multiple characters at position $currentPos in string $chars" }
if (atPos.isNotEmpty()) {
val atPosChange = atPos[0]
if (afterPos.isEmpty()) {
// it is situation when cursor is on closing quote, so we must consider that we are inside quotes pair
afterPos = afterPos.toMutableList()
afterPos.add(atPosChange)
} else {
// it is situation when cursor is on opening quote, so we must consider that we are inside quotes pair
beforePos = beforePos.toMutableList()
beforePos.add(atPosChange)
}
}
val lastBeforePos = beforePos.lastOrNull()
// if opening quote was found before pos (inQuote=true), it doesn't mean pos is in string, we need
// to find closing quote to be sure
var posInQuote = lastBeforePos?.inQuote?.let { if (it) null else it }
val lastOpenSingleQuotePosBeforeCurrentPos = lastBeforePos?.lastOpenSingleQuotePos ?: -1
var posInChar = if (lastOpenSingleQuotePosBeforeCurrentPos == -1) false else null
var inQuote: Boolean? = null
for ((_, trigger, inQuoteAfter, lastOpenSingleQuotePosAfter) in afterPos) {
inQuote = inQuoteAfter
if (posInQuote != null && posInChar != null) break
if (posInQuote == null && inQuoteAfter != null) {
// if we found double quote
if (trigger == '"') {
// then previously it has opposite value
posInQuote = !inQuoteAfter
// if we found single quote
} else if (trigger == '\'') {
// then we found closing single quote
posInQuote = inQuoteAfter
}
}
if (posInChar == null && lastOpenSingleQuotePosAfter != lastOpenSingleQuotePosBeforeCurrentPos) {
// if we found double quote and we reset position of last single quote
if (trigger == '"' && lastOpenSingleQuotePosAfter == -1) {
// then it means previously there supposed to be open single quote
posInChar = false
// if we found single quote
} else if (trigger == '\'') {
// if we reset position of last single quote
// it means we found closing single quote
// else it means we found opening single quote
posInChar = lastOpenSingleQuotePosAfter == -1
}
}
}
return if (str) posInQuote != null && posInQuote && (inQuote == null || !inQuote) else posInChar != null && posInChar
}
// yields changes of inQuote and lastOpenSingleQuotePos during while iterating over chars
// rules are that:
// - escaped quotes are skipped
// - single quoted group may enclose only one character, maybe escaped,
// - so distance between opening and closing single quotes cannot be more than 3
// - bounds are considered inside corresponding quotes
private fun quoteChanges(chars: CharSequence, begin: Int) = sequence {
// position of last found unpaired single quote
var lastOpenSingleQuotePos = -1
// whether we are in double quotes
// true - definitely yes
// false - definitely no
// null - maybe yes, in case we found such combination: '"
// in that situation it may be double quote inside single quotes, so we cannot threat it as double quote pair open/close
var inQuote: Boolean? = false
val charsToSearch = setOf('\'', '"', '\n')
var found = findPositionOfFirstCharacter(chars, begin, charsToSearch, false, Direction.FORWARDS)
while (found != null && found.first != '\n') {
val i = found.second
val c = found.first
when (c) {
'"' -> {
// if [maybe] in quote, then we know we found closing quote, so now we surely are not in quote
if (inQuote == null || inQuote) {
// we just found closing double quote
inQuote = false
// reset last found single quote, as it was in string literal
lastOpenSingleQuotePos = -1
// if we previously found unclosed single quote
} else if (lastOpenSingleQuotePos >= 0) {
// ...but we are too far from it
if (i - lastOpenSingleQuotePos > 2) {
// then it definitely was not opening single quote
lastOpenSingleQuotePos = -1
// and we found opening double quote
inQuote = true
} else {
// else we don't know if we inside double or single quotes or not
inQuote = null
}
// we were not in double nor in single quote, so now we are in double quote
} else {
inQuote = true
}
}
'\'' -> {
// if we previously found unclosed single quote
if (lastOpenSingleQuotePos >= 0) {
// ...but we are too far from it
if (i - lastOpenSingleQuotePos > 3) {
// ... forget about it and threat current one as unclosed
lastOpenSingleQuotePos = i
} else {
// else we found closing single quote
lastOpenSingleQuotePos = -1
// and if we didn't know whether we are in double quote or not
if (inQuote == null) {
// then now we are definitely not in
inQuote = false
}
}
} else {
// we found opening single quote
lastOpenSingleQuotePos = i
}
}
}
yield(State(i, c, inQuote, lastOpenSingleQuotePos))
found =
findPositionOfFirstCharacter(chars, i + Direction.FORWARDS.toInt(), charsToSearch, false, Direction.FORWARDS)
}
}
/** /**
* 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.
@@ -51,150 +180,3 @@ 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)
}
/**
* 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

View File

@@ -210,28 +210,12 @@ private fun findClosestMatch(
return -1 return -1
} }
val sortedResults = if (forwards) { val sortedResults = results.sortedBy { it.startOffset }.let { if (!forwards) it.reversed() else it }
results.sortedBy { it.startOffset } val nextIndex = sortedResults.indexOfFirst {
} else { if (forwards) it.startOffset > initialOffset else it.startOffset < initialOffset
results.sortedByDescending { it.startOffset }
} }
val closestIndex = if (forwards) { val toDrop = (nextIndex + count - 1).let { if (injector.globalOptions().wrapscan) it % results.size else it }
sortedResults.indexOfFirst { it.startOffset > initialOffset } return sortedResults.drop(toDrop).firstOrNull()?.startOffset ?: -1
}
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) {

View File

@@ -8,17 +8,31 @@
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)")
@ApiStatus.ScheduledForRemoval public fun parseKeys(vararg string: String): List<KeyStroke> {
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()
} }

View File

@@ -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
class TestInputModel private constructor() { public class TestInputModel private constructor() {
private val myKeyStrokes: MutableList<KeyStroke> = Lists.newArrayList() private val myKeyStrokes: MutableList<KeyStroke> = Lists.newArrayList()
fun setKeyStrokes(keyStrokes: List<KeyStroke>) { public fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
myKeyStrokes.clear() myKeyStrokes.clear()
myKeyStrokes.addAll(keyStrokes) myKeyStrokes.addAll(keyStrokes)
} }
fun nextKeyStroke(): KeyStroke? { public 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()) {
} }
} }
companion object { public companion object {
@JvmStatic @JvmStatic
fun getInstance(editor: Editor): TestInputModel { public fun getInstance(editor: Editor): TestInputModel {
var model = editor.vimTestInputModel var model = editor.vimTestInputModel
if (model == null) { if (model == null) {
model = TestInputModel() model = TestInputModel()

View File

@@ -10,13 +10,8 @@
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
@@ -37,21 +32,11 @@ internal fun runAfterGotFocus(runnable: Runnable) {
IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable, ModalityState.defaultModalityState()) IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable, ModalityState.defaultModalityState())
} }
internal fun selectEditorFont(editor: Editor?, forText: String): Font { internal fun selectFont(forStr: String): Font {
val fontSize = when {
editor is EditorImpl -> editor.fontSize2D
UISettings.getInstance().presentationMode -> UISettingsUtils.getInstance().presentationModeFontSize
editor?.editorKind == EditorKind.CONSOLE -> UISettingsUtils.getInstance().scaledConsoleFontSize
else -> UISettingsUtils.getInstance().scaledEditorFontSize
}
val scheme = EditorColorsManager.getInstance().globalScheme val scheme = EditorColorsManager.getInstance().globalScheme
scheme.fontPreferences.realFontFamilies.forEach { fontName ->
val font = Font(fontName, Font.PLAIN, scheme.editorFontSize)
if (font.canDisplayUpTo(forText) == -1) {
return font.deriveFont(fontSize)
}
}
return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize).deriveFont(fontSize) val fontName = scheme.fontPreferences.realFontFamilies.firstOrNull {
Font(it, Font.PLAIN, scheme.editorFontSize).canDisplayUpTo(forStr) == -1
} ?: return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize)
return Font(fontName, Font.PLAIN, scheme.editorFontSize)
} }

Some files were not shown because too many files have changed in this diff Show More