1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-04-19 10:15:44 +02:00

Compare commits

..

21 Commits

Author SHA1 Message Date
f33589fbce
Set plugin version to chylex-42 2024-12-17 13:04:35 +01:00
35445cb730
Exit insert mode after refactoring 2024-12-17 13:04:35 +01:00
9cba953bf7
Add action to run last macro in all opened files 2024-12-17 13:04:35 +01:00
eb7c1953ae
Stop macro execution after a failed search 2024-12-17 13:04:35 +01:00
8d4a1cd7d5
Revert per-caret registers 2024-12-17 13:04:35 +01:00
fd33e4254b
Fix(VIM-3364): Exception with mapped Generate action 2024-12-17 12:44:01 +01:00
f242cf18d7
Apply scrolloff after executing native IDEA actions 2024-12-17 12:44:01 +01:00
ac46a45f26
Stay on same line after reindenting 2024-12-17 12:44:01 +01:00
9d1e29d4bc
Update search register when using f/t 2024-12-17 12:44:01 +01:00
8c30f802ca
Automatically add unambiguous imports after running a macro 2024-12-17 12:44:01 +01:00
766f3f498d
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-12-17 12:44:00 +01:00
60d9c59a3e
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-12-17 12:44:00 +01:00
025bfe5aba
Add support for count for visual and line motion surround 2024-12-17 12:43:59 +01:00
2edb650830
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-12-17 12:43:48 +01:00
3d75e14dc4
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-12-17 12:06:53 +01:00
db0290d218
Respect count with <Action> mappings 2024-12-17 12:06:53 +01:00
36716fe849
Change matchit plugin to use HTML patterns in unrecognized files 2024-12-17 12:06:53 +01:00
7cfe42688b
Reset insert mode when switching active editor 2024-12-17 12:06:53 +01:00
1d9fb7f6a7
Disable switching to insert mode for some editors 2024-12-17 12:06:53 +01:00
8c4828a6fd
Remove update checker 2024-12-17 12:06:53 +01:00
86f11a9554
Set custom plugin version 2024-12-17 12:06:53 +01:00
720 changed files with 118305 additions and 24178 deletions

View File

@ -20,10 +20,10 @@ jobs:
- name: Fetch origin repo - name: Fetch origin repo
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set up JDK 21 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '21' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

View File

@ -23,10 +23,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 21 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '21' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

View File

@ -39,18 +39,12 @@ jobs:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps: steps:
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -61,7 +55,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v3 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -75,4 +69,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v2

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 21 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '21' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

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 21 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '21' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

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 21 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '21' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

View File

@ -13,7 +13,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: zulu distribution: zulu
java-version: 21 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
# - name: Setup Gradle # - name: Setup Gradle
@ -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-*/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@ -13,7 +13,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: zulu distribution: zulu
java-version: 21 java-version: 17
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: '3.10' python-version: '3.10'
@ -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-*/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/PC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@ -1,49 +0,0 @@
name: Run UI Rider Tests
on:
workflow_dispatch:
schedule:
- cron: '0 12 * * *'
jobs:
build-for-ui-test-mac-os:
if: github.repository == 'JetBrains/ideavim'
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 21
- name: Setup FFmpeg
run: brew install ffmpeg
# - name: Setup Gradle
# uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin
run: gradle :buildPlugin
- name: Run Idea
run: |
mkdir -p build/reports
gradle --no-configuration-cache :runIdeForUiTests -PideaType=RD > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
url: http://127.0.0.1:8082
max-attempts: 20
retry-delay: 10s
- name: Tests
run: gradle :tests:ui-rd-tests:testUi
- name: Move video
if: always()
run: mv tests/ui-rd-tests/video build/reports
- name: Move sandbox logs
if: always()
run: mv build/idea-sandbox/RD-*/log_runIdeForUiTests idea-sandbox-log
- name: Save report
if: always()
uses: actions/upload-artifact@v4
with:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-rd-tests/build/reports
idea-sandbox-log

View File

@ -13,7 +13,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: zulu distribution: zulu
java-version: 21 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
# - name: Setup Gradle # - name: Setup Gradle
@ -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-*/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

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 21 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '21' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

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 21 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '21' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

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 21 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '21' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

1
.gitignore vendored
View File

@ -13,7 +13,6 @@
!/.idea/vcs.xml !/.idea/vcs.xml
!/.idea/misc.xml !/.idea/misc.xml
!/.idea/.name !/.idea/.name
!/.idea/gradle.xml
**/build/ **/build/
**/out/ **/out/

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/annotation-processors" />
<option value="$PROJECT_DIR$/scripts" />
<option value="$PROJECT_DIR$/tests" />
<option value="$PROJECT_DIR$/tests/java-tests" />
<option value="$PROJECT_DIR$/tests/long-running-tests" />
<option value="$PROJECT_DIR$/tests/property-tests" />
<option value="$PROJECT_DIR$/tests/ui-fixtures" />
<option value="$PROJECT_DIR$/tests/ui-ij-tests" />
<option value="$PROJECT_DIR$/tests/ui-py-tests" />
<option value="$PROJECT_DIR$/tests/ui-rd-tests" />
<option value="$PROJECT_DIR$/vim-engine" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -18,5 +18,5 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK" />
</project> </project>

View File

@ -5,11 +5,12 @@ object Constants {
const val EAP_CHANNEL = "eap" const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev" const val DEV_CHANNEL = "Dev"
const val NVIM_TESTS = "2024.3.3" const val NVIM_TESTS = "2024.2.1"
const val PROPERTY_TESTS = "2024.3.3" const val PROPERTY_TESTS = "2024.2.1"
const val LONG_RUNNING_TESTS = "2024.3.3" const val LONG_RUNNING_TESTS = "2024.2.1"
const val RELEASE = "2024.3.3" const val QODANA_TESTS = "2024.2.1"
const val RELEASE = "2024.2.1"
const val RELEASE_DEV = "2024.3.3" const val RELEASE_DEV = "2024.2.1"
const val RELEASE_EAP = "2024.3.3" const val RELEASE_EAP = "2024.2.1"
} }

View File

@ -5,6 +5,7 @@ import _Self.buildTypes.LongRunning
import _Self.buildTypes.Nvim import _Self.buildTypes.Nvim
import _Self.buildTypes.PluginVerifier import _Self.buildTypes.PluginVerifier
import _Self.buildTypes.PropertyBased import _Self.buildTypes.PropertyBased
import _Self.buildTypes.Qodana
import _Self.buildTypes.TestingBuildType import _Self.buildTypes.TestingBuildType
import _Self.subprojects.GitHub import _Self.subprojects.GitHub
import _Self.subprojects.Releases import _Self.subprojects.Releases
@ -24,7 +25,7 @@ object Project : Project({
// Active tests // Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2024.3.3", "<default>")) buildType(TestingBuildType("2024.2.1", "<default>"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased) buildType(PropertyBased)
@ -33,6 +34,8 @@ object Project : Project({
buildType(Nvim) buildType(Nvim)
buildType(PluginVerifier) buildType(PluginVerifier)
buildType(Compatibility) buildType(Compatibility)
buildType(Qodana)
}) })
// Common build type for all configurations // Common build type for all configurations

View File

@ -20,7 +20,7 @@ object Compatibility : IdeaVimBuildType({
name = "Load Verifier" name = "Load Verifier"
scriptContent = """ scriptContent = """
mkdir verifier1 mkdir verifier1
curl -f -L -o verifier1/verifier-cli-dev-all-2.jar "https://packages.jetbrains.team/files/p/ideavim/plugin-verifier/verifier-cli-dev-all-2.jar" curl -f -L -o verifier1/verifier-cli-dev-all-1.jar "https://packages.jetbrains.team/files/p/ideavim/plugin-verifier/verifier-cli-dev-all-1.jar"
""".trimIndent() """.trimIndent()
} }
script { script {
@ -33,16 +33,16 @@ object Compatibility : IdeaVimBuildType({
# Upload verifier-cli-dev-all.jar artifact to the repo in IdeaVim space repo # Upload verifier-cli-dev-all.jar artifact to the repo in IdeaVim space repo
java --version java --version
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}org.jetbrains.IdeaVim-EasyMotion' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}org.jetbrains.IdeaVim-EasyMotion' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.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-2.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-2.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-2.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-2.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
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.peekaboo' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.peekaboo' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.switch' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.switch' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.functiontextobj' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.functiontextobj' [latest-IU] -team-city
""".trimIndent() """.trimIndent()
} }
} }

View File

@ -0,0 +1,72 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package _Self.buildTypes
import _Self.IdeaVimBuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
object CreateNewReleaseBranchFromMaster : IdeaVimBuildType({
name = "EXP: Create new release branch from master"
vcs {
root(DslContext.settingsRoot)
branchFilter = "+:<default>"
checkoutMode = CheckoutMode.AUTO
}
steps {
script {
name = "Calculate next potential release version"
scriptContent = """
#!/bin/bash
# Fetch all remote branches
git fetch --all
# Get a list of all branches matching the pattern releases/x.y.z
branches=${'$'}(git branch -r | grep -oE 'releases/[0-9]+\.[0-9]+\.x')
# If no matching branches are found, print a message and exit
if [[ -z "${'$'}branches" ]]; then
echo "No release branches found"
exit 1
fi
# Find the largest release version
largest_release=${'$'}(echo "${'$'}branches" | sort -V | tail -n 1)
# Print the largest release
echo "Largest release branch: ${'$'}largest_release"
echo "##teamcity[setParameter name='env.POTENTIAL_VERSION' value='${'$'}largest_release']"
""".trimIndent()
}
script {
name = "Show potential release version"
scriptContent = """
#!/bin/bash
echo "Calculated or user-provided parameter value is: %env.POTENTIAL_VERSION%"
""".trimIndent()
}
}
params {
param("env.POTENTIAL_VERSION", "")
}
features {
sshAgent {
teamcitySshKey = "IdeaVim ssh keys"
}
}
})

View File

@ -28,7 +28,6 @@ object LongRunning : IdeaVimBuildType({
tasks = "clean :tests:long-running-tests:testLongRunning" tasks = "clean :tests:long-running-tests:testLongRunning"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
} }

View File

@ -18,7 +18,7 @@ object Nvim : IdeaVimBuildType({
param("env.ORG_GRADLE_PROJECT_downloadIdeaSources", "false") param("env.ORG_GRADLE_PROJECT_downloadIdeaSources", "false")
param("env.ORG_GRADLE_PROJECT_ideaVersion", NVIM_TESTS) param("env.ORG_GRADLE_PROJECT_ideaVersion", NVIM_TESTS)
param("env.ORG_GRADLE_PROJECT_instrumentPluginCode", "false") param("env.ORG_GRADLE_PROJECT_instrumentPluginCode", "false")
param("env.ideavim.nvim.path", "./nvim-linux-x86_64/bin/nvim") param("env.ideavim.nvim.path", "./nvim-linux64/bin/nvim")
} }
vcs { vcs {
@ -32,9 +32,9 @@ object Nvim : IdeaVimBuildType({
script { script {
name = "Set up NeoVim" name = "Set up NeoVim"
scriptContent = """ scriptContent = """
wget https://github.com/neovim/neovim/releases/download/nightly/nvim-linux-x86_64.tar.gz wget https://github.com/neovim/neovim/releases/download/v0.7.2/nvim-linux64.tar.gz
tar xzf nvim-linux-x86_64.tar.gz tar xzf nvim-linux64.tar.gz
cd nvim-linux-x86_64/bin cd nvim-linux64/bin
chmod +x nvim chmod +x nvim
""".trimIndent() """.trimIndent()
} }
@ -42,7 +42,6 @@ object Nvim : IdeaVimBuildType({
tasks = "clean test -Dnvim" tasks = "clean test -Dnvim"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
} }

View File

@ -25,7 +25,6 @@ object PluginVerifier : IdeaVimBuildType({
tasks = "clean verifyPlugin" tasks = "clean verifyPlugin"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
} }

View File

@ -0,0 +1,42 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package _Self.buildTypes
import _Self.IdeaVimBuildType
import _Self.vcsRoots.ReleasesVcsRoot
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
object PrintReleaseBranch : IdeaVimBuildType({
name = "EXP: Print release branch"
vcs {
root(ReleasesVcsRoot)
branchFilter = "+:heads/releases/*"
checkoutMode = CheckoutMode.AUTO
}
steps {
script {
name = "Print current branch"
scriptContent = """
echo "Current branch is: %teamcity.build.branch%"
""".trimIndent()
}
}
features {
sshAgent {
teamcitySshKey = "IdeaVim ssh keys"
}
}
})

View File

@ -27,7 +27,6 @@ object PropertyBased : IdeaVimBuildType({
tasks = "clean :tests:property-tests:testPropertyBased" tasks = "clean :tests:property-tests:testPropertyBased"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
} }

View File

@ -36,7 +36,6 @@ object PublishVimEngine : IdeaVimBuildType({
tasks = ":vim-engine:publish" tasks = ":vim-engine:publish"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
} }

80
.teamcity/_Self/buildTypes/Qodana.kt vendored Normal file
View File

@ -0,0 +1,80 @@
package _Self.buildTypes
import _Self.Constants.QODANA_TESTS
import _Self.IdeaVimBuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.Qodana
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.qodana
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.BuildFailureOnMetric
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.failOnMetricChange
import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.schedule
import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs
object Qodana : IdeaVimBuildType({
name = "Qodana checks"
params {
param("env.ORG_GRADLE_PROJECT_downloadIdeaSources", "false")
param("env.ORG_GRADLE_PROJECT_ideaVersion", QODANA_TESTS)
param("env.ORG_GRADLE_PROJECT_instrumentPluginCode", "false")
}
vcs {
root(DslContext.settingsRoot)
branchFilter = "+:<default>"
checkoutMode = CheckoutMode.AUTO
}
steps {
gradle {
name = "Generate grammar"
tasks = "generateGrammarSource"
}
qodana {
name = "Qodana"
param("clonefinder-languages", "")
param("collect-anonymous-statistics", "")
param("licenseaudit-enable", "")
param("clonefinder-languages-container", "")
param("linterVersion", "")
param("clonefinder-queried-project", "")
param("clonefinder-enable", "")
param("clonefinder-reference-projects", "")
linter = jvm {
version = Qodana.JVMVersion.LATEST
}
reportAsTests = true
additionalQodanaArguments = "--baseline qodana.sarif.json"
cloudToken = "credentialsJSON:6b79412e-9198-4862-9223-c5019488f903"
}
}
triggers {
vcs {
enabled = false
branchFilter = "+:<default>"
}
schedule {
schedulingPolicy = daily {
hour = 12
minute = 0
timezone = "SERVER"
}
param("dayOfWeek", "Sunday")
}
}
failureConditions {
failOnMetricChange {
threshold = 0
units = BuildFailureOnMetric.MetricUnit.DEFAULT_UNIT
comparison = BuildFailureOnMetric.MetricComparison.MORE
compareTo = value()
metric = BuildFailureOnMetric.MetricType.TEST_FAILED_COUNT
param("metricKey", "QodanaProblemsNew")
enabled = false
}
}
})

View File

@ -47,16 +47,13 @@ object ReleaseDev : IdeaVimBuildType({
gradle { gradle {
name = "Calculate new dev version" name = "Calculate new dev version"
tasks = "scripts:calculateNewDevVersion" tasks = "scripts:calculateNewDevVersion"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
gradle { gradle {
name = "Set TeamCity build number" name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber" tasks = "scripts:setTeamCityBuildNumber"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
gradle { gradle {
tasks = "publishPlugin" tasks = "publishPlugin"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
} }

View File

@ -57,22 +57,18 @@ object ReleaseEap : IdeaVimBuildType({
gradle { gradle {
name = "Calculate new eap version" name = "Calculate new eap version"
tasks = "scripts:calculateNewEapVersion" tasks = "scripts:calculateNewEapVersion"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
gradle { gradle {
name = "Set TeamCity build number" name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber" tasks = "scripts:setTeamCityBuildNumber"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
gradle { gradle {
name = "Add release tag" name = "Add release tag"
tasks = "scripts:addReleaseTag" tasks = "scripts:addReleaseTag"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
gradle { gradle {
name = "Publish plugin" name = "Publish plugin"
tasks = "publishPlugin" tasks = "publishPlugin"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
script { script {
name = "Push changes to the repo" name = "Push changes to the repo"
@ -90,7 +86,6 @@ object ReleaseEap : IdeaVimBuildType({
gradle { gradle {
name = "YouTrack post release actions" name = "YouTrack post release actions"
tasks = "scripts:eapReleaseActions" tasks = "scripts:eapReleaseActions"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
} }

View File

@ -0,0 +1,106 @@
package _Self.buildTypes
import _Self.Constants.EAP_CHANNEL
import _Self.Constants.RELEASE_EAP
import _Self.IdeaVimBuildType
import _Self.vcsRoots.ReleasesVcsRoot
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.BuildFailureOnMetric
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.failOnMetricChange
object ReleaseEapFromBranch : IdeaVimBuildType({
name = "EXP: Publish EAP Build from branch"
description = "Build and publish EAP of IdeaVim plugin"
artifactRules = "build/distributions/*"
params {
param("env.ORG_GRADLE_PROJECT_ideaVersion", RELEASE_EAP)
password(
"env.ORG_GRADLE_PROJECT_publishToken",
"credentialsJSON:61a36031-4da1-4226-a876-b8148bf32bde",
label = "Password"
)
param("env.ORG_GRADLE_PROJECT_publishChannels", EAP_CHANNEL)
password(
"env.ORG_GRADLE_PROJECT_slackUrl",
"credentialsJSON:a8ab8150-e6f8-4eaf-987c-bcd65eac50b5",
label = "Slack Token"
)
password(
"env.YOUTRACK_TOKEN",
"credentialsJSON:2479995b-7b60-4fbb-b095-f0bafae7f622",
display = ParameterDisplay.HIDDEN
)
}
vcs {
root(ReleasesVcsRoot)
branchFilter = """
+:heads/releases/*
""".trimIndent()
checkoutMode = CheckoutMode.AUTO
}
steps {
script {
name = "Pull git tags"
scriptContent = "git fetch --tags origin"
}
script {
name = "Pull git history"
scriptContent = "git fetch --unshallow"
}
gradle {
name = "Calculate new eap version from branch"
tasks = "scripts:calculateNewEapVersionFromBranch"
}
gradle {
name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber"
}
gradle {
name = "Add release tag"
tasks = "scripts:addReleaseTag"
}
gradle {
name = "Publish plugin"
tasks = "publishPlugin"
}
script {
name = "Push changes to the repo"
scriptContent = """
branch=$(git branch --show-current)
echo current branch is ${'$'}branch
git push origin %build.number%
""".trimIndent()
}
gradle {
name = "YouTrack post release actions"
tasks = "scripts:eapReleaseActions"
}
}
features {
sshAgent {
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

@ -28,10 +28,7 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Publish $releaseType release" name = "Publish $releaseType release"
description = "Build and publish IdeaVim plugin" description = "Build and publish IdeaVim plugin"
artifactRules = """ artifactRules = "build/distributions/*"
build/distributions/*
build/reports/*
""".trimIndent()
params { params {
param("env.ORG_GRADLE_PROJECT_ideaVersion", RELEASE) param("env.ORG_GRADLE_PROJECT_ideaVersion", RELEASE)
@ -93,12 +90,10 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
gradle { gradle {
name = "Calculate new version" name = "Calculate new version"
tasks = "scripts:calculateNewVersion" tasks = "scripts:calculateNewVersion"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
gradle { gradle {
name = "Set TeamCity build number" name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber" tasks = "scripts:setTeamCityBuildNumber"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
// gradle { // gradle {
// name = "Update change log" // name = "Update change log"
@ -111,7 +106,6 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
gradle { gradle {
name = "Add release tag" name = "Add release tag"
tasks = "scripts:addReleaseTag" tasks = "scripts:addReleaseTag"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
script { script {
name = "Run tests" name = "Run tests"
@ -120,7 +114,6 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
gradle { gradle {
name = "Publish release" name = "Publish release"
tasks = "publishPlugin" tasks = "publishPlugin"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
// script { // script {
// name = "Checkout master branch" // name = "Checkout master branch"
@ -152,7 +145,6 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Run Integrations" name = "Run Integrations"
tasks = "releaseActions" tasks = "releaseActions"
gradleParams = "--no-configuration-cache" gradleParams = "--no-configuration-cache"
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
// gradle { // gradle {
// name = "Slack Notification" // name = "Slack Notification"

View File

@ -43,7 +43,7 @@ open class TestingBuildType(
tasks = "clean test" tasks = "clean test"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto" jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
} }
} }

View File

@ -42,7 +42,6 @@ class GithubBuildType(command: String, desc: String) : IdeaVimBuildType({
tasks = command tasks = command
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
} }
} }

View File

@ -1,8 +1,11 @@
package _Self.subprojects package _Self.subprojects
import _Self.buildTypes.CreateNewReleaseBranchFromMaster
import _Self.buildTypes.PrintReleaseBranch
import _Self.buildTypes.PublishVimEngine import _Self.buildTypes.PublishVimEngine
import _Self.buildTypes.ReleaseDev import _Self.buildTypes.ReleaseDev
import _Self.buildTypes.ReleaseEap import _Self.buildTypes.ReleaseEap
import _Self.buildTypes.ReleaseEapFromBranch
import _Self.buildTypes.ReleaseMajor import _Self.buildTypes.ReleaseMajor
import _Self.buildTypes.ReleaseMinor import _Self.buildTypes.ReleaseMinor
import _Self.buildTypes.ReleasePatch import _Self.buildTypes.ReleasePatch
@ -38,4 +41,8 @@ object Releases : Project({
buildType(ReleaseEap) buildType(ReleaseEap)
buildType(ReleaseDev) buildType(ReleaseDev)
buildType(PublishVimEngine) buildType(PublishVimEngine)
buildType(CreateNewReleaseBranchFromMaster)
buildType(PrintReleaseBranch)
buildType(ReleaseEapFromBranch)
}) })

View File

@ -30,5 +30,5 @@ node (Plugins -> teamcity-configs -> teamcity-configs:generate),
the 'Debug' option is available in the context menu for the task. the 'Debug' option is available in the context menu for the task.
*/ */
version = "2024.12" version = "2024.03"
project(_Self.Project) project(_Self.Project)

View File

@ -498,7 +498,7 @@ Contributors:
* [![icon][mail]](mailto:81118900+lippfi@users.noreply.github.com) * [![icon][mail]](mailto:81118900+lippfi@users.noreply.github.com)
[![icon][github]](https://github.com/lippfi) [![icon][github]](https://github.com/lippfi)
&nbsp; &nbsp;
lippfi lippfi,
* [![icon][mail]](mailto:fillipser143@gmail.com) * [![icon][mail]](mailto:fillipser143@gmail.com)
[![icon][github]](https://github.com/Parker7123) [![icon][github]](https://github.com/Parker7123)
&nbsp; &nbsp;
@ -514,7 +514,7 @@ Contributors:
* [![icon][mail]](mailto:77796630+throwaway69420-69420@users.noreply.github.com) * [![icon][mail]](mailto:77796630+throwaway69420-69420@users.noreply.github.com)
[![icon][github]](https://github.com/kun-codes) [![icon][github]](https://github.com/kun-codes)
&nbsp; &nbsp;
Bishwa Saha Bishwa Saha,
* [![icon][mail]](mailto:alexfu@fastmail.com) * [![icon][mail]](mailto:alexfu@fastmail.com)
[![icon][github]](https://github.com/alexfu) [![icon][github]](https://github.com/alexfu)
&nbsp; &nbsp;
@ -542,7 +542,7 @@ Contributors:
* [![icon][mail]](mailto:kirill.karnaukhov@jetbrains.com) * [![icon][mail]](mailto:kirill.karnaukhov@jetbrains.com)
[![icon][github]](https://github.com/kkarnauk) [![icon][github]](https://github.com/kkarnauk)
&nbsp; &nbsp;
Kirill Karnaukhov Kirill Karnaukhov,
* [![icon][mail]](mailto:sander.hestvik@gmail.com) * [![icon][mail]](mailto:sander.hestvik@gmail.com)
[![icon][github]](https://github.com/SanderHestvik) [![icon][github]](https://github.com/SanderHestvik)
&nbsp; &nbsp;
@ -558,35 +558,11 @@ Contributors:
* [![icon][mail]](mailto:j.trimailovas@gmail.com) * [![icon][mail]](mailto:j.trimailovas@gmail.com)
[![icon][github]](https://github.com/trimailov) [![icon][github]](https://github.com/trimailov)
&nbsp; &nbsp;
Justas Trimailovas Justas Trimailovas,
* [![icon][mail]](mailto:justast@wix.com) * [![icon][mail]](mailto:justast@wix.com)
[![icon][github]](https://github.com/justast-wix) [![icon][github]](https://github.com/justast-wix)
&nbsp; &nbsp;
Justas Trimailovas Justas Trimailovas
* [![icon][mail]](mailto:wangxinhe06@gmail.com)
[![icon][github]](https://github.com/wxh06)
&nbsp;
Xinhe Wang
* [![icon][mail]](mailto:vladimir.parfinenko@jetbrains.com)
[![icon][github]](https://github.com/cypok)
&nbsp;
Vladimir Parfinenko
* [![icon][mail]](mailto:sdoerner@google.com)
[![icon][github]](https://github.com/sdoerner)
&nbsp;
Sebastian Dörner
* [![icon][mail]](mailto:ocordova@pulsarml.com)
[![icon][github]](https://github.com/oca159)
&nbsp;
Osvaldo
* [![icon][mail]](mailto:nath@squareup.com)
[![icon][github]](https://github.com/nath)
&nbsp;
Nath Tumlin
* [![icon][mail]](mailto:ilya.usov@jetbrains.com)
[![icon][github]](https://github.com/Iliya-usov)
&nbsp;
Ilya Usov
Previous contributors: Previous contributors:
@ -598,6 +574,10 @@ Previous contributors:
[![icon][github]](https://github.com/kevin70) [![icon][github]](https://github.com/kevin70)
&nbsp; &nbsp;
kk kk
* [![icon][mail]](mailto:gregory.shrago@jetbrains.com)
[![icon][github]](https://github.com/gregsh)
&nbsp;
Greg Shrago
If you are a contributor and your name is not listed here, feel free to If you are a contributor and your name is not listed here, feel free to

View File

@ -1,50 +1,26 @@
[![TeamCity Build][teamcity-build-status-svg]][teamcity-build-status] [![TeamCity Build][teamcity-build-status-svg]][teamcity-build-status]
IdeaVim is an open source project created by 130+ contributors. Would you like to make it even better? Thats wonderful! IdeaVim is an open source project created by 80+ contributors. Would you like to make it even better? Thats wonderful!
This page is created to help you start contributing. And who knows, maybe in a few days this project will be brighter than ever! This page is created to help you start contributing. And who knows, maybe in a few days this project will be brighter than ever!
# Awards for Quality Contributions :warning: The plugin is currently under a huge refactoring aiming to split into vim-engine and IdeaVim in order to
support the new [Fleet IDE](https://www.jetbrains.com/fleet/). Please see [Fleet refactoring](#Fleet-refactoring).
In February 2025, were starting a program to award one-year All Products Pack subscriptions to the implementers of quality contributions to the IdeaVim project. The program will continue for all of 2025 and may be prolonged.
Subscriptions can be awarded for merged pull requests that meet the following requirements:
- The change should be non-trivial, though there might be exceptions — for example, where a trivial fix requires a complicated investigation.
- The change should fully implement a feature or fix the root cause of a bug. Workarounds or hacks are not accepted.
- If applicable, the change should be properly covered with unit tests.
- The work should be performed by the contributor, though the IdeaVim team is happy to review it and give feedback.
- The change should fix an issue or implement a feature filed by another user. If you want to file an issue and provide a solution to it, your request for a license should be explicitly discussed with the IdeaVim team in the ticket comments.
We'd like to make sure this award program is helpful and fair. Since we just started it and still fine-tuning the details, the final say on giving licenses remains with the IdeaVim team and the requirements might evolve over time.
Also, a few notes:
- If you have any doubts about whether your change or fix is eligible for the award, get in touch with us in the comments on YouTrack or in any other way.
- Please mention this program in the pull request text. This is not an absolute requirement, but it will help ensure we know you would like to be considered for an award, but this is not required.
- During 2025, a single person may only receive a single subscription. Even if you make multiple contributions, you will not be eligible for multiple awards.
- Any delays caused by the IdeaVim team will not affect eligibility for an award if the other requirements are met.
- Draft pull requests will not be reviewed unless explicitly requested.
- Tickets with the [ideavim-bounty](https://youtrack.jetbrains.com/issues?q=tag:%20%7BIdeaVim-bounty%7D) tag are good candidates for this award.
## Before you begin ## Before you begin
- The project is primarily written in Kotlin with a few Java files. When contributing to the project, use Kotlin unless - The project is written in Kotlin and Java. Choose whichever language you feel more comfortable with,
youre working in areas where Java is explicitly used. or maybe one that youd like to get to know better (why not start [learning Kotlin](https://kotlinlang.org/docs/tutorials/) right now?).
- If you come across some IntelliJ Platform code, these links may prove helpful: - If you come across some IntelliJ Platform code, these links may prove helpful:
* [IntelliJ Platform SDK](https://plugins.jetbrains.com/docs/intellij/welcome.html) * [IntelliJ architectural overview](https://www.jetbrains.org/intellij/sdk/docs/platform/fundamentals.html)
* [IntelliJ architectural overview](https://plugins.jetbrains.com/docs/intellij/fundamentals.html) * [IntelliJ plugin development resources](https://www.jetbrains.org/intellij/sdk/docs/welcome.html)
* [IntelliJ Platform community space](https://platform.jetbrains.com/)
- Having any difficulties? - Having any difficulties?
Ask any questions in [GitHub discussions](https://github.com/JetBrains/ideavim/discussions) or [IntelliJ Platform community space](https://platform.jetbrains.com/). Join the brand new
[![Join the chat at https://gitter.im/JetBrains/ideavim](https://badges.gitter.im/JetBrains/ideavim.svg)](https://gitter.im/JetBrains/ideavim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
for IdeaVim developers and contributors!
OK, ready to do some coding? OK, ready to do some coding?
@ -106,7 +82,7 @@ If you are looking for:
- Common features: - Common features:
- State machine. How every particular keystroke is parsed in IdeaVim: `KeyHandler.handleKey()`. - State machine. How every particular keystroke is parsed in IdeaVim: `KeyHandler.handleKey()`.
- Options (`incsearch`, `iskeyword`, `relativenumber`): `VimOptionGroup`. - Options (`incsearch`, `iskeyword`, `relativenumber`): `OptionServiceImpl`.
- Plugin startup: `PluginStartup`. - Plugin startup: `PluginStartup`.
- Notifications: `NotificationService`. - Notifications: `NotificationService`.
- Status bar icon: `StatusBar.kt`. - Status bar icon: `StatusBar.kt`.
@ -134,7 +110,7 @@ Cras id tellus in ex imperdiet egestas.
carets, dollar motion, etc. carets, dollar motion, etc.
##### Neovim ##### Neovim
IdeaVim has an integration with neovim in tests. Tests that are performed with `doTest` also executed in IdeaVim has an experimental integration with neovim in tests. Tests that are performed with `doTest` also executed in
neovim instance, and the state of IdeaVim is asserted to be the same as the state of neovim. neovim instance, and the state of IdeaVim is asserted to be the same as the state of neovim.
- Only tests that use `doTest` are checked with neovim. - Only tests that use `doTest` are checked with neovim.
- Tests with `@VimBehaviorDiffers` or `@TestWithoutNeovim` annotations don't use neovim. - Tests with `@VimBehaviorDiffers` or `@TestWithoutNeovim` annotations don't use neovim.
@ -160,15 +136,14 @@ We also support proper command mappings (functions are mapped to `<Plug>...`), t
- Magic is supported as well. See `Magic`. - Magic is supported as well. See `Magic`.
## Fleet ## Fleet refactoring
At the moment, IdeaVim is under an active refactoring aiming to split IdeaVim into two modules: vim-engine and IdeaVim.
The IdeaVim plugin is divided into two main modules: IdeaVim and vim-engine.
IdeaVim serves as a plugin for JetBrains IDEs, while vim-engine is an IntelliJ Platform-independent Vim engine.
This engine is utilized in both the Vim plugin for Fleet and IdeaVim.
If you develop a plugin that depends on IdeaVim: We have an instrument to check that our changes don't affect If you develop a plugin that depends on IdeaVim: We have an instrument to check that our changes don't affect
the plugins in the marketplace. the plugins in the marketplace. Also, we commit to support currently used API at least till the end of 2022.
If you still encounter any issues with the newer versions of IdeaVim, please [contact maintainers](https://github.com/JetBrains/ideavim#contact-maintainers). If you still encounter any issues with the newer versions of IdeaVim, please [contact maintainers](https://github.com/JetBrains/ideavim#contact-maintainers).
We kindly ask you not to use anything from the new API (like `VimEditor`, `injector`) because at the moment we don't
guarantee the compatibility of this API in the future versions.
----- -----
@ -191,7 +166,6 @@ This is just terrible. [You know what to do](https://github.com/JetBrains/ideavi
* [Continuous integration builds](https://ideavim.teamcity.com/) * [Continuous integration builds](https://ideavim.teamcity.com/)
* [Bug tracker](https://youtrack.jetbrains.com/issues/VIM) * [Bug tracker](https://youtrack.jetbrains.com/issues/VIM)
* [IntelliJ Platform community space](https://platform.jetbrains.com/)
* [Chat on gitter](https://gitter.im/JetBrains/ideavim) * [Chat on gitter](https://gitter.im/JetBrains/ideavim)
* [IdeaVim Channel](https://jb.gg/bi6zp7) on [JetBrains Server](https://discord.gg/jetbrains) * [IdeaVim Channel](https://jb.gg/bi6zp7) on [JetBrains Server](https://discord.gg/jetbrains)
* [Plugin homepage](https://plugins.jetbrains.com/plugin/164-ideavim) * [Plugin homepage](https://plugins.jetbrains.com/plugin/164-ideavim)

View File

@ -29,7 +29,7 @@ IdeaVim is a Vim engine for JetBrains IDEs.
#### Compatibility #### Compatibility
IntelliJ IDEA, PyCharm, CLion, PhpStorm, WebStorm, RubyMine, DataGrip, GoLand, Rider, Cursive, IntelliJ IDEA, PyCharm, CLion, PhpStorm, WebStorm, RubyMine, AppCode, DataGrip, GoLand, Rider, Cursive,
Android Studio and other IntelliJ platform based IDEs. Android Studio and other IntelliJ platform based IDEs.
Setup Setup
@ -85,7 +85,7 @@ Here are some examples of supported vim features and commands:
* Motion / deletion / change / window / etc. commands * Motion / deletion / change / window / etc. commands
* Key mappings * Key mappings
* Marks / Macros / Digraphs / Registers * Marks / Macros / Digraphs / Registers
* Some [set commands](https://github.com/JetBrains/ideavim/wiki/set-commands) * Some [set commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands)
* Full Vim regexps for search and search/replace * Full Vim regexps for search and search/replace
* Vim web help * Vim web help
* `~/.ideavimrc` configuration file * `~/.ideavimrc` configuration file
@ -309,9 +309,7 @@ endif
The power of contributing drives IdeaVim :muscle:. Even small contributions matter! The power of contributing drives IdeaVim :muscle:. Even small contributions matter!
See the contribution guide in [CONTRIBUTING.md](CONTRIBUTING.md) to start bringing your value to the project. See [CONTRIBUTING.md](CONTRIBUTING.md) to start bringing your value to the project.
😎 In 2025, we launched a rewards program. See the guide for details.
Authors Authors
------- -------
@ -326,11 +324,11 @@ IdeaVim tips and tricks
- `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9). - `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
- Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim. - Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
- Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only) - Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
- Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/set-commands). - Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`. - Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
- Control the status bar icon via the [`ideastatusicon` option](https://github.com/JetBrains/ideavim/wiki/set-commands). - Control the status bar icon via the [`ideastatusicon` option](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
- Not familiar with the default behaviour during a refactoring? See the [`idearefactormode` option](https://github.com/JetBrains/ideavim/wiki/set-commands). - Not familiar with the default behaviour during a refactoring? See the [`idearefactormode` option](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
Some facts about Vim Some facts about Vim
------- -------
@ -372,12 +370,6 @@ is the full list of synonyms.
- 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). - 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).
- The Vim documentation contains many easter eggs. We encounter them occasionally, but GitHub user mikesmithgh has compiled a substantial collection [here](https://github.com/mikesmithgh/vimpromptu).
- In addition to `:call err_teapot()`, which returns `E418: I'm a teapot`, there is also `:call err_teapot(1)`, which returns `E503: Coffee is currently not available`. Naturally, this is also supported in IdeaVim.
- Insert mode has all `Ctrl` keys mapped, except `Ctrl-B`. In the documentation, it is marked as **"CTRL-B in Insert
mode gone"**. Call `:h i_CTRL-B-gone` in Vim to read why `Ctrl-B` was removed.
License License
------- -------

View File

@ -8,7 +8,7 @@
plugins { plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.serialization") version "2.0.21" kotlin("plugin.serialization") version "2.0.0"
} }
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.1.10-1.0.29") compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.0-1.0.29")
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

@ -45,7 +45,7 @@ buildscript {
} }
dependencies { dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0")
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")
@ -53,11 +53,11 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r") classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r")
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:3.1.1") classpath("io.ktor:ktor-client-core:3.0.2")
classpath("io.ktor:ktor-client-cio:3.1.1") classpath("io.ktor:ktor-client-cio:3.0.2")
classpath("io.ktor:ktor-client-auth:3.1.1") classpath("io.ktor:ktor-client-auth:3.0.2")
classpath("io.ktor:ktor-client-content-negotiation:3.1.1") classpath("io.ktor:ktor-client-content-negotiation:3.0.2")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.1.1") classpath("io.ktor:ktor-serialization-kotlinx-json:3.0.2")
// 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")
@ -66,14 +66,14 @@ buildscript {
plugins { plugins {
java java
kotlin("jvm") version "2.0.21" kotlin("jvm") version "2.0.0"
application application
id("java-test-fixtures") id("java-test-fixtures")
id("org.jetbrains.intellij.platform") version "2.3.0" id("org.jetbrains.intellij.platform") version "2.2.0"
id("org.jetbrains.changelog") version "2.2.1" id("org.jetbrains.changelog") version "2.2.1"
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.1" id("com.dorongold.task-tree") version "4.0.0"
id("com.google.devtools.ksp") version "2.0.21-1.0.25" id("com.google.devtools.ksp") version "2.0.0-1.0.23"
} }
val moduleSources by configurations.registering val moduleSources by configurations.registering
@ -107,7 +107,7 @@ dependencies {
compileOnly(project(":annotation-processors")) compileOnly(project(":annotation-processors"))
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:26.0.2") compileOnly("org.jetbrains:annotations:26.0.1")
intellijPlatform { intellijPlatform {
// Snapshots don't use installers // Snapshots don't use installers
@ -121,6 +121,7 @@ dependencies {
pluginVerifier() pluginVerifier()
zipSigner() zipSigner()
instrumentationTools()
testFramework(TestFrameworkType.Platform) testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5) testFramework(TestFrameworkType.JUnit5)
@ -128,8 +129,6 @@ dependencies {
// AceJump is an optional dependency. We use their SessionManager class to check if it's active // AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19") plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "242.20224.159") plugin("com.intellij.classic.ui", "242.20224.159")
bundledPlugins("org.jetbrains.plugins.terminal", "com.intellij.modules.json")
} }
moduleSources(project(":vim-engine", "sourcesJarArtifacts")) moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
@ -151,17 +150,17 @@ dependencies {
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.0") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.5")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.0") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.5")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.12.0") testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.12.0") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.0") testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.12.0") testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.5")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4 // 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 // Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2") // testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.0") testImplementation("org.junit.vintage:junit-vintage-engine:5.10.5")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3") // testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
} }
@ -171,19 +170,6 @@ configurations {
} }
} }
val currentJavaVersion = javaToolchains.launcherFor {}.get().metadata.languageVersion.toString()
if (currentJavaVersion != javaVersion) {
// NOTE: I made this exception because the default Gradle error message is horrible, noone can understand it.
throw RuntimeException(
"""
Incorrect java version used for building.
IdeaVim uses java version $javaVersion, but the current java version is $currentJavaVersion.
If IntelliJ IDEA is used, change the setting in "Settings | Build, Execution, Deployment | Build Tools | Gradle"
If build is run from the terminal, set JAVA_HOME environment variable to the correct java version.
""".trimIndent()
)
}
tasks { tasks {
test { test {
useJUnitPlatform() useJUnitPlatform()
@ -197,11 +183,6 @@ tasks {
} }
systemProperty("ideavim.nvim.test", System.getProperty("nvim") ?: false) systemProperty("ideavim.nvim.test", System.getProperty("nvim") ?: false)
// This removes all localization plugins from the test version of IJ.
// There is a bug that IJ for tests may be loaded with a different locale and some keys may be missing there,
// what breaks the tests. This usually happens in EAP versions of IJ.
classpath -= classpath.filter { it.name.startsWith("localization-") && it.name.endsWith(".jar") }
} }
compileJava { compileJava {
@ -219,7 +200,7 @@ tasks {
jvmTarget = javaVersion jvmTarget = javaVersion
// 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 = "2.0" apiVersion = "1.9"
freeCompilerArgs = listOf( freeCompilerArgs = listOf(
"-Xjvm-default=all-compatibility", "-Xjvm-default=all-compatibility",
@ -237,7 +218,7 @@ tasks {
kotlinOptions { kotlinOptions {
jvmTarget = javaVersion jvmTarget = javaVersion
apiVersion = "2.0" apiVersion = "1.9"
// Needed to compile the AceJump which uses kotlin beta // Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails // Without these two option compilation fails
@ -334,7 +315,9 @@ intellijPlatform {
name = "IdeaVim" name = "IdeaVim"
changeNotes.set( changeNotes.set(
""" """
Weve launched a program to reward quality contributions with a one-year All Products Pack subscription. Learn more at: <a href="https://github.com/JetBrains/ideavim/blob/master/CONTRIBUTING.md">CONTRIBUTING.md</a> . 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/>
<br/> <br/>
<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a> <a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>
@ -845,7 +828,7 @@ fun updateAuthors(uncheckedEmails: Set<String>) {
} }
fun List<Author>.toMdString(): String { fun List<Author>.toMdString(): String {
return this.joinToString(separator = "") { return this.joinToString {
""" """
| |
|* [![icon][mail]](mailto:${it.mail}) |* [![icon][mail]](mailto:${it.mail})

View File

@ -423,24 +423,6 @@ https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
</details> </details>
<details>
<summary><h2>Mini.ai: Extend and create a/i textobjects (IMPORTANT: The plugin is not related with artificial intelligence)</h2></summary>
### Features:
Provides additional text object motions for handling quotes and brackets. The following motions are included:
- aq: Around any quotes.
- iq: Inside any quotes.
- ab: Around any parentheses, curly braces, and square brackets.
- ib: Inside any parentheses, curly braces, and square brackets.
Original plugin: [mini.ai](https://github.com/echasnovski/mini.ai).
### Setup:
- Add the following command to `~/.ideavimrc`: `set mini-ai`
</details>
<details> <details>
<summary><h2>Which-Key</h2></summary> <summary><h2>Which-Key</h2></summary>
@ -502,3 +484,4 @@ or restart the IDE.
https://plugins.jetbrains.com/plugin/25899-vim-switch https://plugins.jetbrains.com/plugin/25899-vim-switch
</details>

View File

@ -1,44 +0,0 @@
# Some facts about Vim
Lets relax and have some fun now! Here are a few things we've found interesting during development
and would like to share with you.
- There are no such commands as `dd`, `yy`, or `cc`. For example, `dd` is not a separate command for deleting the line,
but a `d` command with a `d` motion.
Wait, but there isn't a `d` motion in Vim! Thats right, and thats why Vim has a dedicated set of commands
for which it checks whether the
[command equals to motion](https://github.com/vim/vim/blob/759d81549c1340185f0d92524c563bb37697ea88/src/normal.c#L6468)
and if so, it executes `_` motion instead.
`_` is an interesting motion that isn't even documented in vi, and it refers to the current line.
So, commands like `dd`, `yy`, and similar ones are simply translated to `d_`, `y_`, etc.
[Here](https://github.com/vim/vim/blob/759d81549c1340185f0d92524c563bb37697ea88/src/normal.c#L6502)
is the source of this knowledge.
- `x`, `D`, and `&` are not separate commands either. They are synonyms of `dl`, `d$`, and `:s\r`, respectively.
[Here](https://github.com/vim/vim/blob/759d81549c1340185f0d92524c563bb37697ea88/src/normal.c#L5365)
is the full list of synonyms.
- You can read a [post](https://github.com/JetBrains/ideavim/wiki/how-many-modes-does-vim-have) about how modes work in Vim and IdeaVim.
- Have you ever used `U` after `dd`? [Don't even try](https://github.com/vim/vim/blob/759d81549c1340185f0d92524c563bb37697ea88/src/ops.c#L874).
- A lot of variables that refer to visual mode start with two uppercase letters, e.g. `VIsual_active`. [Some examples](https://github.com/vim/vim/blob/master/src/normal.c#L17).
As mentioned [here](https://vi.stackexchange.com/a/42885/12441), this was done this way to avoid the clash with X11.
- Other [strange things](https://github.com/vim/vim/blob/759d81549c1340185f0d92524c563bb37697ea88/src/ex_docmd.c#L1845) from vi:
* ":3" jumps to line 3
* ":3|..." prints line 3
* ":|" prints current line
- Vim script doesn't skip white space before comma. `F(a ,b)` => E475.
- 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.
- 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).
- The Vim documentation contains many easter eggs. We encounter them occasionally, but GitHub user mikesmithgh has compiled a substantial collection [here](https://github.com/mikesmithgh/vimpromptu).
- In addition to `:call err_teapot()`, which returns `E418: I'm a teapot`, there is also `:call err_teapot(1)`, which returns `E503: Coffee is currently not available`. Naturally, this is also supported in IdeaVim.
- Insert mode has all `Ctrl` keys mapped, except `Ctrl-B`. In the documentation, it is marked as **"CTRL-B in Insert
mode gone"**. Call `:h i_CTRL-B-gone` in Vim to read why `Ctrl-B` was removed.

View File

@ -16,19 +16,19 @@
# https://data.services.jetbrains.com/products?code=IC # https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases # Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots # And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
ideaVersion=2024.3.3 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
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-43 version=chylex-42
javaVersion=21 javaVersion=17
remoteRobotVersion=0.11.23 remoteRobotVersion=0.11.23
antlrVersion=4.10.1 antlrVersion=4.10.1
# 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.21 kotlinVersion=2.0.0
publishToken=token publishToken=token
publishChannels=eap publishChannels=eap
@ -48,5 +48,3 @@ kotlin.stdlib.default.dependency=false
# Disable incremental annotation processing # Disable incremental annotation processing
ksp.incremental=false ksp.incremental=false
# KSP2 is used with java 21: https://github.com/google/ksp/issues/740#issuecomment-2313498615
ksp.useKSP2=true

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

110555
qodana.sarif.json Normal file

File diff suppressed because it is too large Load Diff

30
qodana.yaml Normal file
View File

@ -0,0 +1,30 @@
version: 1.0
profile:
name: Qodana
include:
- name: CheckDependencyLicenses
exclude:
- name: MoveVariableDeclarationIntoWhen
- name: PluginXmlValidity
- name: RedundantThrows
- name: SuperTearDownInFinally
- name: UnusedReturnValue
- name: All
paths:
- build.gradle.kts
- gradle/wrapper/gradle-wrapper.properties
- src/main/resources/icons/youtrack.svg
- src/main/java/com/maddyhome/idea/vim/helper/SearchHelper.java
- src/main/java/com/maddyhome/idea/vim/regexp/RegExp.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/SimpleText.kt
- 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
- tests/ui-fixtures
dependencyIgnores:
- name: "acejump"
- name: "icu4j"
- name: "antlr-runtime"
- name: "javax.json"

View File

@ -20,13 +20,13 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.10") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
implementation("io.ktor:ktor-client-core:3.1.1") implementation("io.ktor:ktor-client-core:3.0.2")
implementation("io.ktor:ktor-client-cio:3.1.1") implementation("io.ktor:ktor-client-cio:3.0.2")
implementation("io.ktor:ktor-client-content-negotiation:3.1.1") implementation("io.ktor:ktor-client-content-negotiation:3.0.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.1") implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.2")
implementation("io.ktor:ktor-client-auth:3.1.1") implementation("io.ktor:ktor-client-auth:3.0.2")
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
@ -107,6 +107,13 @@ tasks.register("calculateNewEapVersion", JavaExec::class) {
args = listOf("${rootProject.rootDir}") args = listOf("${rootProject.rootDir}")
} }
tasks.register("calculateNewEapVersionFromBranch", JavaExec::class) {
group = "release"
mainClass.set("scripts.release.CalculateNewEapVersionFromBranchKt")
classpath = sourceSets["main"].runtimeClasspath
args = listOf("${rootProject.rootDir}")
}
tasks.register("calculateNewDevVersion", JavaExec::class) { tasks.register("calculateNewDevVersion", JavaExec::class) {
group = "release" group = "release"
mainClass.set("scripts.release.CalculateNewDevVersionKt") mainClass.set("scripts.release.CalculateNewDevVersionKt")

View File

@ -0,0 +1,49 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package scripts.release
fun main(args: Array<String> ) {
val projectDir = args[0]
println("Working directory: $projectDir")
val branch = withRepo(projectDir) { it.branch }
val (majorBranchVersion, minorBranchVersion) = versions(branch)
val versions = getVersionsExistingVersionsFor(majorBranchVersion, minorBranchVersion, projectDir)
val maxExistingVersion = versions.keys.maxOrNull()
val nextVersion = if (maxExistingVersion != null) {
if (maxExistingVersion.suffixTokens.isEmpty()) {
maxExistingVersion.nextPatch().withSuffix("eap.1").value
}
else {
check(maxExistingVersion.suffixTokens.size == 2) {
"We should have exactly two suffix tokens. Current tokens: ${maxExistingVersion.suffixTokens.toList()}"
}
check(maxExistingVersion.suffixTokens[0] == "eap") {
"First suffix token must be eap. Current tokens: ${maxExistingVersion.suffixTokens.toList()}"
}
val newEapNumber = maxExistingVersion.suffixTokens[1].toInt().inc()
maxExistingVersion.withSuffix("eap.$newEapNumber").value
}
} else {
"$majorBranchVersion.$minorBranchVersion.0-eap.1"
}
println("Next eap version: $nextVersion")
println("##teamcity[setParameter name='env.ORG_GRADLE_PROJECT_version' value='$nextVersion']")
}
private val regex = "releases/(\\d+)\\.(\\d+)\\.x".toRegex()
private fun versions(branchName: String): Pair<Int, Int> {
val match = regex.matchEntire(branchName) ?: error("Cannot match branch: $branchName")
val major = match.groups[1]
val minor = match.groups[2]
return major!!.value.toInt() to minor!!.value.toInt()
}

View File

@ -19,4 +19,3 @@ include("tests:long-running-tests")
include("tests:ui-ij-tests") include("tests:ui-ij-tests")
include("tests:ui-py-tests") include("tests:ui-py-tests")
include("tests:ui-fixtures") include("tests:ui-fixtures")
include("tests:ui-rd-tests")

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim
import com.intellij.ide.BrowserUtil import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.IdeaPluginDescriptor import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.ide.plugins.InstalledPluginsState
import com.intellij.ide.plugins.PluginStateListener import com.intellij.ide.plugins.PluginStateListener
import com.intellij.ide.plugins.PluginStateManager import com.intellij.ide.plugins.PluginStateManager
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
@ -22,7 +21,6 @@ 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.IjVimEnabler
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.initInjector
import com.maddyhome.idea.vim.ui.JoinEap import com.maddyhome.idea.vim.ui.JoinEap
@ -42,28 +40,22 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
if (firstInitializationOccurred) return if (firstInitializationOccurred) return
firstInitializationOccurred = true firstInitializationOccurred = true
if (!VimPlugin.getVimState().wasSubscribedToEAPAutomatically && ApplicationManager.getApplication().isEAP && !JoinEap.eapActive()) { if (!VimPlugin.getVimState().wasSubscibedToEAPAutomatically && ApplicationManager.getApplication().isEAP && !JoinEap.eapActive()) {
VimPlugin.getVimState().wasSubscribedToEAPAutomatically = true VimPlugin.getVimState().wasSubscibedToEAPAutomatically = true
UpdateSettings.getInstance().storedPluginHosts += EAP_LINK UpdateSettings.getInstance().storedPluginHosts += EAP_LINK
} }
// This code should be executed once // This code should be executed once
VimPlugin.getInstance().initialize() VimPlugin.getInstance().initialize()
(injector.enabler as IjVimEnabler).ideOpened()
// Uninstall survey. Should be registered once for all projects // Uninstall survey. Should be registered once for all projects
PluginStateManager.addStateListener(object : PluginStateListener { PluginStateManager.addStateListener(object : PluginStateListener {
override fun install(p0: IdeaPluginDescriptor) {/*Nothing*/ override fun install(p0: IdeaPluginDescriptor) {/*Nothing*/
} }
override fun uninstall(descriptor: IdeaPluginDescriptor) { override fun uninstall(descriptor: IdeaPluginDescriptor) {
val pluginId = VimPlugin.getPluginId() if (descriptor.pluginId == VimPlugin.getPluginId()) {
// This event is called for both uninstall and update. There is no proper way to distinguish these two events. BrowserUtil.open("https://surveys.jetbrains.com/s3/ideavim-uninstall-feedback")
// In order not to show the form for the update, we check if the new version is available. If so,
// this may be an update (and may not), and we don't show the form.
if (descriptor.pluginId == pluginId && !InstalledPluginsState.getInstance().hasNewerVersion(pluginId)) {
BrowserUtil.open("https://jb.gg/z6c7db")
} }
} }
}) })

View File

@ -215,8 +215,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (enabled) { if (enabled) {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn(); VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
} } else {
else {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff(); VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
} }

View File

@ -44,12 +44,7 @@ class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandle
LOG.trace("Before execute for typed action") LOG.trace("Before execute for typed action")
if (editor.isIdeaVimDisabledHere) { if (editor.isIdeaVimDisabledHere) {
LOG.trace("IdeaVim disabled here, finish") LOG.trace("IdeaVim disabled here, finish")
(KeyHandlerKeeper.getInstance().originalHandler as? TypedActionHandlerEx)?.beforeExecute( (KeyHandlerKeeper.getInstance().originalHandler as? TypedActionHandlerEx)?.beforeExecute(editor, charTyped, context, plan)
editor,
charTyped,
context,
plan
)
return return
} }

View File

@ -14,16 +14,13 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
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.AnActionWrapper import com.intellij.openapi.actionSystem.AnActionWrapper
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.invokeLater
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.impl.EditorComponentImpl
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.registry.Registry
import com.intellij.ui.KeyStrokeAdapter import com.intellij.ui.KeyStrokeAdapter
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
@ -44,6 +41,7 @@ import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.key.ShortcutOwner import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo 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.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@ -90,9 +88,9 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState) keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState)
if (start != null) { if (start != null) {
val duration = System.currentTimeMillis() - start val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut execution '$keyStroke': $duration ms") LOG.info("VimShortcut update '$keyStroke': $duration ms")
} }
} catch (_: ProcessCanceledException) { } catch (ignored: ProcessCanceledException) {
// Control-flow exceptions (like ProcessCanceledException) should never be logged // Control-flow exceptions (like ProcessCanceledException) should never be logged
// See {@link com.intellij.openapi.diagnostic.Logger.checkException} // See {@link com.intellij.openapi.diagnostic.Logger.checkException}
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
@ -118,10 +116,9 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
} }
private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus { private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
if (keyStroke == null) return ActionEnableStatus.no("Keystroke is null", LogLevel.DEBUG)
if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG) if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
val editor = getEditor(e) ?: return ActionEnableStatus.no("Can't get Editor", LogLevel.DEBUG) val editor = getEditor(e)
if (editor != null && keyStroke != null) {
if (enableOctopus) { if (enableOctopus) {
if (isOctopusEnabled(keyStroke, editor)) { if (isOctopusEnabled(keyStroke, editor)) {
return ActionEnableStatus.no( return ActionEnableStatus.no(
@ -130,19 +127,9 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
) )
} }
} }
if (e.dataContext.isNotSupportedContextComponent && Registry.`is`("ideavim.only.in.editor.component")) {
// Note: Currently, IdeaVim works ONLY in the editor & ExTextField component. However, the presence of the
// PlatformDataKeys.EDITOR in the data context does not mean that the current focused component is editor.
// Note2: The registry key is needed for quick disabling in case something gets broken. It can be removed after
// some time if no issues are found.
return ActionEnableStatus.no("Context component is not editor", LogLevel.INFO)
}
if (editor.isIdeaVimDisabledHere) { if (editor.isIdeaVimDisabledHere) {
return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO) return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO)
} }
// Workaround for smart step into // Workaround for smart step into
@Suppress("DEPRECATION", "LocalVariableName", "VariableNaming") @Suppress("DEPRECATION", "LocalVariableName", "VariableNaming")
val SMART_STEP_INPLACE_DATA = Key.findKeyByName("SMART_STEP_INPLACE_DATA") val SMART_STEP_INPLACE_DATA = Key.findKeyByName("SMART_STEP_INPLACE_DATA")
@ -181,6 +168,10 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
return ActionEnableStatus.no("The key is tab and the template is active", LogLevel.INFO) return ActionEnableStatus.no("The key is tab and the template is active", LogLevel.INFO)
} }
if ((keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_ENTER) && editor.appCodeTemplateCaptured()) {
return ActionEnableStatus.no("App code template is active", LogLevel.INFO)
}
if (editor.inInsertMode) { if (editor.inInsertMode) {
if (keyCode == KeyEvent.VK_TAB) { if (keyCode == KeyEvent.VK_TAB) {
// TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view // TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
@ -212,9 +203,8 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
val info = savedShortcutConflicts[keyStroke] val info = savedShortcutConflicts[keyStroke]
return when (info?.forEditor(editor.vim)) { return when (info?.forEditor(editor.vim)) {
ShortcutOwner.VIM -> { ShortcutOwner.VIM -> {
ActionEnableStatus.yes("Owner is vim", LogLevel.DEBUG) return ActionEnableStatus.yes("Owner is vim", LogLevel.DEBUG)
} }
ShortcutOwner.IDE -> { ShortcutOwner.IDE -> {
if (!isShortcutConflict(keyStroke)) { if (!isShortcutConflict(keyStroke)) {
ActionEnableStatus.yes("Owner is IDE, but no actionve shortcut conflict", LogLevel.DEBUG) ActionEnableStatus.yes("Owner is IDE, but no actionve shortcut conflict", LogLevel.DEBUG)
@ -222,7 +212,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
ActionEnableStatus.no("Owner is IDE", LogLevel.DEBUG) ActionEnableStatus.no("Owner is IDE", LogLevel.DEBUG)
} }
} }
else -> { else -> {
if (isShortcutConflict(keyStroke)) { if (isShortcutConflict(keyStroke)) {
savedShortcutConflicts[keyStroke] = ShortcutOwnerInfo.allUndefined savedShortcutConflicts[keyStroke] = ShortcutOwnerInfo.allUndefined
@ -231,10 +220,11 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
} }
} }
} }
return ActionEnableStatus.no("End of the selection", LogLevel.DEBUG)
}
private fun isEnabledForEscape(editor: Editor): Boolean { private fun isEnabledForEscape(editor: Editor): Boolean {
val ideaVimSupportDialog = val ideaVimSupportDialog = injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
return editor.isPrimaryEditor() || return editor.isPrimaryEditor() ||
EditorHelper.isFileEditor(editor) && !editor.vim.mode.inNormalMode || EditorHelper.isFileEditor(editor) && !editor.vim.mode.inNormalMode ||
ideaVimSupportDialog && !editor.vim.mode.inNormalMode ideaVimSupportDialog && !editor.vim.mode.inNormalMode
@ -398,9 +388,3 @@ private class ActionEnableStatus(
private enum class LogLevel { private enum class LogLevel {
DEBUG, INFO, ERROR, DEBUG, INFO, ERROR,
} }
private val DataContext.isNotSupportedContextComponent: Boolean
get() {
val contextComponent = this.getData(PlatformDataKeys.CONTEXT_COMPONENT) ?: return true
return contextComponent !is EditorComponentImpl && contextComponent !is ExTextField
}

View File

@ -37,12 +37,7 @@ import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
// todo make it multicaret // todo make it multicaret
private fun doOperatorAction( private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, motionType: SelectionType): Boolean {
editor: VimEditor,
context: ExecutionContext,
textRange: TextRange,
motionType: SelectionType,
): Boolean {
val func = injector.globalOptions().operatorfunc val func = injector.globalOptions().operatorfunc
if (func.isEmpty()) { if (func.isEmpty()) {
VimPlugin.showMessage(MessageHelper.message("E774")) VimPlugin.showMessage(MessageHelper.message("E774"))
@ -66,7 +61,8 @@ private fun doOperatorAction(
// Get the argument for function('...') or funcref('...') for the error message // Get the argument for function('...') or funcref('...') for the error message
val functionName = if (expression is FunctionCallExpression && expression.arguments.isNotEmpty()) { val functionName = if (expression is FunctionCallExpression && expression.arguments.isNotEmpty()) {
expression.arguments[0].evaluate(editor, context, scriptContext).toString() expression.arguments[0].evaluate(editor, context, scriptContext).toString()
} else { }
else {
func func
} }
@ -104,12 +100,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
override val argumentType: Argument.Type = Argument.Type.MOTION override val argumentType: Argument.Type = Argument.Type.MOTION
override fun execute( override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
val argument = cmd.argument as? Argument.Motion ?: return false val argument = cmd.argument as? Argument.Motion ?: return false
if (!editor.inRepeatMode) { if (!editor.inRepeatMode) {
argumentCaptured = argument argumentCaptured = argument

View File

@ -23,12 +23,7 @@ import com.maddyhome.idea.vim.newapi.ij
internal class RepeatChangeAction : VimActionHandler.SingleExecution() { 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( override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
val state = injector.vimState val state = injector.vimState
var lastCommand = VimRepeater.lastChangeCommand var lastCommand = VimRepeater.lastChangeCommand

View File

@ -38,7 +38,7 @@ internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CA
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
cmd: Command, cmd: Command,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments
): Boolean { ): Boolean {
val undo = injector.undo val undo = injector.undo
when (undo) { when (undo) {
@ -67,7 +67,7 @@ internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARE
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
cmd: Command, cmd: Command,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments
): Boolean { ): Boolean {
val undo = injector.undo val undo = injector.undo
when (undo) { when (undo) {
@ -85,12 +85,7 @@ internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARE
internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() { 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( override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
injector.actionExecutor.executeAction(editor, IdeActions.ACTION_QUICK_JAVADOC, context) injector.actionExecutor.executeAction(editor, IdeActions.ACTION_QUICK_JAVADOC, context)
return true return true
} }

View File

@ -12,14 +12,12 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
import org.jetbrains.annotations.ApiStatus
/** /**
* COMPATIBILITY-LAYER: Additional class * COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r * Please see: https://jb.gg/zo8n0r
*/ */
@Deprecated("Use `injector.vimState`") @Deprecated("Use `injector.vimState`")
@ApiStatus.ScheduledForRemoval
class CommandState(private val machine: VimStateMachine) { class CommandState(private val machine: VimStateMachine) {
val mode: Mode val mode: Mode
@ -36,18 +34,15 @@ class CommandState(private val machine: VimStateMachine) {
} }
} }
@get:Deprecated( @Deprecated("Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith(
"Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.commandBuilder", "KeyHandler.getInstance().keyHandlerState.commandBuilder",
"com.maddyhome.idea.vim.KeyHandler" "com.maddyhome.idea.vim.KeyHandler"
) )
) )
@get:ApiStatus.ScheduledForRemoval
val commandBuilder: CommandBuilder val commandBuilder: CommandBuilder
get() = KeyHandler.getInstance().keyHandlerState.commandBuilder get() = KeyHandler.getInstance().keyHandlerState.commandBuilder
@Deprecated( @Deprecated("Use `KeyHandler.keyHandlerState.mappingState", ReplaceWith(
"Use `KeyHandler.keyHandlerState.mappingState", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.mappingState", "KeyHandler.getInstance().keyHandlerState.mappingState",
"com.maddyhome.idea.vim.KeyHandler" "com.maddyhome.idea.vim.KeyHandler"
) )
@ -70,7 +65,6 @@ class CommandState(private val machine: VimStateMachine) {
companion object { companion object {
@JvmStatic @JvmStatic
@Deprecated("Use `injector.vimState`") @Deprecated("Use `injector.vimState`")
@ApiStatus.ScheduledForRemoval
fun getInstance(editor: Editor): CommandState { fun getInstance(editor: Editor): CommandState {
return CommandState(injector.vimState) return CommandState(injector.vimState)
} }

View File

@ -14,7 +14,7 @@ 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 import com.maddyhome.idea.vim.api.VimIndentConfig
internal class IndentConfig private constructor(indentOptions: IndentOptions) : VimIndentConfig { internal class IndentConfig private constructor(indentOptions: IndentOptions): VimIndentConfig {
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

View File

@ -18,8 +18,8 @@ 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 wasSubscribedToEAPAutomatically by StateProperty("was-automatically-subscribed-to-eap") var isNewUndoNotified by StateProperty("idea-undo")
var firstIdeaVimVersion: String? by StringProperty("first-ideavim-version", null) 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")
@ -28,11 +28,6 @@ internal class VimState {
map[name] = it.toBoolean() map[name] = it.toBoolean()
} }
} }
stringMap.keys.forEach { name ->
notifications?.getChild(name)?.getAttributeValue("value")?.let {
stringMap[name] = it.decode
}
}
} }
fun saveData(element: Element) { fun saveData(element: Element) {
@ -44,22 +39,10 @@ internal class VimState {
child.setAttribute("enabled", value.toString()) child.setAttribute("enabled", value.toString())
notifications.addContent(child) notifications.addContent(child)
} }
stringMap.forEach { (name, value) ->
val child = Element(name)
child.setAttribute("value", value.encode)
notifications.addContent(child)
}
} }
} }
private val String?.encode: String get() = this ?: NULL_VALUE
private val String?.decode: String? get() = if (this == NULL_VALUE) null else this
// Settings cannot store null values
private const val NULL_VALUE = "__NULL_VALUE_CONST__"
private val map by lazy { mutableMapOf<String, Boolean>() } private val map by lazy { mutableMapOf<String, Boolean>() }
private val stringMap by lazy { mutableMapOf<String, String?>() }
private class StateProperty(val xmlName: String) : ReadWriteProperty<VimState, Boolean> { private class StateProperty(val xmlName: String) : ReadWriteProperty<VimState, Boolean> {
@ -73,19 +56,3 @@ private class StateProperty(val xmlName: String) : ReadWriteProperty<VimState, B
map[xmlName] = value map[xmlName] = value
} }
} }
private class StringProperty(val propertyName: String, val defaultValue: String?) :
ReadWriteProperty<VimState, String?> {
init {
stringMap[propertyName] = defaultValue
}
override fun getValue(thisRef: VimState, property: KProperty<*>): String? {
return stringMap.getOrPut(propertyName) { defaultValue }
}
override fun setValue(thisRef: VimState, property: KProperty<*>, value: String?) {
stringMap[propertyName] = value
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright 2003-2025 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.customization.feature.terminal
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isAlternateBufferEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isOutputEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isPromptEditor
/**
* The only implementation is defined right here.
*/
// [VERSION UPDATE] 2025.1+ Add 2 new predicates
internal class IdeaVimTerminalDisablerExtension : IdeaVimDisablerExtensionPoint {
override fun isDisabledForEditor(editor: Editor): Boolean {
return editor.isPromptEditor || editor.isOutputEditor || editor.isAlternateBufferEditor
// || editor.isOutputModelEditor || editor.isAlternateBufferModelEditor
}
}

View File

@ -127,7 +127,8 @@ class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPane
override fun close() { override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) { if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).close() } editor?.let { ExOutputPanel.getInstance(it).close() }
} else { }
else {
isActiveInTestMode = false isActiveInTestMode = false
} }
} }

View File

@ -69,8 +69,7 @@ object VimExtensionFacade {
@JvmStatic @JvmStatic
@Deprecated( @Deprecated("Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
"Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
ReplaceWith( ReplaceWith(
"VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)", "VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
"com.maddyhome.idea.vim.VimPlugin" "com.maddyhome.idea.vim.VimPlugin"
@ -187,22 +186,24 @@ object VimExtensionFacade {
return (injector.commandLine as ExEntryPanelService).inputString(editor.vim, context.vim, prompt, finishOn) ?: "" return (injector.commandLine as ExEntryPanelService).inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
} }
/** Get the current contents of the given register similar to 'getreg()'. */
@JvmStatic
@Deprecated("Please use com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister(com.maddyhome.idea.vim.api.VimEditor, char)")
fun getRegister(register: Char): List<KeyStroke>? {
val reg = VimPlugin.getRegister().getRegister(register) ?: return null
return reg.keys
}
/** 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(editor: VimEditor, register: Char): List<KeyStroke>? { fun getRegister(editor: VimEditor, register: Char): List<KeyStroke>? {
val reg = VimPlugin.getRegister() val reg = VimPlugin.getRegister().getRegister(editor, injector.executionContextManager.getEditorExecutionContext(editor), register) ?: return null
.getRegister(editor, injector.executionContextManager.getEditorExecutionContext(editor), register) ?: return null
return reg.keys return reg.keys
} }
@JvmStatic @JvmStatic
fun getRegisterForCaret( fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
editor: VimEditor, val reg = caret.registerStorage.getRegister(register) ?: return null
context: ExecutionContext,
register: Char,
caret: VimCaret,
): List<KeyStroke>? {
val reg = caret.registerStorage.getRegister(editor, context, register) ?: return null
return reg.keys return reg.keys
} }
@ -232,7 +233,7 @@ object VimExtensionFacade {
defaultArgs: List<Pair<String, Expression>>, defaultArgs: List<Pair<String, Expression>>,
hasOptionalArguments: Boolean, hasOptionalArguments: Boolean,
flags: EnumSet<FunctionFlag>, flags: EnumSet<FunctionFlag>,
function: ScriptFunction, function: ScriptFunction
) { ) {
var functionDeclaration: FunctionDeclaration? = null var functionDeclaration: FunctionDeclaration? = null
val body = listOf(object : Executable { val body = listOf(object : Executable {
@ -262,7 +263,8 @@ object VimExtensionFacade {
} }
fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) { fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) {
exportScriptFunction(null, name, listOf("type"), emptyList(), false, noneOfEnum()) { editor, context, args -> exportScriptFunction(null, name, listOf("type"), emptyList(), false, noneOfEnum()) {
editor, context, args ->
val type = args["type"]?.asString() val type = args["type"]?.asString()
val selectionType = when (type) { val selectionType = when (type) {
@ -274,7 +276,8 @@ fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFu
if (function.apply(editor, context, selectionType)) { if (function.apply(editor, context, selectionType)) {
ExecutionResult.Success ExecutionResult.Success
} else { }
else {
ExecutionResult.Error ExecutionResult.Error
} }
} }

View File

@ -12,9 +12,7 @@ import com.intellij.openapi.editor.Document;
import com.maddyhome.idea.vim.KeyHandler; import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.command.MappingMode; import com.maddyhome.idea.vim.command.*;
import com.maddyhome.idea.vim.command.OperatorArguments;
import com.maddyhome.idea.vim.command.TextObjectVisualType;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.extension.ExtensionHandler; import com.maddyhome.idea.vim.extension.ExtensionHandler;
import com.maddyhome.idea.vim.extension.VimExtension; import com.maddyhome.idea.vim.extension.VimExtension;

View File

@ -9,6 +9,7 @@ package com.maddyhome.idea.vim.extension.commentary
import com.intellij.codeInsight.actions.AsyncActionExecutionService import com.intellij.codeInsight.actions.AsyncActionExecutionService
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.runWriteAction
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.openapi.util.Ref import com.intellij.openapi.util.Ref
@ -64,6 +65,7 @@ internal class CommentaryExtension : VimExtension {
editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset) editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset)
} }
return runWriteAction {
// Treat block- and character-wise selections as block comments. Fall back if the first action isn't available // Treat block- and character-wise selections as block comments. Fall back if the first action isn't available
val actions = if (selectionType === SelectionType.LINE_WISE) { val actions = if (selectionType === SelectionType.LINE_WISE) {
listOf(IdeActions.ACTION_COMMENT_LINE, IdeActions.ACTION_COMMENT_BLOCK) listOf(IdeActions.ACTION_COMMENT_LINE, IdeActions.ACTION_COMMENT_BLOCK)
@ -73,7 +75,8 @@ 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) }
return actions.any { executeActionWithCallbackOnSuccess(editor, it, project, context, callback) } actions.any { executeActionWithCallbackOnSuccess(editor, it, project, context, callback) }
}
} }
private fun executeActionWithCallbackOnSuccess( private fun executeActionWithCallbackOnSuccess(

View File

@ -8,6 +8,7 @@
package com.maddyhome.idea.vim.extension.exchange package com.maddyhome.idea.vim.extension.exchange
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.editor.colors.EditorColors
@ -110,12 +111,14 @@ internal class VimExchangeExtension : VimExtension {
private class VExchangeHandler : ExtensionHandler { private class VExchangeHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
runWriteAction {
val mode = editor.mode val mode = editor.mode
// Leave visual mode to create selection marks // Leave visual mode to create selection marks
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
Operator(true).apply(editor, context, mode.selectionType ?: SelectionType.CHARACTER_WISE) Operator(true).apply(editor, context, mode.selectionType ?: SelectionType.CHARACTER_WISE)
} }
} }
}
private class Operator(private val isVisual: Boolean = false) : OperatorFunction { private class Operator(private val isVisual: Boolean = false) : OperatorFunction {
fun Editor.getMarkOffset(mark: Mark) = IjVimEditor(this).getOffset(mark.line, mark.col) fun Editor.getMarkOffset(mark: Mark) = IjVimEditor(this).getOffset(mark.line, mark.col)
@ -223,7 +226,7 @@ internal class VimExchangeExtension : VimExtension {
val unnRegText = getRegister(editor.vim, '"') val unnRegText = getRegister(editor.vim, '"')
val startRegText = getRegister(editor.vim, '*') val startRegText = getRegister(editor.vim, '*')
val plusRegText = getRegister(editor.vim, '+') val plusRegText = getRegister(editor.vim, '+')
runWriteAction {
// TODO handle: // TODO handle:
// " Compare using =~ because "'==' != 0" returns 0 // " Compare using =~ because "'==' != 0" returns 0
// let indent = s:get_setting('exchange_indent', 1) !~ 0 && a:x.type ==# 'V' && a:y.type ==# 'V' // let indent = s:get_setting('exchange_indent', 1) !~ 0 && a:x.type ==# 'V' && a:y.type ==# 'V'
@ -240,6 +243,7 @@ internal class VimExchangeExtension : VimExtension {
setRegister('*', startRegText) setRegister('*', startRegText)
setRegister('+', plusRegText) setRegister('+', plusRegText)
} }
}
private fun compareExchanges(x: Exchange, y: Exchange): ExchangeCompareResult { private fun compareExchanges(x: Exchange, y: Exchange): ExchangeCompareResult {
fun intersects(x: Exchange, y: Exchange) = fun intersects(x: Exchange, y: Exchange) =

View File

@ -1,290 +0,0 @@
/*
* Copyright 2003-2025 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.extension.miniai
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.EnumSet
/**
* A simplified imitation of mini.ai approach for motions "aq", "iq", "ab", "ib".
* Instead of "closest" text object, we apply a "cover-or-next" logic:
*
* 1) Among all candidate pairs, pick any that cover the caret (start <= caret < end).
* Among those, pick the smallest range.
* 2) If none cover the caret, pick the "next" pair (start >= caret) that is closest.
* 3) If none are next, pick the "previous" pair (end <= caret) that is closest.
*
* For "i" text object, we shrink the boundaries inward by one character on each side.
*/
class MiniAI : VimExtension {
companion object {
// <Plug> mappings
private const val PLUG_AQ = "<Plug>mini-ai-aq"
private const val PLUG_IQ = "<Plug>mini-ai-iq"
private const val PLUG_AB = "<Plug>mini-ai-ab"
private const val PLUG_IB = "<Plug>mini-ai-ib"
// Actual user key sequences
private const val KEY_AQ = "aq"
private const val KEY_IQ = "iq"
private const val KEY_AB = "ab"
private const val KEY_IB = "ib"
}
override fun getName() = "mini-ai"
override fun init() {
registerMappings()
}
private fun registerMappings() {
fun createHandler(
rangeFunc: (VimEditor, ImmutableVimCaret, Boolean) -> TextRange?
): ExtensionHandler = object : ExtensionHandler {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
addAction(PortedMiniAiAction(rangeFunc))
}
}
listOf(
// Outer quotes
PLUG_AQ to createHandler { e, c, _ -> findQuoteRange(e, c, isOuter = true) },
// Inner quotes
PLUG_IQ to createHandler { e, c, _ -> findQuoteRange(e, c, isOuter = false) },
// Outer brackets
PLUG_AB to createHandler { e, c, _ -> findBracketRange(e, c, isOuter = true) },
// Inner brackets
PLUG_IB to createHandler { e, c, _ -> findBracketRange(e, c, isOuter = false) }
).forEach { (plug, handler) ->
putExtensionHandlerMapping(MappingMode.XO, injector.parser.parseKeys(plug), owner, handler, false)
}
// Map user keys -> <Plug> keys
listOf(
KEY_AQ to PLUG_AQ,
KEY_IQ to PLUG_IQ,
KEY_AB to PLUG_AB,
KEY_IB to PLUG_IB
).forEach { (key, plug) ->
putKeyMapping(MappingMode.XO, injector.parser.parseKeys(key), owner, injector.parser.parseKeys(plug), true)
}
}
}
// A text object action that uses the "mini.ai-like" picking strategy.
private class PortedMiniAiAction(
private val rangeFunc: (VimEditor, ImmutableVimCaret, Boolean) -> TextRange?
) : TextObjectActionHandler() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_TEXT_BLOCK)
override val visualType: TextObjectVisualType = TextObjectVisualType.CHARACTER_WISE
override fun getRange(
editor: VimEditor,
caret: ImmutableVimCaret,
context: ExecutionContext,
count: Int,
rawCount: Int
): TextRange? = rangeFunc(editor, caret, true).also {
// We don't do 'count' expansions here. If you wanted to replicate
// mini.ai's multi-level expansions, you'd call the "rangeFunc" multiple
// times re-feeding the last output as reference, etc.
}
}
// Utility to register action in KeyHandler
private fun addAction(action: TextObjectActionHandler) {
KeyHandler.getInstance().keyHandlerState.commandBuilder.addAction(action)
}
/* -------------------------------------------------------------------------
* Core mini.ai-like logic
* ------------------------------------------------------------------------- */
/**
* Find a text range for quotes (", ', `) around the caret using a "cover-or-next" approach.
* - If one or more pairs **cover** the caret, pick the *smallest* covering pair.
* - Else pick the "next" pair whose start >= caret offset (closest).
* - Else pick the "previous" pair whose end <= caret offset (closest).
*
* If [isOuter] == false (i.e. 'iq'), shrink the final range by 1 char on each side.
*/
private fun findQuoteRange(editor: VimEditor, caret: ImmutableVimCaret, isOuter: Boolean): TextRange? {
val text = editor.text()
val caretOffset = caret.offset
val caretLine = caret.getLine()
// 1) Gather quotes in *this caret's line*
val lineStart = editor.getLineStartOffset(caretLine)
val lineEnd = editor.getLineEndOffset(caretLine)
val lineText = text.substring(lineStart, lineEnd)
val lineRanges = gatherAllQuoteRanges(lineText).map {
TextRange(it.startOffset + lineStart, it.endOffset + lineStart)
}
val localBest = pickBestRange(lineRanges, caretOffset)
if (localBest != null) {
return adjustRangeForInnerOuter(localBest, isOuter)
}
// 2) Fallback: entire buffer
val allRanges = gatherAllQuoteRanges(text)
val bestOverall = pickBestRange(allRanges, caretOffset) ?: return null
return adjustRangeForInnerOuter(bestOverall, isOuter)
}
/** Adjust final range if user requested 'inner' (i.e. skip bounding chars). */
private fun adjustRangeForInnerOuter(range: TextRange, isOuter: Boolean): TextRange? {
if (isOuter) return range
// For 'inner', skip bounding chars if possible
if (range.endOffset - range.startOffset < 2) return null
return TextRange(range.startOffset + 1, range.endOffset - 1)
}
/**
* Gather all "balanced" pairs for single/double/backtick quotes in the entire text.
* For simplicity, we treat each ["]...["] or [']...['] or [`]...[`] as one range,
* ignoring complicated cases of escaping, multi-line, etc.
*/
private fun gatherAllQuoteRanges(text: CharSequence): List<TextRange> {
val results = mutableListOf<TextRange>()
val patterns = listOf(
"\"([^\"]*)\"",
"'([^']*)'",
"`([^`]*)`"
)
for (p in patterns) {
Regex(p).findAll(text).forEach {
results.add(TextRange(it.range.first, it.range.last + 1))
}
}
return results
}
/**
* Find a text range for brackets using a "cover-or-next" approach.
* We treat bracket pairs ( (), [], {}, <> ) in a naive balanced scanning way.
* If [isOuter] is false, we shrink boundaries to skip the bracket chars.
*/
private fun findBracketRange(editor: VimEditor, caret: ImmutableVimCaret, isOuter: Boolean): TextRange? {
val text = editor.text()
val caretOffset = caret.offset
val caretLine = caret.getLine()
// 1) Gather bracket pairs in *this caret's line*
val lineStart = editor.getLineStartOffset(caretLine)
val lineEnd = editor.getLineEndOffset(caretLine)
val bracketChars = listOf('(', ')', '[', ']', '{', '}', '<', '>')
// Gather local line bracket pairs
val lineText = text.substring(lineStart, lineEnd)
val lineRanges = gatherAllBracketRanges(lineText, bracketChars).map {
// Shift each range's offsets to the global text
TextRange(it.startOffset + lineStart, it.endOffset + lineStart)
}
// Pick the best match on this line
val localBest = pickBestRange(lineRanges, caretOffset)
if (localBest != null) {
return adjustRangeForInnerOuter(localBest, isOuter)
}
// 2) Fallback: gather bracket pairs in the entire file
val allRanges = gatherAllBracketRanges(text, bracketChars)
val bestOverall = pickBestRange(allRanges, caretOffset) ?: return null
return adjustRangeForInnerOuter(bestOverall, isOuter)
}
/**
* Gathers naive "balanced bracket" ranges for the given bracket pairs.
* This is a simplified stack-based approach scanning the entire file text.
*/
private fun gatherAllBracketRanges(
text: CharSequence,
brackets: List<Char>
): List<TextRange> {
val pairs = mapOf('(' to ')', '[' to ']', '{' to '}', '<' to '>')
val results = mutableListOf<TextRange>()
val stack = ArrayDeque<Int>() // offsets of open bracket
val bracketTypeStack = ArrayDeque<Char>() // store which bracket
text.forEachIndexed { i, ch ->
if (pairs.containsKey(ch)) {
// Opening bracket
stack.addLast(i)
bracketTypeStack.addLast(ch)
} else {
// Maybe a closing bracket?
val top = bracketTypeStack.lastOrNull() ?: '\u0000'
if (pairs[top] == ch) {
// Balanced pair
val openPos = stack.removeLast()
bracketTypeStack.removeLast()
results.add(TextRange(openPos, i + 1)) // i+1 for endOffset
}
}
}
return results
}
/**
* Picks best range among [candidates] in a cover-or-next approach:
* 1) Among those covering [caretOffset], pick the narrowest.
* 2) Else pick the "next" bracket whose start >= caret, if any (closest).
* 3) Else pick the "previous" bracket whose end <= caret, if any (closest).
*/
private fun pickBestRange(candidates: List<TextRange>, caretOffset: Int): TextRange? {
if (candidates.isEmpty()) return null
val covering = mutableListOf<TextRange>()
val nextOnes = mutableListOf<TextRange>()
val prevOnes = mutableListOf<TextRange>()
for (r in candidates) {
if (r.startOffset <= caretOffset && caretOffset < r.endOffset) {
covering.add(r)
} else if (r.startOffset >= caretOffset) {
nextOnes.add(r)
} else if (r.endOffset <= caretOffset) {
prevOnes.add(r)
}
}
// 1) Covering, smallest width
if (covering.isNotEmpty()) {
return covering.minByOrNull { it.endOffset - it.startOffset }
}
// 2) Next (closest by startOffset)
if (nextOnes.isNotEmpty()) {
return nextOnes.minByOrNull { kotlin.math.abs(it.startOffset - caretOffset) }
}
// 3) Previous (closest by endOffset)
if (prevOnes.isNotEmpty()) {
return prevOnes.minByOrNull { kotlin.math.abs(it.endOffset - caretOffset) }
}
return null
}

View File

@ -34,6 +34,7 @@ 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
@ -234,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 = injector.searchHelper.findWordNearestCursor(editor.vim, primaryCaret.vim) ?: return val range = findWordUnderCursor(editor, primaryCaret) ?: return
if (range.startOffset > primaryCaret.offset) return if (range.startOffset > primaryCaret.offset) return
IjVimEditor(editor).getText(range) IjVimEditor(editor).getText(range)
} }
@ -299,8 +300,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
} }
private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? { private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? {
// TODO: I think vim-multiple-cursors uses a text object rather than the star operator val range = findWordUnderCursor(editor, caret) ?: return null
val range = injector.searchHelper.findWordNearestCursor(editor.vim, caret.vim) ?: return null
if (range.startOffset > caret.offset) return null if (range.startOffset > caret.offset) return null
enterVisualMode(editor.vim) enterVisualMode(editor.vim)

View File

@ -143,7 +143,7 @@ internal class ReplaceWithRegister : VimExtension {
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) { private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
val registerGroup = injector.registerGroup val registerGroup = injector.registerGroup
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret() val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
val savedRegister = caret.registerStorage.getRegister(editor.vim, context.vim, 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

View File

@ -47,6 +47,7 @@ import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.Mode 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.returnTo
import com.maddyhome.idea.vim.state.mode.selectionType 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
@ -168,7 +169,7 @@ internal class VimSurroundExtension : VimExtension {
// Save old register values for carets // Save old register values for carets
val surroundings = editor.sortedCarets() val surroundings = editor.sortedCarets()
.map { .map {
val oldValue: List<KeyStroke>? = getRegisterForCaret(editor, context, REGISTER, it) val oldValue: List<KeyStroke>? = getRegisterForCaret(REGISTER, it)
setRegisterForCaret(REGISTER, it, null) setRegisterForCaret(REGISTER, it, null)
SurroundingInfo(it, null, oldValue, false) SurroundingInfo(it, null, oldValue, false)
} }
@ -182,12 +183,10 @@ internal class VimSurroundExtension : VimExtension {
val currentSurrounding = getCurrentSurrounding(it.caret, pick(charFrom)) val currentSurrounding = getCurrentSurrounding(it.caret, pick(charFrom))
if (currentSurrounding != null) { if (currentSurrounding != null) {
it.caret.moveToOffset(currentSurrounding.startOffset) it.caret.moveToOffset(currentSurrounding.startOffset)
injector.application.runWriteAction {
editor.deleteString(currentSurrounding) editor.deleteString(currentSurrounding)
} }
}
val registerValue = getRegisterForCaret(editor, context, REGISTER, it.caret) val registerValue = getRegisterForCaret(REGISTER, it.caret)
val innerValue = if (registerValue.isNullOrEmpty()) emptyList() else registerValue val innerValue = if (registerValue.isNullOrEmpty()) emptyList() else registerValue
it.innerText = innerValue it.innerText = innerValue
@ -317,7 +316,7 @@ 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) { if (editor.mode is Mode.CMD_LINE) {
editor.mode = editor.mode.returnTo editor.mode = (editor.mode as Mode.CMD_LINE).returnTo()
} }
return when (editor.mode) { return when (editor.mode) {
is Mode.NORMAL -> injector.markService.getChangeMarks(caret) is Mode.NORMAL -> injector.markService.getChangeMarks(caret)
@ -365,7 +364,7 @@ private fun getSurroundPair(c: Char): SurroundPair? = if (c in SURROUND_PAIRS) {
private fun inputTagPair(editor: Editor, context: DataContext): SurroundPair? { private fun inputTagPair(editor: Editor, context: DataContext): SurroundPair? {
val tagInput = inputString(editor, context, "<", '>') val tagInput = inputString(editor, context, "<", '>')
if (editor.vim.mode is Mode.CMD_LINE) { if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo 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()) {
@ -384,7 +383,7 @@ private fun inputFunctionName(
): SurroundPair? { ): SurroundPair? {
val functionNameInput = inputString(editor, context, "function: ", null) val functionNameInput = inputString(editor, context, "function: ", null)
if (editor.vim.mode is Mode.CMD_LINE) { if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo editor.vim.mode = editor.vim.mode.returnTo()
} }
if (functionNameInput.isEmpty()) return null if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) { return if (withInternalSpaces) {

View File

@ -14,9 +14,7 @@ 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;
import com.maddyhome.idea.vim.api.VimInjectorKt; import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.command.MappingMode; import com.maddyhome.idea.vim.command.*;
import com.maddyhome.idea.vim.command.OperatorArguments;
import com.maddyhome.idea.vim.command.TextObjectVisualType;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.extension.ExtensionHandler; import com.maddyhome.idea.vim.extension.ExtensionHandler;
import com.maddyhome.idea.vim.extension.VimExtension; import com.maddyhome.idea.vim.extension.VimExtension;

View File

@ -14,9 +14,7 @@ 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;
import com.maddyhome.idea.vim.api.VimInjectorKt; import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.command.MappingMode; import com.maddyhome.idea.vim.command.*;
import com.maddyhome.idea.vim.command.OperatorArguments;
import com.maddyhome.idea.vim.command.TextObjectVisualType;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.extension.ExtensionHandler; import com.maddyhome.idea.vim.extension.ExtensionHandler;
import com.maddyhome.idea.vim.extension.VimExtension; import com.maddyhome.idea.vim.extension.VimExtension;

View File

@ -129,10 +129,8 @@ class ChangeGroup : VimChangeGroupBase() {
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)
injector.application.runWriteAction {
CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange)) CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange))
} }
}
override fun autoIndentRange( override fun autoIndentRange(
editor: VimEditor, editor: VimEditor,
@ -184,24 +182,12 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
@Deprecated( @Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.add", imports = ["import com.maddyhome.idea.vim.api.injector"]))
message = "Please use listenersNotifier",
replaceWith = ReplaceWith(
"injector.listenersNotifier.modeChangeListeners.add",
imports = ["import com.maddyhome.idea.vim.api.injector"]
)
)
fun addInsertListener(listener: VimInsertListener) { fun addInsertListener(listener: VimInsertListener) {
injector.listenersNotifier.modeChangeListeners.add(listener) injector.listenersNotifier.modeChangeListeners.add(listener)
} }
@Deprecated( @Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.remove", imports = ["import com.maddyhome.idea.vim.api.injector"]))
message = "Please use listenersNotifier",
replaceWith = ReplaceWith(
"injector.listenersNotifier.modeChangeListeners.remove",
imports = ["import com.maddyhome.idea.vim.api.injector"]
)
)
fun removeInsertListener(listener: VimInsertListener) { fun removeInsertListener(listener: VimInsertListener) {
injector.listenersNotifier.modeChangeListeners.remove(listener) injector.listenersNotifier.modeChangeListeners.remove(listener)
} }

View File

@ -23,6 +23,7 @@ import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
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.EditorHelper; import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.UserDataManager; import com.maddyhome.idea.vim.helper.UserDataManager;
@ -42,7 +43,7 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static com.intellij.openapi.editor.EditorSettings.LineNumerationType; 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.api.VimInjectorKt.options;
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions; import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
@ -156,8 +157,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
private static void repaintRelativeLineNumbers(final @NotNull Editor editor) { private static void repaintRelativeLineNumbers(final @NotNull Editor editor) {
final EditorGutter gutter = editor.getGutter(); final EditorGutter gutter = editor.getGutter();
final EditorGutterComponentEx gutterComponent = final EditorGutterComponentEx gutterComponent = gutter instanceof EditorGutterComponentEx ? (EditorGutterComponentEx) gutter : null;
gutter instanceof EditorGutterComponentEx ? (EditorGutterComponentEx)gutter : null;
if (gutterComponent != null) { if (gutterComponent != null) {
gutterComponent.repaint(); gutterComponent.repaint();
} }
@ -252,18 +252,18 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
@Override @Override
public void notifyIdeaJoin(@NotNull VimEditor editor) { public void notifyIdeaJoin(@NotNull VimEditor editor) {
notifyIdeaJoin(((IjVimEditor)editor).getEditor().getProject(), editor); notifyIdeaJoin(((IjVimEditor) editor).getEditor().getProject(), editor);
} }
@Override @Override
public void updateCaretsVisualAttributes(@NotNull VimEditor editor) { public void updateCaretsVisualAttributes(@NotNull VimEditor editor) {
Editor ijEditor = ((IjVimEditor)editor).getEditor(); Editor ijEditor = ((IjVimEditor) editor).getEditor();
CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor); CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
} }
@Override @Override
public void updateCaretsVisualPosition(@NotNull VimEditor editor) { public void updateCaretsVisualPosition(@NotNull VimEditor editor) {
Editor ijEditor = ((IjVimEditor)editor).getEditor(); Editor ijEditor = ((IjVimEditor) editor).getEditor();
CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor); CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
} }
@ -310,21 +310,26 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
@Override @Override
public @NotNull Collection<VimEditor> getEditorsRaw() { public @NotNull Collection<VimEditor> getEditorsRaw() {
return getLocalEditors().map(IjVimEditor::new).collect(Collectors.toList()); return getLocalEditors()
.map(IjVimEditor::new)
.collect(Collectors.toList());
} }
@Override @Override
public @NotNull Collection<VimEditor> getEditors() { public @NotNull Collection<VimEditor> getEditors() {
return getLocalEditors().filter(UserDataManager::getVimInitialised).map(IjVimEditor::new) return getLocalEditors()
.filter(UserDataManager::getVimInitialised)
.map(IjVimEditor::new)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@Override @Override
public @NotNull Collection<VimEditor> getEditors(@NotNull VimDocument buffer) { public @NotNull Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument(); final Document document = ((IjVimDocument)buffer).getDocument();
return getLocalEditors().filter( return getLocalEditors()
editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document)) .filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
.map(IjVimEditor::new).collect(Collectors.toList()); .map(IjVimEditor::new)
.collect(Collectors.toList());
} }
private Stream<Editor> getLocalEditors() { private Stream<Editor> getLocalEditors() {

View File

@ -32,13 +32,17 @@ 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.VimFileBase import com.maddyhome.idea.vim.api.VimFileBase
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.group.LastTabService.Companion.getInstance import com.maddyhome.idea.vim.group.LastTabService.Companion.getInstance
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.MessageHelper.message 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.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.execute import com.maddyhome.idea.vim.newapi.execute
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -261,6 +265,92 @@ class FileGroup : VimFileBase() {
return null 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) { override fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean) {
val editor = (vimEditor as IjVimEditor).editor val editor = (vimEditor as IjVimEditor).editor
val msg = StringBuilder() val msg = StringBuilder()

View File

@ -14,10 +14,7 @@ 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.history.HistoryBlock; import com.maddyhome.idea.vim.history.*;
import com.maddyhome.idea.vim.history.HistoryEntry;
import com.maddyhome.idea.vim.history.VimHistory;
import com.maddyhome.idea.vim.history.VimHistoryBase;
import org.jdom.Element; import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -28,7 +25,8 @@ import java.util.List;
import static com.maddyhome.idea.vim.history.HistoryConstants.*; import static com.maddyhome.idea.vim.history.HistoryConstants.*;
@State(name = "VimHistorySettings", storages = { @State(name = "VimHistorySettings", storages = {
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)}) @Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
})
public class HistoryGroup extends VimHistoryBase implements PersistentStateComponent<Element> { public class HistoryGroup extends VimHistoryBase implements PersistentStateComponent<Element> {
public void saveData(@NotNull Element element) { public void saveData(@NotNull Element element) {
@ -107,7 +105,7 @@ public class HistoryGroup extends VimHistoryBase implements PersistentStateCompo
return INPUT; return INPUT;
} }
if (type instanceof VimHistory.Type.Custom) { if (type instanceof VimHistory.Type.Custom) {
return ((Type.Custom)type).getId(); return ((Type.Custom) type).getId();
} }
return "unreachable"; return "unreachable";
} }

View File

@ -42,7 +42,7 @@ 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) { 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) var breakindent: Boolean by optionProperty(IjOptions.breakindent)
val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn) val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)

View File

@ -35,7 +35,7 @@ object IjOptions {
// We also override the default value of 'clipboard', because IntelliJ supports the 'ideaput' item, which will use // We also override the default value of 'clipboard', because IntelliJ supports the 'ideaput' item, which will use
// IntelliJ's paste processes (e.g. to convert pasted Java to Kotlin) // IntelliJ's paste processes (e.g. to convert pasted Java to Kotlin)
Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect")) Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux"))
} }
// 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
@ -122,7 +122,6 @@ object IjOptions {
IjOptionConstants.ideavimsupportValues IjOptionConstants.ideavimsupportValues
) )
) )
@JvmField @JvmField
val ideawrite: StringOption = addOption( val ideawrite: StringOption = addOption(
StringOption("ideawrite", GLOBAL, "ideawrite", "all", IjOptionConstants.ideaWriteValues) StringOption("ideawrite", GLOBAL, "ideawrite", "all", IjOptionConstants.ideaWriteValues)
@ -132,21 +131,17 @@ object IjOptions {
"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)) val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100)) val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
// Temporary feature flags during development, not really intended for external use // Temporary feature flags during development, not really intended for external use
val closenotebooks: ToggleOption = val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true)) val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
val commandOrMotionAnnotation: ToggleOption =
addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true)) val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true)) val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
val vimscriptFunctionAnnotation: ToggleOption = val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
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,7 +21,7 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@Service @Service
class IjVimPsiService : VimPsiService { class IjVimPsiService: VimPsiService {
override fun getCommentAtPos(editor: VimEditor, pos: Int): Pair<TextRange, Pair<String, String>?>? { override fun getCommentAtPos(editor: VimEditor, pos: Int): Pair<TextRange, Pair<String, String>?>? {
val psiFile = PsiHelper.getFile(editor.ij) ?: return null val psiFile = PsiHelper.getFile(editor.ij) ?: return null
val psiElement = psiFile.findElementAt(pos) ?: return null val psiElement = psiFile.findElementAt(pos) ?: return null

View File

@ -42,7 +42,7 @@ internal class IjVimStorageService : VimStorageServiceBase() {
private val ijKeys = mutableMapOf<String, Key<out Any?>>() private val ijKeys = mutableMapOf<String, Key<out Any?>>()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
private val <T> com.maddyhome.idea.vim.api.Key<T>.ij: Key<T> private val <T> com.maddyhome.idea.vim.api.Key<T>.ij : Key<T>
get(): Key<T> { get(): Key<T> {
val storedIjKey = ijKeys[this.name] val storedIjKey = ijKeys[this.name]
if (storedIjKey != null) { if (storedIjKey != null) {

View File

@ -39,8 +39,8 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.*;
import java.util.List; import java.util.List;
import java.util.*;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
@ -49,7 +49,7 @@ import static java.util.stream.Collectors.toList;
* @author vlan * @author vlan
*/ */
@State(name = "VimKeySettings", storages = {@Storage(value = "$APP_CONFIG$/vim_settings.xml")}) @State(name = "VimKeySettings", storages = {@Storage(value = "$APP_CONFIG$/vim_settings.xml")})
public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponent<Element> { public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponent<Element>{
public static final @NonNls String SHORTCUT_CONFLICTS_ELEMENT = "shortcut-conflicts"; public static final @NonNls String SHORTCUT_CONFLICTS_ELEMENT = "shortcut-conflicts";
private static final @NonNls String SHORTCUT_CONFLICT_ELEMENT = "shortcut-conflict"; private static final @NonNls String SHORTCUT_CONFLICT_ELEMENT = "shortcut-conflict";
private static final @NonNls String OWNER_ATTRIBUTE = "owner"; private static final @NonNls String OWNER_ATTRIBUTE = "owner";
@ -303,8 +303,8 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
@Override @Override
public @NotNull List<NativeAction> getActions(@NotNull VimEditor editor, @NotNull KeyStroke keyStroke) { public @NotNull List<NativeAction> getActions(@NotNull VimEditor editor, @NotNull KeyStroke keyStroke) {
return getActions(((IjVimEditor)editor).getEditor().getComponent(), keyStroke).stream().map(IjNativeAction::new) return getActions(((IjVimEditor)editor).getEditor().getComponent(), keyStroke).stream()
.collect(toList()); .map(IjNativeAction::new).collect(toList());
} }
private static @NotNull List<AnAction> getLocalActions(@NotNull Component component, @NotNull KeyStroke keyStroke) { private static @NotNull List<AnAction> getLocalActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
@ -314,7 +314,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
if (c instanceof JComponent) { if (c instanceof JComponent) {
final List<AnAction> actions = ActionUtil.getActions((JComponent)c); final List<AnAction> actions = ActionUtil.getActions((JComponent)c);
for (AnAction action : actions) { for (AnAction action : actions) {
if (action instanceof VimShortcutKeyAction || action == VimShortcutKeyAction.getInstance()) { if (action instanceof VimShortcutKeyAction) {
continue; continue;
} }
final Shortcut[] shortcuts = action.getShortcutSet().getShortcuts(); final Shortcut[] shortcuts = action.getShortcutSet().getShortcuts();
@ -334,11 +334,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
for (String id : keymap.getActionIds(keyStroke)) { for (String id : keymap.getActionIds(keyStroke)) {
final AnAction action = ActionManager.getInstance().getAction(id); final AnAction action = ActionManager.getInstance().getAction(id);
if (action != null) {
// EmptyAction is used to reserve a shortcut, but can't be executed. Code can ask for an action by ID and
// use its shortcut(s) to dynamically register a new action on a component in the UI hierarchy. It's not
// useful for our needs here - we'll pick up the real action when we get local actions.
if (action != null && !(action instanceof EmptyAction)) {
results.add(action); results.add(action);
} }
} }
@ -359,26 +355,21 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
} }
@Override @Override
public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, @NotNull List<? extends KeyStroke> prefix, @NotNull VimEditor editor) {
@NotNull List<? extends KeyStroke> prefix,
@NotNull VimEditor editor) {
List<Pair<Set<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes, prefix); List<Pair<Set<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes, prefix);
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
for (Pair<Set<MappingMode>, MappingInfo> row : rows) { for (Pair<Set<MappingMode>, MappingInfo> row : rows) {
MappingInfo mappingInfo = row.getSecond(); MappingInfo mappingInfo = row.getSecond();
builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 3, ' ')); builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 3, ' '));
builder.append( builder.append(StringsKt.padEnd(VimInjectorKt.getInjector().getParser().toKeyNotation(mappingInfo.getFromKeys()) + " ", 12, ' '));
StringsKt.padEnd(VimInjectorKt.getInjector().getParser().toKeyNotation(mappingInfo.getFromKeys()) + " ", 12,
' '));
builder.append(mappingInfo.isRecursive() ? " " : "*"); // Or `&` if script-local mappings being recursive builder.append(mappingInfo.isRecursive() ? " " : "*"); // Or `&` if script-local mappings being recursive
builder.append(" "); // Should be `@` if it's a buffer-local mapping builder.append(" "); // Should be `@` if it's a buffer-local mapping
builder.append(mappingInfo.getPresentableString()); builder.append(mappingInfo.getPresentableString());
builder.append("\n"); builder.append("\n");
} }
VimOutputPanel outputPanel = injector.getOutputPanel() VimOutputPanel outputPanel = injector.getOutputPanel().getOrCreate(editor, injector.getExecutionContextManager().getEditorExecutionContext(editor));
.getOrCreate(editor, injector.getExecutionContextManager().getEditorExecutionContext(editor));
outputPanel.addText(builder.toString(), true); outputPanel.addText(builder.toString(), true);
outputPanel.show(); outputPanel.show();
return true; return true;

View File

@ -9,6 +9,7 @@ package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.completion.CompletionPhase import com.intellij.codeInsight.completion.CompletionPhase
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
@ -40,6 +41,9 @@ internal class MacroGroup : VimMacroBase() {
context: ExecutionContext, context: ExecutionContext,
total: Int, total: Int,
) { ) {
// This is to make sure that we don't access potemkin progress from different threads
ApplicationManager.getApplication().assertWriteAccessAllowed()
val project = (editor as IjVimEditor).editor.project val project = (editor as IjVimEditor).editor.project
val keyStack = getInstance().keyStack val keyStack = getInstance().keyStack
if (!keyStack.hasStroke()) { if (!keyStack.hasStroke()) {

View File

@ -19,6 +19,7 @@ import com.intellij.openapi.fileEditor.impl.EditorWindow
import com.maddyhome.idea.vim.KeyHandler 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.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.VimMotionGroupBase
@ -47,10 +48,13 @@ 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.impl.state.VimStateMachineImpl
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
@ -60,6 +64,10 @@ import kotlin.math.min
*/ */
@Service @Service
internal class MotionGroup : VimMotionGroupBase() { internal class MotionGroup : VimMotionGroupBase() {
override fun onAppCodeMovement(editor: VimEditor, caret: VimCaret, offset: Int, oldOffset: Int) {
AppCodeTemplates.onMovement(editor.ij, caret.ij, oldOffset < offset)
}
override fun moveCaretToFirstDisplayLine( override fun moveCaretToFirstDisplayLine(
editor: VimEditor, editor: VimEditor,
caret: ImmutableVimCaret, caret: ImmutableVimCaret,
@ -312,13 +320,11 @@ internal class MotionGroup : VimMotionGroupBase() {
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 val commandLine = injector.commandLine.getActiveCommandLine() ?: return
commandLine.close(refocusOwningEditor = false, resetCaret = false) commandLine.close(refocusOwningEditor = false, resetCaret = false)
injector.outputPanel.getCurrentOutputPanel()?.close() injector.outputPanel.getCurrentOutputPanel()?.close()
} }
else -> {} else -> {}
} }
} }
@ -326,7 +332,12 @@ internal class MotionGroup : VimMotionGroupBase() {
} else { } else {
val state = injector.vimState as VimStateMachineImpl val state = injector.vimState as VimStateMachineImpl
if (state.mode is Mode.VISUAL) { if (state.mode is Mode.VISUAL) {
state.mode = state.mode.returnTo 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() val keyHandler = KeyHandler.getInstance()
KeyHandler.getInstance().reset(keyHandler.keyHandlerState, state.mode) KeyHandler.getInstance().reset(keyHandler.keyHandlerState, state.mode)

View File

@ -34,12 +34,15 @@ import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.SystemInfo
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.handler.KeyMapIssue import com.maddyhome.idea.vim.handler.KeyMapIssue
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.key.ShortcutOwner import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ijOptions
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.statistic.ActionTracker import com.maddyhome.idea.vim.statistic.ActionTracker
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable import com.maddyhome.idea.vim.ui.VimEmulationConfigurable
import com.maddyhome.idea.vim.vimscript.services.VimRcService import com.maddyhome.idea.vim.vimscript.services.VimRcService
@ -59,11 +62,74 @@ internal class NotificationService(private val project: Project?) {
@Suppress("unused") @Suppress("unused")
constructor() : this(null) constructor() : this(null)
fun notifyAboutNewUndo() {} 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,
)
fun notifyAboutIdeaPut() {} 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")
}
})
fun notifyAboutIdeaJoin(editor: VimEditor) {} notification.notify(project)
}
fun notifyAboutIdeaPut() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"""Add <code>ideaput</code> to <code>clipboard</code> option to perform a put via the IDE<br/><b><code>set clipboard+=ideaput</code></b>""",
NotificationType.INFORMATION,
)
notification.addAction(OpenIdeaVimRcAction(notification))
notification.addAction(
AppendToIdeaVimRcAction(
notification,
"set clipboard^=ideaput",
"ideaput",
) {
// Technically, we're supposed to prepend values to clipboard so that it's not added to the "exclude" item.
// Since we don't handle exclude, it's safe to append. But let's be clean.
injector.globalOptions().clipboard.prependValue(OptionConstants.clipboard_ideaput)
},
)
notification.notify(project)
}
fun notifyAboutIdeaJoin(editor: VimEditor) {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"""Put <b><code>set ideajoin</code></b> into your <code>~/.ideavimrc</code> to perform a join via the IDE""",
NotificationType.INFORMATION,
)
notification.addAction(OpenIdeaVimRcAction(notification))
notification.addAction(
AppendToIdeaVimRcAction(
notification,
"set ideajoin",
"ideajoin"
) {
// This is a global-local option. Setting it will always set the global value
injector.ijOptions(editor).ideajoin = true
},
)
notification.addAction(HelpLink(ideajoinExamplesUrl))
notification.notify(project)
}
fun enableRepeatingMode() = Messages.showYesNoDialog( fun enableRepeatingMode() = Messages.showYesNoDialog(
"Do you want to enable repeating keys in macOS on press and hold?\n\n" + "Do you want to enable repeating keys in macOS on press and hold?\n\n" +
@ -149,7 +215,6 @@ internal class NotificationService(private val project: Project?) {
is KeyMapIssue.AddShortcut -> { is KeyMapIssue.AddShortcut -> {
appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>") appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
} }
is KeyMapIssue.RemoveShortcut -> { is KeyMapIssue.RemoveShortcut -> {
appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>") appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
} }
@ -231,8 +296,7 @@ internal class NotificationService(private val project: Project?) {
} }
} + "<small>See the ${ActionCenter.getToolwindowName()} tool window for previous IDs</small>" } + "<small>See the ${ActionCenter.getToolwindowName()} tool window for previous IDs</small>"
notification = notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also {
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also {
it.whenExpired { notification = null } it.whenExpired { notification = null }
it.addAction(StopTracking()) it.addAction(StopTracking())
@ -248,8 +312,7 @@ internal class NotificationService(private val project: Project?) {
} }
} }
class CopyActionId(val id: String?, val project: Project?) : class CopyActionId(val id: String?, val project: Project?) : DumbAwareAction(MessageHelper.message("action.copy.action.id.text")) {
DumbAwareAction(MessageHelper.message("action.copy.action.id.text")) {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
CopyPasteManager.getInstance().setContents(StringSelection(id ?: "")) CopyPasteManager.getInstance().setContents(StringSelection(id ?: ""))
if (id != null) { if (id != null) {

View File

@ -66,7 +66,7 @@ import java.nio.charset.Charset
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.* import java.util.*
internal interface IjVimOptionGroup : VimOptionGroup { internal interface IjVimOptionGroup: VimOptionGroup {
/** /**
* Return an accessor for options that only have a global value * Return an accessor for options that only have a global value
*/ */
@ -151,7 +151,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup, InternalOpt
override fun <T : VimDataType> setOptionValueInternal( override fun <T : VimDataType> setOptionValueInternal(
option: Option<T>, option: Option<T>,
scope: OptionAccessScope, scope: OptionAccessScope,
value: OptionValue<T>, value: OptionValue<T>
) { ) {
super.setOptionValueInternal(option, scope, value) super.setOptionValueInternal(option, scope, value)
} }
@ -391,6 +391,7 @@ private abstract class GlobalLocalOptionToGlobalLocalIdeaSettingMapper<T : VimDa
} }
/** /**
* Maps the `'bomb'` local-to-buffer Vim option to the file's current byte order mark * Maps the `'bomb'` local-to-buffer Vim option to the file's current byte order mark
* *
@ -424,8 +425,7 @@ private class BombOptionMapper : LocalOptionValueOverride<VimInt> {
// Use IntelliJ's own actions to modify the BOM. This will change the BOM stored in the virtual file, update the // Use IntelliJ's own actions to modify the BOM. This will change the BOM stored in the virtual file, update the
// file contents and save it // file contents and save it
val actionId = if (hasBom) "RemoveBom" else "AddBom" val actionId = if (hasBom) "RemoveBom" else "AddBom"
val action = val action = injector.actionExecutor.getAction(actionId) ?: throw ExException("Cannot find native action: $actionId")
injector.actionExecutor.getAction(actionId) ?: throw ExException("Cannot find native action: $actionId")
val context = injector.executionContextManager.getEditorExecutionContext(editor) val context = injector.executionContextManager.getEditorExecutionContext(editor)
injector.actionExecutor.executeAction(editor, action, context) injector.actionExecutor.executeAction(editor, action, context)
return true return true
@ -463,8 +463,8 @@ private class BreakIndentOptionMapper(
* *
* TODO: This is a code style setting - how can we react to changes? * TODO: This is a code style setting - how can we react to changes?
*/ */
private class ColorColumnOptionValueProvider(private val colorColumnOption: StringListOption) : private class ColorColumnOptionValueProvider(private val colorColumnOption: StringListOption)
LocalOptionToGlobalLocalExternalSettingMapper<VimString>(colorColumnOption) { : LocalOptionToGlobalLocalExternalSettingMapper<VimString>(colorColumnOption) {
// The IntelliJ setting is in practice global, from the user's perspective // The IntelliJ setting is in practice global, from the user's perspective
override val canUserModifyExternalLocalValue: Boolean = false override val canUserModifyExternalLocalValue: Boolean = false
@ -510,7 +510,8 @@ private class ColorColumnOptionValueProvider(private val colorColumnOption: Stri
// Given an empty string, hide the margin. // Given an empty string, hide the margin.
if (value == VimString.EMPTY) { if (value == VimString.EMPTY) {
editor.ij.settings.isRightMarginShown = false editor.ij.settings.isRightMarginShown = false
} else { }
else {
editor.ij.settings.isRightMarginShown = true editor.ij.settings.isRightMarginShown = true
val softMargins = mutableListOf<Int>() val softMargins = mutableListOf<Int>()
@ -523,7 +524,8 @@ private class ColorColumnOptionValueProvider(private val colorColumnOption: Stri
// We could perhaps add a property change listener from editor settings state? // We could perhaps add a property change listener from editor settings state?
// (editor.ij as EditorImpl).state.addPropertyChangeListener(...) // (editor.ij as EditorImpl).state.addPropertyChangeListener(...)
// (editor.ij.settings as SettingsImpl).getState().addPropertyChangeListener(...) // (editor.ij.settings as SettingsImpl).getState().addPropertyChangeListener(...)
} else { }
else {
it.toIntOrNull()?.let(softMargins::add) it.toIntOrNull()?.let(softMargins::add)
} }
} }
@ -557,8 +559,8 @@ private class ColorColumnOptionValueProvider(private val colorColumnOption: Stri
* *
* Note that there isn't a global IntelliJ setting for this option. * Note that there isn't a global IntelliJ setting for this option.
*/ */
private class CursorLineOptionMapper(cursorLineOption: ToggleOption) : private class CursorLineOptionMapper(cursorLineOption: ToggleOption)
LocalOptionToGlobalLocalExternalSettingMapper<VimInt>(cursorLineOption) { : LocalOptionToGlobalLocalExternalSettingMapper<VimInt>(cursorLineOption) {
// The IntelliJ setting is in practice global, from the user's perspective // The IntelliJ setting is in practice global, from the user's perspective
override val canUserModifyExternalLocalValue: Boolean = false override val canUserModifyExternalLocalValue: Boolean = false
@ -665,12 +667,7 @@ private class FileEncodingOptionMapper : LocalOptionValueOverride<VimString> {
} }
} }
private fun isSafeToReloadIn( private fun isSafeToReloadIn(virtualFile: VirtualFile, text: CharSequence, bytes: ByteArray, charset: Charset): Magic8 {
virtualFile: VirtualFile,
text: CharSequence,
bytes: ByteArray,
charset: Charset,
): Magic8 {
val bom = virtualFile.bom val bom = virtualFile.bom
if (bom != null && !CharsetToolkit.canHaveBom(charset, bom)) return Magic8.NO_WAY if (bom != null && !CharsetToolkit.canHaveBom(charset, bom)) return Magic8.NO_WAY
@ -683,9 +680,11 @@ private class FileEncodingOptionMapper : LocalOptionValueOverride<VimString> {
var bytesToSave = try { var bytesToSave = try {
StringUtil.convertLineSeparators(loaded, separator).toByteArray(charset) StringUtil.convertLineSeparators(loaded, separator).toByteArray(charset)
} catch (e: UnsupportedOperationException) { }
catch (e: UnsupportedOperationException) {
return Magic8.NO_WAY return Magic8.NO_WAY
} catch (e: NullPointerException) { }
catch (e: NullPointerException) {
return Magic8.NO_WAY return Magic8.NO_WAY
} }
if (bom != null && !ArrayUtil.startsWith(bytesToSave, bom)) { if (bom != null && !ArrayUtil.startsWith(bytesToSave, bom)) {
@ -719,14 +718,12 @@ private class FileFormatOptionMapper : LocalOptionValueOverride<VimString> {
// We should have a virtual file for most scenarios, e.g., scratch files, commit message dialog, etc. // We should have a virtual file for most scenarios, e.g., scratch files, commit message dialog, etc.
// The fallback window (TextComponentEditorImpl) does not have a virtual file // The fallback window (TextComponentEditorImpl) does not have a virtual file
val separator = editor.ij.virtualFile?.let { LoadTextUtil.detectLineSeparator(it, false) } val separator = editor.ij.virtualFile?.let { LoadTextUtil.detectLineSeparator(it, false) }
val value = VimString( val value = VimString(when (separator) {
when (separator) {
LineSeparator.LF.separatorString -> "unix" LineSeparator.LF.separatorString -> "unix"
LineSeparator.CR.separatorString -> "mac" LineSeparator.CR.separatorString -> "mac"
LineSeparator.CRLF.separatorString -> "dos" LineSeparator.CRLF.separatorString -> "dos"
else -> if (injector.systemInfoService.isWindows) "dos" else "unix" else -> if (injector.systemInfoService.isWindows) "dos" else "unix"
} })
)
// There is no difference between user/external/default - the file is always just one format // There is no difference between user/external/default - the file is always just one format
return OptionValue.User(value) return OptionValue.User(value)
@ -765,8 +762,8 @@ private class FileFormatOptionMapper : LocalOptionValueOverride<VimString> {
/** /**
* Maps the `'list'` local-to-window Vim option to the IntelliJ global-local whitespace setting * Maps the `'list'` local-to-window Vim option to the IntelliJ global-local whitespace setting
*/ */
private class ListOptionMapper(listOption: ToggleOption, internalOptionValueAccessor: InternalOptionValueAccessor) : private class ListOptionMapper(listOption: ToggleOption, internalOptionValueAccessor: InternalOptionValueAccessor)
LocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(listOption, internalOptionValueAccessor) { : LocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(listOption, internalOptionValueAccessor) {
override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_IS_WHITESPACES_SHOWN override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_IS_WHITESPACES_SHOWN
@ -790,8 +787,8 @@ private class ListOptionMapper(listOption: ToggleOption, internalOptionValueAcce
* *
* Note that this must work with `'relativenumber'` to correctly handle the hybrid modes. * Note that this must work with `'relativenumber'` to correctly handle the hybrid modes.
*/ */
private class NumberOptionMapper(numberOption: ToggleOption, internalOptionValueAccessor: InternalOptionValueAccessor) : private class NumberOptionMapper(numberOption: ToggleOption, internalOptionValueAccessor: InternalOptionValueAccessor)
LocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(numberOption, internalOptionValueAccessor) { : LocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(numberOption, internalOptionValueAccessor) {
// This is a global-local setting, and can be modified by the user via _View | Active Editor | Show Line Numbers_ // This is a global-local setting, and can be modified by the user via _View | Active Editor | Show Line Numbers_
override val canUserModifyExternalLocalValue: Boolean = true override val canUserModifyExternalLocalValue: Boolean = true
@ -811,11 +808,13 @@ private class NumberOptionMapper(numberOption: ToggleOption, internalOptionValue
if (isShowingRelativeLineNumbers(editor.ij.settings.lineNumerationType)) { if (isShowingRelativeLineNumbers(editor.ij.settings.lineNumerationType)) {
editor.ij.settings.lineNumerationType = LineNumerationType.HYBRID editor.ij.settings.lineNumerationType = LineNumerationType.HYBRID
} }
} else { }
else {
editor.ij.settings.isLineNumbersShown = true editor.ij.settings.isLineNumbersShown = true
editor.ij.settings.lineNumerationType = LineNumerationType.ABSOLUTE editor.ij.settings.lineNumerationType = LineNumerationType.ABSOLUTE
} }
} else { }
else {
// Turn off 'number'. Hide lines if 'relativenumber' is not set, else switch to relative // Turn off 'number'. Hide lines if 'relativenumber' is not set, else switch to relative
if (editor.ij.settings.isLineNumbersShown) { if (editor.ij.settings.isLineNumbersShown) {
if (isShowingRelativeLineNumbers(editor.ij.settings.lineNumerationType)) { if (isShowingRelativeLineNumbers(editor.ij.settings.lineNumerationType)) {
@ -829,8 +828,7 @@ private class NumberOptionMapper(numberOption: ToggleOption, internalOptionValue
override fun onGlobalIdeaValueChanged(propertyName: String) { override fun onGlobalIdeaValueChanged(propertyName: String) {
if (propertyName == EditorSettingsExternalizable.PropNames.PROP_ARE_LINE_NUMBERS_SHOWN if (propertyName == EditorSettingsExternalizable.PropNames.PROP_ARE_LINE_NUMBERS_SHOWN
|| propertyName == EditorSettingsExternalizable.PropNames.PROP_LINE_NUMERATION || propertyName == EditorSettingsExternalizable.PropNames.PROP_LINE_NUMERATION) {
) {
doOnGlobalIdeaValueChanged() doOnGlobalIdeaValueChanged()
} }
} }
@ -865,11 +863,13 @@ private class RelativeNumberOptionMapper(
if (isShowingAbsoluteLineNumbers(editor.ij.settings.lineNumerationType)) { if (isShowingAbsoluteLineNumbers(editor.ij.settings.lineNumerationType)) {
editor.ij.settings.lineNumerationType = LineNumerationType.HYBRID editor.ij.settings.lineNumerationType = LineNumerationType.HYBRID
} }
} else { }
else {
editor.ij.settings.isLineNumbersShown = true editor.ij.settings.isLineNumbersShown = true
editor.ij.settings.lineNumerationType = LineNumerationType.RELATIVE editor.ij.settings.lineNumerationType = LineNumerationType.RELATIVE
} }
} else { }
else {
// Turn off 'relativenumber'. Hide lines if 'number' is not set, else switch to relative // Turn off 'relativenumber'. Hide lines if 'number' is not set, else switch to relative
if (editor.ij.settings.isLineNumbersShown) { if (editor.ij.settings.isLineNumbersShown) {
if (isShowingAbsoluteLineNumbers(editor.ij.settings.lineNumerationType)) { if (isShowingAbsoluteLineNumbers(editor.ij.settings.lineNumerationType)) {
@ -883,8 +883,7 @@ private class RelativeNumberOptionMapper(
override fun onGlobalIdeaValueChanged(propertyName: String) { override fun onGlobalIdeaValueChanged(propertyName: String) {
if (propertyName == EditorSettingsExternalizable.PropNames.PROP_ARE_LINE_NUMBERS_SHOWN if (propertyName == EditorSettingsExternalizable.PropNames.PROP_ARE_LINE_NUMBERS_SHOWN
|| propertyName == EditorSettingsExternalizable.PropNames.PROP_LINE_NUMERATION || propertyName == EditorSettingsExternalizable.PropNames.PROP_LINE_NUMERATION) {
) {
doOnGlobalIdeaValueChanged() doOnGlobalIdeaValueChanged()
} }
} }
@ -915,8 +914,8 @@ private fun isShowingRelativeLineNumbers(lineNumerationType: LineNumerationType)
* We can also clear the overridden IDE setting value by setting it to `-1`. So when the user resets the Vim option to * We can also clear the overridden IDE setting value by setting it to `-1`. So when the user resets the Vim option to
* defaults, it will again map to the global IDE value. It's a shame not all IDE settings do this. * defaults, it will again map to the global IDE value. It's a shame not all IDE settings do this.
*/ */
private class ScrollJumpOptionMapper(option: NumberOption, internalOptionValueAccessor: InternalOptionValueAccessor) : private class ScrollJumpOptionMapper(option: NumberOption, internalOptionValueAccessor: InternalOptionValueAccessor)
GlobalOptionToGlobalLocalIdeaSettingMapper<VimInt>(option, internalOptionValueAccessor) { : GlobalOptionToGlobalLocalIdeaSettingMapper<VimInt>(option, internalOptionValueAccessor) {
override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_VERTICAL_SCROLL_JUMP override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_VERTICAL_SCROLL_JUMP
@ -950,8 +949,8 @@ private class ScrollJumpOptionMapper(option: NumberOption, internalOptionValueAc
* We can also clear the overridden IDE setting value by setting it to `-1`. So when the user resets the Vim option to * We can also clear the overridden IDE setting value by setting it to `-1`. So when the user resets the Vim option to
* defaults, it will again map to the global IDE value. It's a shame not all IDE settings do this. * defaults, it will again map to the global IDE value. It's a shame not all IDE settings do this.
*/ */
private class SideScrollOptionMapper(option: NumberOption, internalOptionValueAccessor: InternalOptionValueAccessor) : private class SideScrollOptionMapper(option: NumberOption, internalOptionValueAccessor: InternalOptionValueAccessor)
GlobalOptionToGlobalLocalIdeaSettingMapper<VimInt>(option, internalOptionValueAccessor) { : GlobalOptionToGlobalLocalIdeaSettingMapper<VimInt>(option, internalOptionValueAccessor) {
override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_HORIZONTAL_SCROLL_JUMP override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_HORIZONTAL_SCROLL_JUMP
@ -1141,8 +1140,8 @@ private abstract class OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<T :
* (window) overrides. The [LocalOptionToGlobalLocalExternalSettingMapper] base class will handle this by calling * (window) overrides. The [LocalOptionToGlobalLocalExternalSettingMapper] base class will handle this by calling
* [setLocalExternalValue] for all open editors for the changed buffer. * [setLocalExternalValue] for all open editors for the changed buffer.
*/ */
private class TextWidthOptionMapper(textWidthOption: NumberOption) : private class TextWidthOptionMapper(textWidthOption: NumberOption)
LocalOptionToGlobalLocalExternalSettingMapper<VimInt>(textWidthOption) { : LocalOptionToGlobalLocalExternalSettingMapper<VimInt>(textWidthOption) {
// The IntelliJ setting is in practice global, from the user's perspective // The IntelliJ setting is in practice global, from the user's perspective
override val canUserModifyExternalLocalValue: Boolean = false override val canUserModifyExternalLocalValue: Boolean = false
@ -1169,7 +1168,8 @@ private class TextWidthOptionMapper(textWidthOption: NumberOption) :
val project = ijEditor.project ?: ProjectManager.getInstance().defaultProject val project = ijEditor.project ?: ProjectManager.getInstance().defaultProject
return if (ijEditor.settings.isWrapWhenTypingReachesRightMargin(project)) { return if (ijEditor.settings.isWrapWhenTypingReachesRightMargin(project)) {
ijEditor.settings.getRightMargin(ijEditor.project).asVimInt() ijEditor.settings.getRightMargin(ijEditor.project).asVimInt()
} else { }
else {
VimInt.ZERO VimInt.ZERO
} }
} }
@ -1215,8 +1215,8 @@ private class TextWidthOptionMapper(textWidthOption: NumberOption) :
/** /**
* Maps the `'wrap'` Vim option to the IntelliJ soft wrap settings * Maps the `'wrap'` Vim option to the IntelliJ soft wrap settings
*/ */
private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAccessor: InternalOptionValueAccessor) : private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAccessor: InternalOptionValueAccessor)
LocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(wrapOption, internalOptionValueAccessor) { : LocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(wrapOption, internalOptionValueAccessor) {
// This is a global-local setting, and can be modified by the user via _View | Active Editor | Soft-Wrap_ // This is a global-local setting, and can be modified by the user via _View | Active Editor | Soft-Wrap_
override val canUserModifyExternalLocalValue: Boolean = true override val canUserModifyExternalLocalValue: Boolean = true
@ -1245,9 +1245,7 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
fun editorKindToSoftWrapAppliancesPlace(kind: EditorKind) = when (kind) { fun editorKindToSoftWrapAppliancesPlace(kind: EditorKind) = when (kind) {
EditorKind.UNTYPED, EditorKind.UNTYPED,
EditorKind.DIFF, EditorKind.DIFF,
EditorKind.MAIN_EDITOR, EditorKind.MAIN_EDITOR -> SoftWrapAppliancePlaces.MAIN_EDITOR
-> SoftWrapAppliancePlaces.MAIN_EDITOR
EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE
// Treat PREVIEW as a kind of MAIN_EDITOR instead of SWAP.PREVIEW. There are fewer noticeable differences // Treat PREVIEW as a kind of MAIN_EDITOR instead of SWAP.PREVIEW. There are fewer noticeable differences
EditorKind.PREVIEW -> SoftWrapAppliancePlaces.MAIN_EDITOR EditorKind.PREVIEW -> SoftWrapAppliancePlaces.MAIN_EDITOR
@ -1263,9 +1261,7 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
val softWrapAppliancePlace = when (editor.ij.editorKind) { val softWrapAppliancePlace = when (editor.ij.editorKind) {
EditorKind.UNTYPED, EditorKind.UNTYPED,
EditorKind.DIFF, EditorKind.DIFF,
EditorKind.MAIN_EDITOR, EditorKind.MAIN_EDITOR -> SoftWrapAppliancePlaces.MAIN_EDITOR
-> SoftWrapAppliancePlaces.MAIN_EDITOR
EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE
EditorKind.PREVIEW -> SoftWrapAppliancePlaces.PREVIEW EditorKind.PREVIEW -> SoftWrapAppliancePlaces.PREVIEW
} }
@ -1308,8 +1304,7 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
override fun onGlobalIdeaValueChanged(propertyName: String) { override fun onGlobalIdeaValueChanged(propertyName: String) {
if (propertyName == EditorSettingsExternalizable.PropNames.PROP_USE_SOFT_WRAPS if (propertyName == EditorSettingsExternalizable.PropNames.PROP_USE_SOFT_WRAPS
|| propertyName == EditorSettingsExternalizable.PropNames.PROP_SOFT_WRAP_FILE_MASKS || propertyName == EditorSettingsExternalizable.PropNames.PROP_SOFT_WRAP_FILE_MASKS) {
) {
doOnGlobalIdeaValueChanged() doOnGlobalIdeaValueChanged()
} }
} }
@ -1336,10 +1331,8 @@ class IjOptionConstants {
const val ideawrite_file: String = "file" const val ideawrite_file: String = "file"
val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled) val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
val ideaRefactorModeValues: Set<String> = val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file) val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
val ideavimsupportValues: Set<String> = val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
} }
} }

View File

@ -37,7 +37,7 @@ class ProcessGroup : VimProcessGroupBase() {
editor: VimEditor, editor: VimEditor,
command: String, command: String,
input: CharSequence?, input: CharSequence?,
currentDirectoryPath: String?, currentDirectoryPath: String?
): String? { ): String? {
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will // This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're // redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're

View File

@ -32,7 +32,8 @@ import java.util.List;
* This group works with command associated with copying and pasting text * This group works with command associated with copying and pasting text
*/ */
@State(name = "VimRegisterSettings", storages = { @State(name = "VimRegisterSettings", storages = {
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)}) @Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
})
public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> { public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> {
static { static {
@ -144,8 +145,9 @@ public class RegisterGroup extends VimRegisterGroupBase implements PersistentSta
final int modifiers = Integer.parseInt(keyElement.getAttributeValue("mods")); final int modifiers = Integer.parseInt(keyElement.getAttributeValue("mods"));
final char c = (char)Integer.parseInt(keyElement.getAttributeValue("char")); final char c = (char)Integer.parseInt(keyElement.getAttributeValue("char"));
//noinspection MagicConstant //noinspection MagicConstant
strokes.add( strokes.add(c == KeyEvent.CHAR_UNDEFINED ?
c == KeyEvent.CHAR_UNDEFINED ? KeyStroke.getKeyStroke(code, modifiers) : KeyStroke.getKeyStroke(c)); KeyStroke.getKeyStroke(code, modifiers) :
KeyStroke.getKeyStroke(c));
} }
register = new Register(key, type, strokes); register = new Register(key, type, strokes);
} }

View File

@ -28,15 +28,11 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.initInjector import com.maddyhome.idea.vim.newapi.initInjector
import org.jdom.Element import org.jdom.Element
@State( @State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
name = "VimJumpsSettings",
storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)]
)
internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateComponent<Element?> { internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateComponent<Element?> {
companion object { companion object {
private val logger = vimLogger<VimJumpServiceImpl>() private val logger = vimLogger<VimJumpServiceImpl>()
} }
override var lastJumpTimeStamp: Long = 0 override var lastJumpTimeStamp: Long = 0
override fun includeCurrentCommandAsNavigation(editor: VimEditor) { override fun includeCurrentCommandAsNavigation(editor: VimEditor) {

View File

@ -45,10 +45,7 @@ import java.util.*
// todo sync vim jumps with ide jumps // todo sync vim jumps with ide jumps
// todo exception after moving to global mark after deleting it via IDE (impossible to receive markChar) // todo exception after moving to global mark after deleting it via IDE (impossible to receive markChar)
@State( @State(name = "VimMarksSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
name = "VimMarksSettings",
storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)]
)
internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateComponent<Element?> { internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateComponent<Element?> {
private fun createOrGetSystemMark(ch: Char, line: Int, col: Int, editor: VimEditor): Mark? { private fun createOrGetSystemMark(ch: Char, line: Int, col: Int, editor: VimEditor): Mark? {
val ijEditor = (editor as IjVimEditor).editor val ijEditor = (editor as IjVimEditor).editor
@ -74,8 +71,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
} }
element.addContent(globalMarksElement) element.addContent(globalMarksElement)
val localMarksElement = Element("localmarks") val localMarksElement = Element("localmarks")
var files: List<LocalMarks<Char, Mark>> = var files: List<LocalMarks<Char, Mark>> = filepathToLocalMarks.values.sortedWith(Comparator.comparing(LocalMarks<Char, Mark>::myTimestamp))
filepathToLocalMarks.values.sortedWith(Comparator.comparing(LocalMarks<Char, Mark>::myTimestamp))
if (files.size > SAVE_MARK_COUNT) { if (files.size > SAVE_MARK_COUNT) {
files = files.subList(files.size - SAVE_MARK_COUNT, files.size) files = files.subList(files.size - SAVE_MARK_COUNT, files.size)
} }
@ -89,12 +85,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
fileMarkElem.setAttribute("name", file) fileMarkElem.setAttribute("name", file)
fileMarkElem.setAttribute("timestamp", java.lang.Long.toString(marks.myTimestamp.time)) fileMarkElem.setAttribute("timestamp", java.lang.Long.toString(marks.myTimestamp.time))
for (mark in marks.values) { for (mark in marks.values) {
if (!Character.isUpperCase(mark.key) && injector.markService.isValidMark( if (!Character.isUpperCase(mark.key) && injector.markService.isValidMark(mark.key, VimMarkService.Operation.SAVE, true)) {
mark.key,
VimMarkService.Operation.SAVE,
true
)
) {
val markElem = Element("mark") val markElem = Element("mark")
markElem.setAttribute("key", mark.key.toString()) markElem.setAttribute("key", mark.key.toString())
markElem.setAttribute("line", mark.line.toString()) markElem.setAttribute("line", mark.line.toString())

View File

@ -31,8 +31,8 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.util.*;
import java.util.List; import java.util.List;
import java.util.*;
public class WindowGroup extends WindowGroupBase { public class WindowGroup extends WindowGroupBase {
@Override @Override
@ -56,7 +56,7 @@ public class WindowGroup extends WindowGroupBase {
} }
public void closeAll(@NotNull ExecutionContext context) { public void closeAll(@NotNull ExecutionContext context) {
getFileEditorManager(((IjEditorExecutionContext)context).getContext()).closeAllFiles(); getFileEditorManager(((IjEditorExecutionContext) context).getContext()).closeAllFiles();
} }
@Override @Override
@ -105,11 +105,8 @@ public class WindowGroup extends WindowGroupBase {
@Override @Override
@RWLockLabel.Readonly @RWLockLabel.Readonly
@RequiresReadLock @RequiresReadLock
public void selectWindowInRow(@NotNull VimCaret caret, public void selectWindowInRow(@NotNull VimCaret caret, @NotNull ExecutionContext context, int relativePosition, boolean vertical) {
@NotNull ExecutionContext context, final Caret ijCaret = ((IjVimCaret) caret).getCaret();
int relativePosition,
boolean vertical) {
final Caret ijCaret = ((IjVimCaret)caret).getCaret();
final FileEditorManagerEx fileEditorManager = getFileEditorManager(((DataContext)context.getContext())); final FileEditorManagerEx fileEditorManager = getFileEditorManager(((DataContext)context.getContext()));
final EditorWindow currentWindow = fileEditorManager.getCurrentWindow(); final EditorWindow currentWindow = fileEditorManager.getCurrentWindow();
if (currentWindow != null) { if (currentWindow != null) {
@ -119,8 +116,7 @@ public class WindowGroup extends WindowGroupBase {
} }
} }
private void selectWindow(@NotNull EditorWindow currentWindow, private void selectWindow(@NotNull EditorWindow currentWindow, @NotNull List<EditorWindow> windows,
@NotNull List<EditorWindow> windows,
int relativePosition) { int relativePosition) {
final int pos = windows.indexOf(currentWindow); final int pos = windows.indexOf(currentWindow);
final int selected = pos + relativePosition; final int selected = pos + relativePosition;
@ -130,8 +126,7 @@ public class WindowGroup extends WindowGroupBase {
private static @NotNull List<EditorWindow> findWindowsInRow(@NotNull Caret caret, private static @NotNull List<EditorWindow> findWindowsInRow(@NotNull Caret caret,
@NotNull EditorWindow editorWindow, @NotNull EditorWindow editorWindow,
@NotNull List<EditorWindow> windows, @NotNull List<EditorWindow> windows, final boolean vertical) {
final boolean vertical) {
final Point anchorPoint = getCaretPoint(caret); final Point anchorPoint = getCaretPoint(caret);
if (anchorPoint != null) { if (anchorPoint != null) {
final List<EditorWindow> result = new ArrayList<>(); final List<EditorWindow> result = new ArrayList<>();

View File

@ -19,6 +19,7 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.util.PlatformUtils
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
@ -33,7 +34,6 @@ import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.RWLockLabel import com.maddyhome.idea.vim.helper.RWLockLabel
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.ide.isClionNova import com.maddyhome.idea.vim.ide.isClionNova
import com.maddyhome.idea.vim.ide.isRider
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
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
@ -79,7 +79,7 @@ internal class PutGroup : VimPutBase() {
vimEditor: VimEditor, vimEditor: VimEditor,
vimContext: ExecutionContext, vimContext: ExecutionContext,
text: ProcessedTextData, text: ProcessedTextData,
selectionType: SelectionType, subMode: SelectionType,
data: PutData, data: PutData,
additionalData: Map<String, Any>, additionalData: Map<String, Any>,
) { ) {
@ -147,7 +147,7 @@ internal class PutGroup : VimPutBase() {
startOffset, startOffset,
endOffset, endOffset,
text.typeInRegister, text.typeInRegister,
selectionType, subMode,
data.caretAfterInsertedText, data.caretAfterInsertedText,
) )
} }
@ -179,8 +179,7 @@ internal class PutGroup : VimPutBase() {
val sizeBeforeInsert = allContentsBefore.size val sizeBeforeInsert = allContentsBefore.size
val firstItemBefore = allContentsBefore.firstOrNull() val firstItemBefore = allContentsBefore.firstOrNull()
logger.debug { "Transferable classes: ${text.transferableData.joinToString { it.javaClass.name }}" } logger.debug { "Transferable classes: ${text.transferableData.joinToString { it.javaClass.name }}" }
val origContent: TextBlockTransferable = val origContent: TextBlockTransferable = injector.clipboardManager.setClipboardText(
injector.clipboardManager.setClipboardText(
text.text, text.text,
transferableData = text.transferableData, transferableData = text.transferableData,
) as TextBlockTransferable ) as TextBlockTransferable
@ -207,7 +206,7 @@ internal class PutGroup : VimPutBase() {
endOffset: Int, endOffset: Int,
): Int { ): Int {
// Temp fix for VIM-2808. Should be removed after rider will fix it's issues // Temp fix for VIM-2808. Should be removed after rider will fix it's issues
if (isRider() || isClionNova()) return endOffset if (PlatformUtils.isRider() || isClionNova()) return endOffset
val startLine = editor.offsetToBufferPosition(startOffset).line val startLine = editor.offsetToBufferPosition(startOffset).line
val endLine = editor.offsetToBufferPosition(endOffset - 1).line val endLine = editor.offsetToBufferPosition(endOffset - 1).line

View File

@ -12,14 +12,12 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.trace import com.intellij.openapi.diagnostic.trace
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.util.concurrency.annotations.RequiresReadLock
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.options import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl.controlNonVimSelectionChange import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl.controlNonVimSelectionChange
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl.predictMode import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl.predictMode
import com.maddyhome.idea.vim.helper.RWLockLabel
import com.maddyhome.idea.vim.helper.exitSelectMode import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.hasVisualSelection import com.maddyhome.idea.vim.helper.hasVisualSelection
@ -35,6 +33,7 @@ import com.maddyhome.idea.vim.state.mode.inCommandLineMode
import com.maddyhome.idea.vim.state.mode.inNormalMode import com.maddyhome.idea.vim.state.mode.inNormalMode
import com.maddyhome.idea.vim.state.mode.inSelectMode import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.inVisualMode import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.returnTo
import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect
@ -76,7 +75,7 @@ internal object IdeaSelectionControl {
} }
if (hasSelection) { if (hasSelection) {
if (editor.vim.inCommandLineMode && editor.vim.mode.returnTo.hasVisualSelection) { if (editor.vim.inCommandLineMode && editor.vim.mode.returnTo().hasVisualSelection) {
logger.trace { "Modifying selection while in Command-line mode, most likely incsearch" } logger.trace { "Modifying selection while in Command-line mode, most likely incsearch" }
return@singleTask return@singleTask
} }
@ -91,8 +90,7 @@ internal object IdeaSelectionControl {
editor.vim.mode = Mode.NORMAL() editor.vim.mode = Mode.NORMAL()
val mode = injector.application.runReadAction { chooseSelectionMode(editor, selectionSource, true) } activateMode(editor, chooseSelectionMode(editor, selectionSource, true))
activateMode(editor, mode)
} else { } else {
logger.debug("None of carets have selection. State before adjustment: ${editor.vim.mode}") logger.debug("None of carets have selection. State before adjustment: ${editor.vim.mode}")
if (editor.vim.inVisualMode) editor.vim.exitVisualMode() if (editor.vim.inVisualMode) editor.vim.exitVisualMode()
@ -118,8 +116,6 @@ internal object IdeaSelectionControl {
* This method is created to improve user experience. It allows avoiding delay in some operations * This method is created to improve user experience. It allows avoiding delay in some operations
* (because [controlNonVimSelectionChange] is not executed immediately) * (because [controlNonVimSelectionChange] is not executed immediately)
*/ */
@RWLockLabel.Readonly
@RequiresReadLock
fun predictMode(editor: Editor, selectionSource: VimListenerManager.SelectionSource): Mode { fun predictMode(editor: Editor, selectionSource: VimListenerManager.SelectionSource): Mode {
if (editor.selectionModel.hasSelection(true)) { if (editor.selectionModel.hasSelection(true)) {
if (dontChangeMode(editor)) return editor.vim.mode if (dontChangeMode(editor)) return editor.vim.mode
@ -153,8 +149,6 @@ internal object IdeaSelectionControl {
return Mode.NORMAL() return Mode.NORMAL()
} }
@RWLockLabel.Readonly
@RequiresReadLock
private fun chooseSelectionMode( private fun chooseSelectionMode(
editor: Editor, editor: Editor,
selectionSource: VimListenerManager.SelectionSource, selectionSource: VimListenerManager.SelectionSource,
@ -164,27 +158,23 @@ internal object IdeaSelectionControl {
return when { return when {
editor.isOneLineMode -> { editor.isOneLineMode -> {
if (logReason) logger.debug("Enter select mode. Reason: one line mode") if (logReason) logger.debug("Enter select mode. Reason: one line mode")
Mode.SELECT(VimPlugin.getVisualMotion().detectSelectionType(editor.vim)) Mode.SELECT(VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim))
} }
selectionSource == VimListenerManager.SelectionSource.MOUSE && OptionConstants.selectmode_mouse in selectmode -> { selectionSource == VimListenerManager.SelectionSource.MOUSE && OptionConstants.selectmode_mouse in selectmode -> {
if (logReason) logger.debug("Enter select mode. Selection source is mouse and selectMode option has mouse") if (logReason) logger.debug("Enter select mode. Selection source is mouse and selectMode option has mouse")
Mode.SELECT(VimPlugin.getVisualMotion().detectSelectionType(editor.vim)) Mode.SELECT(VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim))
} }
editor.isTemplateActive() && editor.vim.isIdeaRefactorModeSelect -> { editor.isTemplateActive() && editor.vim.isIdeaRefactorModeSelect -> {
if (logReason) logger.debug("Enter select mode. Template is active and selectMode has template") if (logReason) logger.debug("Enter select mode. Template is active and selectMode has template")
Mode.SELECT(VimPlugin.getVisualMotion().detectSelectionType(editor.vim)) Mode.SELECT(VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim))
} }
selectionSource == VimListenerManager.SelectionSource.OTHER && OptionConstants.selectmode_ideaselection in selectmode -> { selectionSource == VimListenerManager.SelectionSource.OTHER && OptionConstants.selectmode_ideaselection in selectmode -> {
if (logReason) logger.debug("Enter select mode. Selection source is OTHER and selectMode has refactoring") if (logReason) logger.debug("Enter select mode. Selection source is OTHER and selectMode has refactoring")
Mode.SELECT(VimPlugin.getVisualMotion().detectSelectionType(editor.vim)) Mode.SELECT(VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim))
} }
else -> { else -> {
if (logReason) logger.debug("Enter visual mode") if (logReason) logger.debug("Enter visual mode")
Mode.VISUAL(VimPlugin.getVisualMotion().detectSelectionType(editor.vim)) Mode.VISUAL(VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim))
} }
} }
} }

View File

@ -9,8 +9,6 @@
package com.maddyhome.idea.vim.group.visual package com.maddyhome.idea.vim.group.visual
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import java.awt.event.ActionEvent import java.awt.event.ActionEvent

View File

@ -18,13 +18,13 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
* @author Alex Plate * @author Alex Plate
*/ */
internal class VisualMotionGroup : VimVisualMotionGroupBase() { internal class VisualMotionGroup : VimVisualMotionGroupBase() {
override fun detectSelectionType(editor: VimEditor): SelectionType { override fun autodetectVisualSubmode(editor: VimEditor): SelectionType {
// IJ specific. See https://youtrack.jetbrains.com/issue/VIM-1924. // IJ specific. See https://youtrack.jetbrains.com/issue/VIM-1924.
val project = editor.ij.project val project = editor.ij.project
if (project != null && FindManager.getInstance(project).selectNextOccurrenceWasPerformed()) { if (project != null && FindManager.getInstance(project).selectNextOccurrenceWasPerformed()) {
return SelectionType.CHARACTER_WISE return SelectionType.CHARACTER_WISE
} }
return super.detectSelectionType(editor) return super.autodetectVisualSubmode(editor)
} }
} }

View File

@ -31,7 +31,7 @@ import kotlinx.coroutines.launch
// We use alarm with delay to avoid many actions in case many events are fired at the same time // We use alarm with delay to avoid many actions in case many events are fired at the same time
internal val correctorRequester = MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) internal val correctorRequester = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val LOG = logger<CopilotKeymapCorrector>() private val LOG = logger<CopilotKeymapCorrector>()
@ -106,7 +106,8 @@ private fun correctCopilotKeymap() {
keymap.removeShortcut("copilot.disposeInlays", escapeShortcut) keymap.removeShortcut("copilot.disposeInlays", escapeShortcut)
copilotHideActionMap[keymap.name] = Unit copilotHideActionMap[keymap.name] = Unit
LOG.info("Remove copilot escape shortcut from keymap ${keymap.name}") LOG.info("Remove copilot escape shortcut from keymap ${keymap.name}")
} else { }
else {
copilotHideActionMap.forEach { (name, _) -> copilotHideActionMap.forEach { (name, _) ->
val keymap = KeymapManagerEx.getInstanceEx().getKeymap(name) ?: return@forEach val keymap = KeymapManagerEx.getInstanceEx().getKeymap(name) ?: return@forEach
val currentShortcuts = keymap.getShortcuts("copilot.disposeInlays") val currentShortcuts = keymap.getShortcuts("copilot.disposeInlays")

View File

@ -18,12 +18,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
* Base class for Vim commands handled by existing IDE actions. * Base class for Vim commands handled by existing IDE actions.
*/ */
internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() { internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() {
override fun execute( override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
injector.actionExecutor.executeAction(editor, name = actionName, context = context) injector.actionExecutor.executeAction(editor, name = actionName, context = context)
injector.scroll.scrollCaretIntoView(editor) injector.scroll.scrollCaretIntoView(editor)
return true return true

View File

@ -31,7 +31,7 @@ import kotlinx.coroutines.launch
import javax.swing.KeyStroke import javax.swing.KeyStroke
// We use alarm with delay to avoid many notifications in case many events are fired at the same time // We use alarm with delay to avoid many notifications in case many events are fired at the same time
internal val keyCheckRequests = MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) internal val keyCheckRequests = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/** /**
* This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin * This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
@ -152,5 +152,5 @@ internal sealed interface KeyMapIssue {
val action: String, val action: String,
val actionId: String, val actionId: String,
val shortcut: Shortcut, val shortcut: Shortcut,
) : KeyMapIssue ): KeyMapIssue
} }

View File

@ -31,7 +31,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.IjActionExecutor
import com.maddyhome.idea.vim.helper.inNormalMode import com.maddyhome.idea.vim.helper.inNormalMode
import com.maddyhome.idea.vim.helper.isPrimaryEditor import com.maddyhome.idea.vim.helper.isPrimaryEditor
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
@ -67,8 +66,6 @@ internal class CaretShapeEnterEditorHandler(private val nextHandler: EditorActio
/** /**
* This handler doesn't work in tests for ex commands * This handler doesn't work in tests for ex commands
*
* About this handler: VIM-2974
*/ */
internal abstract class OctopusHandler(private val nextHandler: EditorActionHandler?) : EditorActionHandler() { internal abstract class OctopusHandler(private val nextHandler: EditorActionHandler?) : EditorActionHandler() {
@ -167,7 +164,6 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
} }
if (dataContext?.actionStartedFromVim == true) return true if (dataContext?.actionStartedFromVim == true) return true
if ((injector.actionExecutor as? IjActionExecutor)?.isRunningActionFromVim == true) return true
return false return false
} }

View File

@ -15,7 +15,6 @@ import com.intellij.openapi.editor.CaretVisualAttributes
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
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.util.concurrency.annotations.RequiresEdt
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
@ -89,9 +88,7 @@ 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 if (isIdeaVimDisabledHere) return
ApplicationManager.getApplication().invokeAndWait {
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
// NOTE: At the moment, this causes project leak in tests // NOTE: At the moment, this causes project leak in tests
@ -114,12 +111,9 @@ private fun Editor.updateSecondaryCaretsVisualAttributes() {
} }
} }
private val HIDDEN = private val HIDDEN = CaretVisualAttributes(null, CaretVisualAttributes.Weight.NORMAL, CaretVisualAttributes.Shape.BAR, 0F)
CaretVisualAttributes(null, CaretVisualAttributes.Weight.NORMAL, CaretVisualAttributes.Shape.BAR, 0F) private val BLOCK = CaretVisualAttributes(null, CaretVisualAttributes.Weight.NORMAL, CaretVisualAttributes.Shape.BLOCK, 1.0F)
private val BLOCK = private val BAR = CaretVisualAttributes(null, CaretVisualAttributes.Weight.NORMAL, CaretVisualAttributes.Shape.BAR, 0.25F)
CaretVisualAttributes(null, CaretVisualAttributes.Weight.NORMAL, CaretVisualAttributes.Shape.BLOCK, 1.0F)
private val BAR =
CaretVisualAttributes(null, CaretVisualAttributes.Weight.NORMAL, CaretVisualAttributes.Shape.BAR, 0.25F)
private object AttributesCache { private object AttributesCache {
private var lastGuicursorValue = "" private var lastGuicursorValue = ""
@ -166,14 +160,12 @@ class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener,
updateCaretsVisual(editor) updateCaretsVisual(editor)
} }
@RequiresEdt
private fun updateCaretsVisual(editor: VimEditor) { private fun updateCaretsVisual(editor: VimEditor) {
val ijEditor = (editor as IjVimEditor).editor val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes() ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition() ijEditor.updateCaretsVisualPosition()
} }
@RequiresEdt
fun updateAllEditorsCaretsVisual() { fun updateAllEditorsCaretsVisual() {
injector.editorGroup.getEditors().forEach { editor -> injector.editorGroup.getEditors().forEach { editor ->
updateCaretsVisual(editor) updateCaretsVisual(editor)

View File

@ -79,8 +79,7 @@ public class EditorHelper {
public static int getVisualLineAtMiddleOfScreen(final @NotNull Editor editor) { public static int getVisualLineAtMiddleOfScreen(final @NotNull Editor editor) {
// The editor will return line numbers of virtual space if the text doesn't reach the end of the visible area // The editor will return line numbers of virtual space if the text doesn't reach the end of the visible area
// (either because it's too short, or it's been scrolled up) // (either because it's too short, or it's been scrolled up)
final int lastLineBaseline = final int lastLineBaseline = editor.logicalPositionToXY(new LogicalPosition(new IjVimEditor(editor).lineCount(), 0)).y;
editor.logicalPositionToXY(new LogicalPosition(new IjVimEditor(editor).lineCount(), 0)).y;
final Rectangle visibleArea = getVisibleArea(editor); final Rectangle visibleArea = getVisibleArea(editor);
final int height = min(lastLineBaseline - visibleArea.y, visibleArea.height); final int height = min(lastLineBaseline - visibleArea.y, visibleArea.height);
return editor.yToVisualLine(visibleArea.y + (height / 2)); return editor.yToVisualLine(visibleArea.y + (height / 2));
@ -92,7 +91,8 @@ public class EditorHelper {
// Adjust available height if the ex entry text field is visible // Adjust available height if the ex entry text field is visible
final Rectangle visibleArea = getVisibleArea(editor); final Rectangle visibleArea = getVisibleArea(editor);
final int height = visibleArea.height - getExEntryHeight() - getHorizontalScrollbarHeight(editor); final int height = visibleArea.height - getExEntryHeight() - getHorizontalScrollbarHeight(editor);
return getFullVisualLine(editor, visibleArea.y + height, visibleArea.y, visibleArea.y + height); return getFullVisualLine(editor, visibleArea.y + height, visibleArea.y,
visibleArea.y + height);
} }
public static int getVisualLineAtBottomOfScreen(final @NotNull Editor editor) { public static int getVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
@ -288,8 +288,7 @@ public class EditorHelper {
// virtual space at the bottom of the screen // virtual space at the bottom of the screen
final @NotNull VimEditor editor1 = new IjVimEditor(editor); final @NotNull VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1; final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final int yBottomLineOffset = final int yBottomLineOffset = max(getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine), visibleArea.y);
max(getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine), visibleArea.y);
scrollVertically(editor, min(yVisualLine - caretScreenOffset - inlayOffset, yBottomLineOffset)); scrollVertically(editor, min(yVisualLine - caretScreenOffset - inlayOffset, yBottomLineOffset));
} }
@ -332,9 +331,7 @@ public class EditorHelper {
* @param editor The editor to scroll * @param editor The editor to scroll
* @param visualLine The visual line to place in the middle of the current window * @param visualLine The visual line to place in the middle of the current window
*/ */
public static void scrollVisualLineToMiddleOfScreen(@NotNull Editor editor, public static void scrollVisualLineToMiddleOfScreen(@NotNull Editor editor, int visualLine, boolean allowVirtualSpace) {
int visualLine,
boolean allowVirtualSpace) {
final int y = editor.visualLineToY(EngineEditorHelperKt.normalizeVisualLine(new IjVimEditor(editor), visualLine)); final int y = editor.visualLineToY(EngineEditorHelperKt.normalizeVisualLine(new IjVimEditor(editor), visualLine));
final Rectangle visibleArea = getVisibleArea(editor); final Rectangle visibleArea = getVisibleArea(editor);
final int screenHeight = visibleArea.height; final int screenHeight = visibleArea.height;
@ -426,8 +423,7 @@ public class EditorHelper {
} }
} }
final int columnLeftX = final int columnLeftX = (int) Math.round(editor.visualPositionToPoint2D(new VisualPosition(visualLine, targetVisualColumn)).getX());
(int)Math.round(editor.visualPositionToPoint2D(new VisualPosition(visualLine, targetVisualColumn)).getX());
scrollHorizontally(editor, columnLeftX); scrollHorizontally(editor, columnLeftX);
} }
@ -439,8 +435,8 @@ public class EditorHelper {
// of columns. It also works with inline inlays and folds. It is slightly inaccurate for proportional fonts, but is // of columns. It also works with inline inlays and folds. It is slightly inaccurate for proportional fonts, but is
// still a good solution. Besides, what kind of monster uses Vim with proportional fonts? // still a good solution. Besides, what kind of monster uses Vim with proportional fonts?
final float standardColumnWidth = EditorHelper.getPlainSpaceWidthFloat(editor); final float standardColumnWidth = EditorHelper.getPlainSpaceWidthFloat(editor);
final int screenMidColumn = (int)(screenWidth / standardColumnWidth / 2); final int screenMidColumn = (int) (screenWidth / standardColumnWidth / 2);
final int x = max(0, (int)Math.round(point.getX() - (screenMidColumn * standardColumnWidth))); final int x = max(0, (int) Math.round(point.getX() - (screenMidColumn * standardColumnWidth)));
scrollHorizontally(editor, x); scrollHorizontally(editor, x);
} }
@ -465,8 +461,7 @@ public class EditorHelper {
} }
// Scroll to the left edge of the target column, minus a screenwidth, and adjusted for inlays // Scroll to the left edge of the target column, minus a screenwidth, and adjusted for inlays
final int targetColumnRightX = final int targetColumnRightX = (int) Math.round(editor.visualPositionToPoint2D(new VisualPosition(visualLine, targetVisualColumn + 1)).getX());
(int)Math.round(editor.visualPositionToPoint2D(new VisualPosition(visualLine, targetVisualColumn + 1)).getX());
final int screenWidth = getVisibleArea(editor).width; final int screenWidth = getVisibleArea(editor).width;
scrollHorizontally(editor, targetColumnRightX - screenWidth); scrollHorizontally(editor, targetColumnRightX - screenWidth);
} }
@ -601,8 +596,7 @@ public class EditorHelper {
} }
if (xActualLeft >= leftBound) { if (xActualLeft >= leftBound) {
final VisualPosition nextVisualPosition = final VisualPosition nextVisualPosition = new VisualPosition(closestVisualPosition.line, closestVisualPosition.column + 1);
new VisualPosition(closestVisualPosition.line, closestVisualPosition.column + 1);
final long xActualRight = Math.round(editor.visualPositionToPoint2D(nextVisualPosition).getX()) - 1; final long xActualRight = Math.round(editor.visualPositionToPoint2D(nextVisualPosition).getX()) - 1;
if (xActualRight <= rightBound) { if (xActualRight <= rightBound) {
return closestVisualPosition.column; return closestVisualPosition.column;

View File

@ -20,7 +20,6 @@ import com.intellij.util.ui.table.JBTableRowEditor
import com.maddyhome.idea.vim.api.StringListOptionValue import com.maddyhome.idea.vim.api.StringListOptionValue
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection import com.maddyhome.idea.vim.state.mode.inBlockSelection
@ -41,8 +40,7 @@ internal val Editor.isIdeaVimDisabledHere: Boolean
val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport
return (ideaVimDisabledInDialog(ideaVimSupportValue) && isInDialog()) || return (ideaVimDisabledInDialog(ideaVimSupportValue) && isInDialog()) ||
!ClientId.isCurrentlyUnderLocalId || // CWM-927 !ClientId.isCurrentlyUnderLocalId || // CWM-927
(ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine()) || (ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine())
IdeaVimDisablerExtensionPoint.isDisabledForEditor(this)
} }
private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean { private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean {

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