1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-18 10:31:44 +02:00

Compare commits

..

21 Commits

Author SHA1 Message Date
a7def05aa8 Set plugin version to chylex-28 2024-02-18 18:49:31 +01:00
51e13a5f20 Disable speed search in Project tool window when NERDTree is enabled 2024-02-18 18:49:16 +01:00
9b67260d5a Work around VIM-3306 (make paragraph-motion plugin go to the end of the line) 2024-02-18 18:49:16 +01:00
ac37432db6 Apply scrolloff after executing native IDEA actions 2024-02-18 18:49:16 +01:00
4c946568e4 Stay on same line after reindenting 2024-02-18 18:49:16 +01:00
7e70eed1ab Implement motions to go to next/previous misspelled word 2024-02-18 18:49:16 +01:00
43ae90044b Update search register when using f/t 2024-02-18 18:49:16 +01:00
d79f7c23c5 Automatically add unambiguous imports after running a macro 2024-02-18 18:49:15 +01:00
6033450158 Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-02-18 18:49:15 +01:00
ea86d7132c Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-02-18 18:49:15 +01:00
711d1f0329 Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2024-02-18 18:49:15 +01:00
338e137347 Add support for count for visual and line motion surround 2024-02-18 18:49:15 +01:00
2b0e9bfec5 Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-02-18 18:49:15 +01:00
b42346b9e1 Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-02-18 18:49:15 +01:00
d58d3ca8b0 Revert(VIM-2884): Fix moving lines to cursor 2024-02-18 18:49:15 +01:00
22b2ca2352 Respect count with <Action> mappings 2024-02-18 18:49:15 +01:00
1c98daa180 Add Matchit support for Java statements 2024-02-18 18:49:15 +01:00
de906bcbac Change matchit plugin to use HTML patterns in unrecognized files 2024-02-18 18:49:14 +01:00
05fac8bf00 Reset insert mode when switching active editor 2024-02-18 18:49:14 +01:00
a985d260f7 Remove update checker 2024-02-18 18:49:14 +01:00
9f4c679d77 Set custom plugin version 2024-02-18 18:49:14 +01:00
1083 changed files with 77854 additions and 115863 deletions

View File

@@ -20,10 +20,10 @@ jobs:
fetch-depth: 300 fetch-depth: 300
- name: Get tags - name: Get tags
run: git fetch --tags origin run: git fetch --tags origin
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file
@@ -34,7 +34,7 @@ jobs:
echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-close-youtrack)" >> $GITHUB_ENV echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-close-youtrack)" >> $GITHUB_ENV
- name: Update YouTrack - name: Update YouTrack
run: ./gradlew --no-configuration-cache updateYoutrackOnCommit run: ./gradlew updateYoutrackOnCommit
env: env:
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }} SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }} YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}

View File

@@ -18,16 +18,16 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 300 fetch-depth: 300
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Run tests - name: Run tests
run: ./gradlew --no-configuration-cache integrationsTest run: ./gradlew integrationsTest
env: env:
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }} YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@@ -11,7 +11,7 @@ on:
jobs: jobs:
build: build:
if: false if: github.event.pull_request.merged == true && github.repository == 'JetBrains/ideavim'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -20,17 +20,17 @@ jobs:
fetch-depth: 50 fetch-depth: 50
# See end of file updateChangeslog.yml for explanation of this secret # See end of file updateChangeslog.yml for explanation of this secret
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }} ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Update authors - name: Update authors
id: update_authors id: update_authors
run: ./gradlew --no-configuration-cache updateMergedPr -PprId=${{ github.event.number }} run: ./gradlew updateMergedPr -PprId=${{ github.event.number }}
env: env:
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -9,21 +9,28 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Apply Patch
run: |
git apply tests/ui-ij-tests/src/test/kotlin/ui/octopus.patch
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: zulu distribution: zulu
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg uses: FedericoCarboni/setup-ffmpeg@v3
# - name: Setup Gradle with:
# uses: gradle/gradle-build-action@v2.4.2 # Not strictly necessary, but it may prevent rate limit
# errors especially on GitHub-hosted macos machines.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log & gradle runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -37,7 +44,7 @@ jobs:
run: mv tests/ui-ij-tests/video build/reports run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -45,8 +52,7 @@ jobs:
name: ui-test-fails-report-mac name: ui-test-fails-report-mac
path: | path: |
build/reports build/reports
tests/ui-ij-tests/build/reports sandbox-idea-log
idea-sandbox-log
# build-for-ui-test-linux: # build-for-ui-test-linux:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
@@ -78,4 +84,4 @@ jobs:
# with: # with:
# name: ui-test-fails-report-linux # name: ui-test-fails-report-linux
# path: | # path: |
# ui-test-example/build/reports # ui-test-example/build/reports

View File

@@ -18,15 +18,19 @@ jobs:
with: with:
python-version: '3.10' python-version: '3.10'
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg uses: FedericoCarboni/setup-ffmpeg@v3
# - name: Setup Gradle with:
# uses: gradle/gradle-build-action@v2.4.2 # Not strictly necessary, but it may prevent rate limit
# errors especially on GitHub-hosted macos machines.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache :runIdeForUiTests -PideaType=PC > build/reports/idea.log & gradle :runIdeForUiTests -PideaType=PC > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -40,7 +44,7 @@ jobs:
run: mv tests/ui-py-tests/video build/reports run: mv tests/ui-py-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/PC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -48,5 +52,4 @@ jobs:
name: ui-test-fails-report-mac name: ui-test-fails-report-mac
path: | path: |
build/reports build/reports
tests/ui-py-tests/build/reports sandbox-idea-log
idea-sandbox-log

View File

@@ -15,15 +15,19 @@ jobs:
distribution: zulu distribution: zulu
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg uses: FedericoCarboni/setup-ffmpeg@v3
# - name: Setup Gradle with:
# uses: gradle/gradle-build-action@v2.4.2 # Not strictly necessary, but it may prevent rate limit
# errors especially on GitHub-hosted macos machines.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache runIdeForUiTests > build/reports/idea.log & gradle runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -37,7 +41,7 @@ jobs:
run: mv tests/ui-ij-tests/video build/reports run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -45,8 +49,7 @@ jobs:
name: ui-test-fails-report-mac name: ui-test-fails-report-mac
path: | path: |
build/reports build/reports
tests/ui-ij-tests/build/reports sandbox-idea-log
idea-sandbox-log
# build-for-ui-test-linux: # build-for-ui-test-linux:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
@@ -78,4 +81,4 @@ jobs:
# with: # with:
# name: ui-test-fails-report-linux # name: ui-test-fails-report-linux
# path: | # path: |
# ui-test-example/build/reports # ui-test-example/build/reports

View File

@@ -0,0 +1,34 @@
# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
# This workflow syncs changes from the docs folder of IdeaVim to the IdeaVim.wiki repository
name: Update Affected Rate field on YouTrack
on:
workflow_dispatch:
schedule:
- cron: '0 8 * * *'
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- name: Fetch origin repo
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Update YouTrack
run: ./gradlew scripts:updateAffectedRates
env:
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}

View File

@@ -25,10 +25,10 @@ jobs:
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }} ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
- name: Get tags - name: Get tags
run: git fetch --tags origin run: git fetch --tags origin
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file
@@ -40,7 +40,7 @@ jobs:
- name: Update authors - name: Update authors
id: update_authors id: update_authors
run: ./gradlew --no-configuration-cache updateAuthors --stacktrace run: ./gradlew updateAuthors --stacktrace
env: env:
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }} SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -7,12 +7,15 @@ on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: '0 10 * * *' - cron: '0 10 * * *'
# Workflow run on push is disabled to avoid conflicts when merging PR
# push:
# branches: [ master ]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: false if: github.repository == 'JetBrains/ideavim'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -22,10 +25,10 @@ jobs:
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }} ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
- name: Get tags - name: Get tags
run: git fetch --tags origin run: git fetch --tags origin
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file
@@ -36,7 +39,7 @@ jobs:
echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-changelog)" >> $GITHUB_ENV echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-changelog)" >> $GITHUB_ENV
- name: Update changelog - name: Update changelog
run: ./gradlew --no-configuration-cache updateChangelog run: ./gradlew updateChangelog
env: env:
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }} SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
@@ -60,4 +63,4 @@ jobs:
# dependabot updates. See mergeDependatobPR.yml file. # dependabot updates. See mergeDependatobPR.yml file.
# However, it turned out that GitHub accepts pushes from the actions as a PR and requires checks, that are always # However, it turned out that GitHub accepts pushes from the actions as a PR and requires checks, that are always
# false for pushing from actions. # false for pushing from actions.
# This secret is created to implement the workaround described in https://stackoverflow.com/a/76135647/3124227 # This secret is created to implement the workaround described in https://stackoverflow.com/a/76135647/3124227

View File

@@ -20,10 +20,10 @@ jobs:
fetch-depth: 50 fetch-depth: 50
# See end of file updateChangeslog.yml for explanation of this secret # See end of file updateChangeslog.yml for explanation of this secret
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }} ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file

10
.gitignore vendored
View File

@@ -1,6 +1,5 @@
*.swp *.swp
/.gradle/ /.gradle/
/.intellijPlatform/
/.idea/ /.idea/
!/.idea/scopes !/.idea/scopes
@@ -11,8 +10,6 @@
!/.idea/runConfigurations !/.idea/runConfigurations
!/.idea/codeStyles !/.idea/codeStyles
!/.idea/vcs.xml !/.idea/vcs.xml
!/.idea/misc.xml
!/.idea/.name
**/build/ **/build/
**/out/ **/out/
@@ -25,10 +22,11 @@
.teamcity/*.iml .teamcity/*.iml
# Generated by gradle task "generateGrammarSource" # Generated by gradle task "generateGrammarSource"
vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
# Generated JSONs for lazy classloading
/vim-engine/src/main/resources/ksp-generated
/src/main/resources/ksp-generated
# Created by github automation # Created by github automation
settings.xml settings.xml
.kotlin

1
.idea/.name generated
View File

@@ -1 +0,0 @@
IdeaVim

22
.idea/misc.xml generated
View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="3">
<item index="0" class="java.lang.String" itemvalue="com.intellij.vim.annotations.CommandOrMotion" />
<item index="1" class="java.lang.String" itemvalue="com.intellij.vim.annotations.ExCommand" />
<item index="2" class="java.lang.String" itemvalue="com.intellij.vim.annotations.VimscriptFunction" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/.teamcity/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK" />
</project>

View File

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

View File

@@ -1,25 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start IJ with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runIdeSplitMode" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

2
.idea/vcs.xml generated
View File

@@ -11,6 +11,6 @@
</option> </option>
</component> </component>
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

View File

@@ -5,13 +5,13 @@ object Constants {
const val EAP_CHANNEL = "eap" const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev" const val DEV_CHANNEL = "Dev"
const val GITHUB_TESTS = "2024.1.1" const val GITHUB_TESTS = "2023.3.2"
const val NVIM_TESTS = "2024.1.1" const val NVIM_TESTS = "2023.3.2"
const val PROPERTY_TESTS = "2024.1.1" const val PROPERTY_TESTS = "2023.3.2"
const val LONG_RUNNING_TESTS = "2024.1.1" const val LONG_RUNNING_TESTS = "2023.3.2"
const val QODANA_TESTS = "2024.1.1" const val QODANA_TESTS = "2023.3.2"
const val RELEASE = "2024.1.1" const val RELEASE = "2023.3.2"
const val RELEASE_DEV = "2024.1.1" const val RELEASE_DEV = "2023.3.2"
const val RELEASE_EAP = "2024.1.1" const val RELEASE_EAP = "2023.3.2"
} }

View File

@@ -11,7 +11,6 @@ import _Self.subprojects.GitHub
import _Self.subprojects.OldTests import _Self.subprojects.OldTests
import _Self.subprojects.Releases import _Self.subprojects.Releases
import _Self.vcsRoots.GitHubPullRequest import _Self.vcsRoots.GitHubPullRequest
import _Self.vcsRoots.ReleasesVcsRoot
import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.Project import jetbrains.buildServer.configs.kotlin.v2019_2.Project
@@ -22,12 +21,10 @@ object Project : Project({
// VCS roots // VCS roots
vcsRoot(GitHubPullRequest) vcsRoot(GitHubPullRequest)
vcsRoot(ReleasesVcsRoot)
// 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.1.1", "<default>")) buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
buildType(TestingBuildType("2024.2", "<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)

View File

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

View File

@@ -1,72 +0,0 @@
/*
* 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

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

View File

@@ -1,42 +0,0 @@
/*
* 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

@@ -26,7 +26,7 @@ object PublishVimEngine : IdeaVimBuildType({
vcs { vcs {
root(DslContext.settingsRoot) root(DslContext.settingsRoot)
branchFilter = "+:fleet" branchFilter = "+:<default>"
checkoutMode = CheckoutMode.AUTO checkoutMode = CheckoutMode.AUTO
} }

View File

@@ -46,8 +46,8 @@ object Qodana : IdeaVimBuildType({
version = Qodana.JVMVersion.LATEST version = Qodana.JVMVersion.LATEST
} }
reportAsTests = true reportAsTests = true
additionalDockerArguments = "-e QODANA_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJvcmdhbml6YXRpb24iOiIzUFZrQSIsInByb2plY3QiOiIzN1FlQSIsInRva2VuIjoiM0t2bXoifQ.uohp81tM7iAfvvB6k8faarfpV-OjusAaEbWQ8iNrOgs"
additionalQodanaArguments = "--baseline qodana.sarif.json" additionalQodanaArguments = "--baseline qodana.sarif.json"
cloudToken = "credentialsJSON:6b79412e-9198-4862-9223-c5019488f903"
} }
} }
@@ -63,6 +63,7 @@ object Qodana : IdeaVimBuildType({
timezone = "SERVER" timezone = "SERVER"
} }
param("dayOfWeek", "Sunday") param("dayOfWeek", "Sunday")
enabled = false
} }
} }

View File

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

@@ -19,6 +19,8 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.BuildFailureOnMetric
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.failOnMetricChange
object ReleaseMajor : ReleasePlugin("major") object ReleaseMajor : ReleasePlugin("major")
object ReleaseMinor : ReleasePlugin("minor") object ReleaseMinor : ReleasePlugin("minor")
@@ -95,14 +97,14 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Set TeamCity build number" name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber" tasks = "scripts:setTeamCityBuildNumber"
} }
// gradle { gradle {
// name = "Update change log" name = "Update change log"
// tasks = "scripts:changelogUpdateUnreleased" tasks = "scripts:changelogUpdateUnreleased"
// } }
// gradle { gradle {
// name = "Commit preparation changes" name = "Commit preparation changes"
// tasks = "scripts:commitChanges" tasks = "scripts:commitChanges"
// } }
gradle { gradle {
name = "Add release tag" name = "Add release tag"
tasks = "scripts:addReleaseTag" tasks = "scripts:addReleaseTag"
@@ -115,24 +117,33 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Publish release" name = "Publish release"
tasks = "publishPlugin" tasks = "publishPlugin"
} }
// script { script {
// name = "Checkout master branch" name = "Checkout master branch"
// scriptContent = """ scriptContent = """
// echo Checkout master echo Checkout master
// git checkout master git checkout master
// """.trimIndent() """.trimIndent()
// } }
// gradle { gradle {
// name = "Update change log in master" name = "Update change log in master"
// tasks = "scripts:changelogUpdateUnreleased" tasks = "scripts:changelogUpdateUnreleased"
// } }
// gradle { gradle {
// name = "Commit preparation changes in master" name = "Commit preparation changes in master"
// tasks = "scripts:commitChanges" tasks = "scripts:commitChanges"
// } }
script { script {
name = "Push changes to the repo" name = "Push changes to the repo"
scriptContent = """ scriptContent = """
branch=$(git branch --show-current)
echo Current branch is ${'$'}branch
if [ "master" != "${'$'}branch" ];
then
git checkout master
fi
git push origin
git checkout release git checkout release
echo checkout release branch echo checkout release branch
git branch --set-upstream-to=origin/release release git branch --set-upstream-to=origin/release release
@@ -144,12 +155,11 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
gradle { gradle {
name = "Run Integrations" name = "Run Integrations"
tasks = "releaseActions" tasks = "releaseActions"
gradleParams = "--no-configuration-cache"
} }
// gradle { gradle {
// name = "Slack Notification" name = "Slack Notification"
// tasks = "slackNotification" tasks = "slackNotification"
// } }
} }
features { features {
@@ -157,4 +167,16 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
teamcitySshKey = "IdeaVim ssh keys" teamcitySshKey = "IdeaVim ssh keys"
} }
} }
failureConditions {
failOnMetricChange {
metric = BuildFailureOnMetric.MetricType.ARTIFACT_SIZE
threshold = 5
units = BuildFailureOnMetric.MetricUnit.PERCENTS
comparison = BuildFailureOnMetric.MetricComparison.DIFF
compareTo = build {
buildRule = lastSuccessful()
}
}
}
}) })

View File

@@ -16,10 +16,10 @@ object GitHub : Project({
name = "Pull Requests checks" name = "Pull Requests checks"
description = "Automatic checking of GitHub Pull Requests" description = "Automatic checking of GitHub Pull Requests"
buildType(GithubBuildType("clean test", "Tests")) buildType(Github("clean test", "Tests"))
}) })
class GithubBuildType(command: String, desc: String) : IdeaVimBuildType({ class Github(command: String, desc: String) : IdeaVimBuildType({
name = "GitHub Pull Requests $desc" name = "GitHub Pull Requests $desc"
description = "Test GitHub pull requests $desc" description = "Test GitHub pull requests $desc"

View File

@@ -1,11 +1,8 @@
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
@@ -41,8 +38,4 @@ object Releases : Project({
buildType(ReleaseEap) buildType(ReleaseEap)
buildType(ReleaseDev) buildType(ReleaseDev)
buildType(PublishVimEngine) buildType(PublishVimEngine)
buildType(CreateNewReleaseBranchFromMaster)
buildType(PrintReleaseBranch)
buildType(ReleaseEapFromBranch)
}) })

View File

@@ -1,13 +0,0 @@
package _Self.vcsRoots
import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot
object ReleasesVcsRoot : GitVcsRoot({
name = "IdeaVim Releases"
url = "git@github.com:JetBrains/ideavim.git"
branch = "refs/heads/master"
branchSpec = "+:refs/(*)"
authMethod = uploadedKey {
uploadedKey = "IdeaVim ssh keys"
}
})

View File

@@ -1,9 +1,11 @@
package patches.buildTypes package patches.buildTypes
import jetbrains.buildServer.configs.kotlin.v2019_2.* import jetbrains.buildServer.configs.kotlin.v2019_2.RelativeId
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.GradleBuildStep import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.GradleBuildStep
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.* import jetbrains.buildServer.configs.kotlin.v2019_2.ui.changeBuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.expectSteps
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.update
/* /*
This patch script was generated by TeamCity on settings change in UI. This patch script was generated by TeamCity on settings change in UI.
@@ -11,18 +13,6 @@ To apply the patch, change the buildType with id = 'IdeaVimTests_Latest_EAP'
accordingly, and delete the patch script. accordingly, and delete the patch script.
*/ */
changeBuildType(RelativeId("IdeaVimTests_Latest_EAP")) { changeBuildType(RelativeId("IdeaVimTests_Latest_EAP")) {
check(artifactRules == """
+:build/reports => build/reports
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
""".trimIndent()) {
"Unexpected option value: artifactRules = $artifactRules"
}
artifactRules = """
+:build/reports => build/reports
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
+:tests/java-tests/build/reports => tests/java-tests/build/reports
""".trimIndent()
expectSteps { expectSteps {
gradle { gradle {
tasks = "clean test" tasks = "clean test"

View File

@@ -5,15 +5,15 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
/* /*
This patch script was generated by TeamCity on settings change in UI. This patch script was generated by TeamCity on settings change in UI.
To apply the patch, change the buildType with id = 'ReleaseEapFromBranch' To apply the patch, change the buildType with id = 'PublishVimEngine'
accordingly, and delete the patch script. accordingly, and delete the patch script.
*/ */
changeBuildType(RelativeId("ReleaseEapFromBranch")) { changeBuildType(RelativeId("PublishVimEngine")) {
vcs { vcs {
check(branchFilter == "+:heads/(releases/*)") { check(branchFilter == "+:<default>") {
"Unexpected option value: branchFilter = $branchFilter" "Unexpected option value: branchFilter = $branchFilter"
} }
branchFilter = "heads/releases/*" branchFilter = "+:fleet"
} }
} }

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.03" version = "2023.11"
project(_Self.Project) project(_Self.Project)

View File

@@ -495,50 +495,6 @@ Contributors:
[![icon][github]](https://github.com/emanuelgestosa) [![icon][github]](https://github.com/emanuelgestosa)
&nbsp; &nbsp;
Emanuel Gestosa Emanuel Gestosa
* [![icon][mail]](mailto:81118900+lippfi@users.noreply.github.com)
[![icon][github]](https://github.com/lippfi)
&nbsp;
lippfi,
* [![icon][mail]](mailto:fillipser143@gmail.com)
[![icon][github]](https://github.com/Parker7123)
&nbsp;
FilipParker
* [![icon][mail]](mailto:7138209+duhaesbaert@users.noreply.github.com)
[![icon][github]](https://github.com/duhaesbaert)
&nbsp;
Eduardo Haesbaert
* [![icon][mail]](mailto:nikolaevsky.egor@gmail.com)
[![icon][github]](https://github.com/Aisper)
&nbsp;
Egor Nikolaevsky
* [![icon][mail]](mailto:77796630+throwaway69420-69420@users.noreply.github.com)
[![icon][github]](https://github.com/kun-codes)
&nbsp;
Bishwa Saha,
* [![icon][mail]](mailto:alexfu@fastmail.com)
[![icon][github]](https://github.com/alexfu)
&nbsp;
Alex Fu
* [![icon][mail]](mailto:jakepeters199@hotmail.com)
[![icon][github]](https://github.com/LazyScaper)
&nbsp;
Jake
* [![icon][mail]](mailto:the1xdeveloper@gmail.com)
[![icon][github]](https://github.com/The1xDeveloper)
&nbsp;
The1xDeveloper
* [![icon][mail]](mailto:shaunewilliams@gmail.com)
[![icon][github]](https://github.com/shaunlebron)
&nbsp;
shaun
* [![icon][mail]](mailto:i.i.babko@gmail.com)
[![icon][github]](https://github.com/igorbabko)
&nbsp;
Igor Babko
* [![icon][mail]](mailto:533601+felixwiemuth@users.noreply.github.com)
[![icon][github]](https://github.com/felixwiemuth)
&nbsp;
Felix Wiemuth
Previous contributors: Previous contributors:

View File

@@ -23,19 +23,15 @@ It is important to distinguish EAP from traditional pre-release software.
Please note that the quality of EAP versions may at times be way below even Please note that the quality of EAP versions may at times be way below even
usual beta standards. usual beta standards.
## End of changelog file maintenance ## To Be Released
Since version 2.9.0, the changelog can be found on YouTrack
* [To Be Released](https://youtrack.jetbrains.com/issues/VIM?q=%23%7BReady%20To%20Release%7D%20)
* [Version Fixes](https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20sort%20by:%20%7BFix%20versions%7D%20asc)
## 2.9.0, 2024-02-20
### Fixes: ### Fixes:
* [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot * [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot
* [VIM-3291](https://youtrack.jetbrains.com/issue/VIM-3291) Remove sync of editor selection between different opened editors
* [VIM-3234](https://youtrack.jetbrains.com/issue/VIM-3234) The space character won't mix in the tab chars after >> and << commands
### Merged PRs: ### Merged PRs:
* [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex
* [805](https://github.com/JetBrains/ideavim/pull/805) by [chylex](https://github.com/chylex): VIM-3238 Fix recording a macro that replays another macro * [805](https://github.com/JetBrains/ideavim/pull/805) by [chylex](https://github.com/chylex): VIM-3238 Fix recording a macro that replays another macro
## 2.8.0, 2024-01-30 ## 2.8.0, 2024-01-30

View File

@@ -62,16 +62,12 @@ for a few days or send it to a friend for testing.
If you are looking for: If you are looking for:
- Vim commands (`w`, `<C-O>`, `p`, etc.): - Vim commands (`w`, `<C-O>`, `p`, etc.):
- Any particular command: - Any particular command: `package-info.java`.
- [Commands common for Fleet and IdeaVim](vim-engine/src/main/resources/ksp-generated/engine_commands.json)
- [IdeaVim only commands](src/main/resources/ksp-generated/intellij_commands.json)
- How commands are executed in common: `EditorActionHandlerBase`. - How commands are executed in common: `EditorActionHandlerBase`.
- Key mapping: `KeyHandler.handleKey()`. - Key mapping: `KeyHandler.handleKey()`.
- Ex commands (`:set`, `:s`, `:nohlsearch`): - Ex commands (`:set`, `:s`, `:nohlsearch`):
- Any particular command: - Any particular ex command: package `com.maddyhome.idea.vim.ex.handler`.
- [Commands common for Fleet and IdeaVim](vim-engine/src/main/resources/ksp-generated/engine_ex_commands.json)
- [IdeaVim only commands](src/main/resources/ksp-generated/intellij_ex_commands.json)
- Vim script grammar: `Vimscript.g4`. - Vim script grammar: `Vimscript.g4`.
- Vim script parsing: package `com.maddyhome.idea.vim.vimscript.parser`. - Vim script parsing: package `com.maddyhome.idea.vim.vimscript.parser`.
- Vim script executor: `Executor`. - Vim script executor: `Executor`.

View File

@@ -109,6 +109,7 @@ etc
See also: See also:
* [The list of all supported commands](src/main/java/com/maddyhome/idea/vim/package-info.java)
* [Top feature requests and bugs](https://youtrack.jetbrains.com/issues/VIM?q=%23Unresolved+sort+by%3A+votes) * [Top feature requests and bugs](https://youtrack.jetbrains.com/issues/VIM?q=%23Unresolved+sort+by%3A+votes)
* [Vimscript support roadmap](vimscript-info/VIMSCRIPT_ROADMAP.md) * [Vimscript support roadmap](vimscript-info/VIMSCRIPT_ROADMAP.md)
* [List of supported in-build functions](vimscript-info/FUNCTIONS_INFO.MD) * [List of supported in-build functions](vimscript-info/FUNCTIONS_INFO.MD)
@@ -221,13 +222,13 @@ Ex commands or via `:map` command mappings:
* Execute an action by `{action_id}`. Works from Ex command line. * Execute an action by `{action_id}`. Works from Ex command line.
* Please don't use `:action` in mappings. Use `<Action>` instead. * Please don't use `:action` in mappings. Use `<Action>` instead.
### Finding action IDs: ### Finding action ids:
* IJ provides `IdeaVim: track action IDs` command to show the id of the executed actions. * IJ provides `IdeaVim: track action Ids` command to show the id of the executed actions.
This command can be found in "Search everywhere" (double `shift`). This command can be found in "Search everywhere" (double `shift`).
<details> <details>
<summary><strong>"Track action IDs" Details</strong> (click to see)</summary> <summary><strong>"Track action Ids" Details</strong> (click to see)</summary>
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif"> <source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif">
<img src="assets/readme/track_action_light.gif" alt="track action ids"/> <img src="assets/readme/track_action_light.gif" alt="track action ids"/>
@@ -368,8 +369,6 @@ is the full list of synonyms.
- Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27): - Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27):
> The local value is set to -123456 when the global value is to be used. > The local value is set to -123456 when the global value is to be used.
- Vi (not Vim) is a POSIX standard, and [has a spec](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html)! Vim is mostly POSIX compliant when Vi compatibility is selected with the `'compatible'` option, but there are still some differences that can be changed with `'copoptions'`. The spec is interesting because it documents the behaviour of different commands in a stricter style than the user documentation, describing the current line and column after the command, for example. [More details can be found by reading `:help posix`](https://vimhelp.org/vi_diff.txt.html#posix).
License License
------- -------

View File

@@ -1,5 +1,6 @@
IdeaVim project is licensed under MIT license except the following parts of it: IdeaVim project is licensed under MIT license except the following parts of it:
* File [RegExp.kt](src/main/java/com/maddyhome/idea/vim/regexp/RegExp.kt) is licensed under Vim License.
* File [ScrollViewHelper.kt](com/maddyhome/idea/vim/helper/ScrollViewHelper.kt) is licensed under Vim License. * File [ScrollViewHelper.kt](com/maddyhome/idea/vim/helper/ScrollViewHelper.kt) is licensed under Vim License.
* File [Tutor.kt](src/main/java/com/maddyhome/idea/vim/ui/Tutor.kt) is licensed under Vim License. * File [Tutor.kt](src/main/java/com/maddyhome/idea/vim/ui/Tutor.kt) is licensed under Vim License.

View File

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

@@ -37,8 +37,7 @@ class CommandOrMotionProcessor(private val environment: SymbolProcessorEnvironme
Files.createDirectories(generatedDirPath) Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["commands_file"]!!) val filePath = generatedDirPath.resolve(environment.options["commands_file"]!!)
val sortedCommands = commands.sortedWith(compareBy({ it.keys }, { it.`class` })) val fileContent = json.encodeToString(commands)
val fileContent = json.encodeToString(sortedCommands)
filePath.writeText(fileContent) filePath.writeText(fileContent)
return emptyList() return emptyList()

View File

@@ -37,8 +37,7 @@ class ExCommandProcessor(private val environment: SymbolProcessorEnvironment): S
Files.createDirectories(generatedDirPath) Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["ex_commands_file"]!!) val filePath = generatedDirPath.resolve(environment.options["ex_commands_file"]!!)
val sortedCommandToClass = commandToClass.toList().sortedWith(compareBy({ it.first }, { it.second })).toMap() val fileContent = json.encodeToString(commandToClass)
val fileContent = json.encodeToString(sortedCommandToClass)
filePath.writeText(fileContent) filePath.writeText(fileContent)
return emptyList() return emptyList()

View File

@@ -37,8 +37,7 @@ class VimscriptFunctionProcessor(private val environment: SymbolProcessorEnviron
Files.createDirectories(generatedDirPath) Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["vimscript_functions_file"]!!) val filePath = generatedDirPath.resolve(environment.options["vimscript_functions_file"]!!)
val sortedNameToClass = nameToClass.toList().sortedWith(compareBy({ it.first }, { it.second })).toMap() val fileContent = json.encodeToString(nameToClass)
val fileContent = json.encodeToString(sortedNameToClass)
filePath.writeText(fileContent) filePath.writeText(fileContent)
return emptyList() return emptyList()

View File

@@ -13,7 +13,7 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.intellij.vim.processors.VimscriptFunctionProcessor import com.intellij.vim.processors.VimscriptFunctionProcessor
class VimscriptFunctionProcessorProvider : SymbolProcessorProvider { public class VimscriptFunctionProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return VimscriptFunctionProcessor(environment) return VimscriptFunctionProcessor(environment)
} }

View File

@@ -32,8 +32,7 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType import org.jetbrains.changelog.exceptions.MissingVersionException
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
import org.kohsuke.github.GHUser import org.kohsuke.github.GHUser
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@@ -45,19 +44,19 @@ buildscript {
} }
dependencies { dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2") classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2")
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.0.202406032230-r") classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.12") classpath("io.ktor:ktor-client-core:2.3.7")
classpath("io.ktor:ktor-client-cio:2.3.10") classpath("io.ktor:ktor-client-cio:2.3.7")
classpath("io.ktor:ktor-client-auth:2.3.12") classpath("io.ktor:ktor-client-auth:2.3.7")
classpath("io.ktor:ktor-client-content-negotiation:2.3.10") classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.12") classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
// This comes from the changelog plugin // This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1") // classpath("org.jetbrains:markdown:0.3.1")
@@ -65,27 +64,45 @@ buildscript {
} }
plugins { plugins {
antlr
java java
kotlin("jvm") version "2.0.0" kotlin("jvm") version "1.9.22"
application application
id("java-test-fixtures") id("java-test-fixtures")
id("org.jetbrains.intellij.platform") version "2.0.0-rc2"
id("org.jetbrains.changelog") version "2.2.1" id("org.jetbrains.intellij") version "1.17.1"
id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.6.1" id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "4.0.0" id("com.dorongold.task-tree") version "2.1.1"
id("com.google.devtools.ksp") version "2.0.0-1.0.23"
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
} }
val moduleSources by configurations.registering ksp {
arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
arg("vimscript_functions_file", "intellij_vimscript_functions.json")
arg("ex_commands_file", "intellij_ex_commands.json")
arg("commands_file", "intellij_commands.json")
}
afterEvaluate {
// tasks.named("kspKotlin").configure { dependsOn("clean") }
tasks.named("kspKotlin").configure { dependsOn("generateGrammarSource") }
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
tasks.named("kspTestKotlin").configure { enabled = false }
}
// Import variables from gradle.properties file // Import variables from gradle.properties file
val javaVersion: String by project val javaVersion: String by project
val kotlinVersion: String by project val kotlinVersion: String by project
val ideaVersion: String by project val ideaVersion: String by project
val ideaType: String by project val ideaType: String by project
val downloadIdeaSources: String by project
val instrumentPluginCode: String by project val instrumentPluginCode: String by project
val antlrVersion: String by project
val remoteRobotVersion: String by project val remoteRobotVersion: String by project
val splitModeVersion: String by project
val publishChannels: String by project val publishChannels: String by project
val publishToken: String by project val publishToken: String by project
@@ -93,45 +110,25 @@ val publishToken: String by project
val slackUrl: String by project val slackUrl: String by project
val youtrackToken: String by project val youtrackToken: String by project
val releaseType: String? by project
repositories { repositories {
mavenCentral() mavenCentral()
intellijPlatform { maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
defaultRepositories()
}
} }
dependencies { dependencies {
api(project(":vim-engine")) api(project(":vim-engine"))
ksp(project(":annotation-processors")) ksp(project(":annotation-processors"))
compileOnly(project(":annotation-processors")) implementation(project(":annotation-processors"))
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.1.0") compileOnly("org.jetbrains:annotations:24.1.0")
runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
intellijPlatform { antlr("org.antlr:antlr4:$antlrVersion")
// Note that it is also possible to use local("...") to compile against a locally installed IDE
// E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
// Or something like: intellijIdeaUltimate(ideaVersion)
create(ideaType, ideaVersion)
pluginVerifier()
zipSigner()
instrumentationTools()
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
// AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "242.20224.159")
}
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
// --------- Test dependencies ---------- // --------- Test dependencies ----------
testImplementation(testFixtures(project(":")))
testApi("com.squareup.okhttp3:okhttp:4.12.0") testApi("com.squareup.okhttp3:okhttp:4.12.0")
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
@@ -145,20 +142,14 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
} }
configurations { configurations {
@@ -169,8 +160,6 @@ configurations {
tasks { tasks {
test { test {
useJUnitPlatform()
// Set teamcity env variable locally to run additional tests for leaks. // Set teamcity env variable locally to run additional tests for leaks.
// By default, this test runs on TC only, but this test doesn't take a lot of time, // By default, this test runs on TC only, but this test doesn't take a lot of time,
// so we can turn it on for local development // so we can turn it on for local development
@@ -183,9 +172,6 @@ tasks {
} }
compileJava { compileJava {
// CodeQL can't resolve the 'by project' property, so we need to give it a hint. This is the minimum version we need
// so doesn't have to match exactly
// Hint for the CodeQL autobuilder: sourceCompatibility = 17
sourceCompatibility = javaVersion sourceCompatibility = javaVersion
targetCompatibility = javaVersion targetCompatibility = javaVersion
@@ -198,94 +184,29 @@ tasks {
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library // See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions // For the list of bundled versions
apiVersion = "1.9" apiVersion = "1.9"
freeCompilerArgs = listOf( freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
compileTestKotlin { compileTestKotlin {
enabled = false
kotlinOptions { kotlinOptions {
jvmTarget = javaVersion jvmTarget = javaVersion
apiVersion = "1.9" apiVersion = "1.9"
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
freeCompilerArgs += listOf("-Xskip-prerelease-check", "-Xallow-unstable-dependencies")
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
// Note that this will run the plugin installed in the IDE specified in dependencies. To run in a different IDE, use downloadRobotServerPlugin {
// a custom task (see below) version.set(remoteRobotVersion)
runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
} }
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies runIdeForUiTests {
// Note that the version must be greater than the plugin's target version, for obvious reasons systemProperty("robot-server.port", "8082")
// val runIdeCustom by intellijPlatformTesting.runIde.registering { systemProperty("ide.mac.message.dialogs.as.sheets", "false")
// type = IntelliJPlatformType.Rider systemProperty("jb.privacy.policy.text", "<!--999.999-->")
// version = "2024.1.2" systemProperty("jb.consents.confirmation.enabled", "false")
// } systemProperty("ide.show.tips.on.startup.default.value", "false")
// Uncomment to run the plugin in a locally installed IDE
// val runIdeLocal by intellijPlatformTesting.runIde.registering {
// localPath = file("/Users/{user}/Applications/WebStorm.app")
// }
val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Drobot-server.port=8082",
"-Dide.mac.message.dialogs.as.sheets=false",
"-Djb.privacy.policy.text=<!--999.999-->",
"-Djb.consents.confirmation.enabled=false",
"-Dide.show.tips.on.startup.default.value=false",
"-Doctopus.handler=" + (System.getProperty("octopus.handler") ?: true),
)
}
}
plugins {
robotServerPlugin(remoteRobotVersion)
}
}
val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.FRONTEND
// Frontend split mode support requires 242+
// TODO: Remove this once IdeaVim targets 242, as the task will naturally use the target version to run
version.set(splitModeVersion)
}
// Add plugin open API sources to the plugin ZIP
val sourcesJar by registering(Jar::class) {
dependsOn(moduleSources)
destinationDirectory.set(layout.buildDirectory.dir("libs"))
archiveClassifier.set(DocsType.SOURCES)
from(sourceSets.main.map { it.kotlin })
from(provider {
moduleSources.map {
it.map { jarFile -> zipTree(jarFile) }
}
})
}
buildPlugin {
dependsOn(sourcesJar)
from(sourcesJar) { into("lib/src") }
} }
} }
@@ -296,6 +217,7 @@ java {
} }
kotlin { kotlin {
explicitApi()
jvmToolchain { jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion)) languageVersion.set(JavaLanguageVersion.of(javaVersion))
} }
@@ -310,68 +232,109 @@ gradle.projectsEvaluated {
// --- Intellij plugin // --- Intellij plugin
intellijPlatform { intellij {
pluginConfiguration { version.set(ideaVersion)
name = "IdeaVim" type.set(ideaType)
changeNotes.set( pluginName.set("IdeaVim")
"""
Undo in IdeaVim now works like in Vim<br/>
Caret movement is no longer a separate undo step, and full insert is undoable in one step.<br/>
<a href="https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks">Share Feedback</a>
<br/>
<br/>
<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>
""".trimIndent()
)
ideaVersion { updateSinceUntilBuild.set(false)
// Let the Gradle plugin set the since-build version. It defaults to the version of the IDE we're building against
// specified as two components, `{branch}.{build}` (e.g., "241.15989"). There is no third component specified.
// The until-build version defaults to `{branch}.*`, but we want to support _all_ future versions, so we set it
// with a null provider (the provider is important).
// By letting the Gradle plugin handle this, the Plugin DevKit IntelliJ plugin cannot help us with the "Usage of
// IntelliJ API not available in older IDEs" inspection. However, since our since-build is the version we compile
// against, we can never get an API that's newer - it would be an unresolved symbol.
untilBuild.set(provider { null })
}
}
publishing { downloadSources.set(downloadIdeaSources.toBoolean())
instrumentCode.set(instrumentPluginCode.toBoolean())
intellijRepository.set("https://www.jetbrains.com/intellij-repository")
plugins.set(listOf("AceJump:3.8.11"))
}
tasks {
publishPlugin {
channels.set(publishChannels.split(",")) channels.set(publishChannels.split(","))
token.set(publishToken) token.set(publishToken)
} }
signing { signPlugin {
certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN")) certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN"))
privateKey.set(providers.environmentVariable("PRIVATE_KEY")) privateKey.set(providers.environmentVariable("PRIVATE_KEY"))
password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD")) password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
} }
verifyPlugin { runPluginVerifier {
teamCityOutputFormat = true downloadDir.set("${project.buildDir}/pluginVerifier/ides")
ides { teamCityOutputFormat.set(true)
recommended() // ideVersions.set(listOf("IC-2021.3.4"))
}
} }
instrumentCode.set(instrumentPluginCode.toBoolean()) generateGrammarSource {
maxHeapSize = "128m"
arguments.addAll(listOf("-package", "com.maddyhome.idea.vim.vimscript.parser.generated", "-visitor"))
outputDirectory = file("src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated")
}
named("compileKotlin") {
dependsOn("generateGrammarSource")
}
named("compileTestKotlin") {
dependsOn("generateTestGrammarSource")
}
named("compileTestFixturesKotlin") {
dependsOn("generateTestFixturesGrammarSource")
}
// Add plugin open API sources to the plugin ZIP
val createOpenApiSourceJar by registering(Jar::class) {
dependsOn("generateGrammarSource")
// Java sources
from(sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
from(project(":vim-engine").sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
// Kotlin sources
from(kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
destinationDirectory.set(layout.buildDirectory.dir("libs"))
archiveClassifier.set("src")
}
buildPlugin {
dependsOn(createOpenApiSourceJar)
from(createOpenApiSourceJar) { into("lib/src") }
}
val pluginVersion = version
// Don't forget to update plugin.xml
patchPluginXml {
// Get the latest available change notes from the changelog file
changeNotes.set(
provider {
with(changelog) {
val log = try {
getUnreleased()
} catch (e: MissingVersionException) {
getOrNull(pluginVersion.toString()) ?: getLatest()
}
renderItem(
log,
org.jetbrains.changelog.Changelog.OutputType.HTML,
)
}
},
)
}
} }
ksp { // --- Tests
arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
arg("vimscript_functions_file", "intellij_vimscript_functions.json")
arg("ex_commands_file", "intellij_ex_commands.json")
arg("commands_file", "intellij_commands.json")
}
afterEvaluate { tasks {
// tasks.named("kspKotlin").configure { dependsOn("clean") } test {
tasks.named("kspTestFixturesKotlin").configure { enabled = false } useJUnitPlatform()
tasks.named("kspTestFixturesKotlin").configure { enabled = false } }
tasks.named("kspTestKotlin").configure { enabled = false }
} }
// --- Changelog // --- Changelog
changelog { changelog {
@@ -466,14 +429,12 @@ val prId: String by project
tasks.register("updateMergedPr") { tasks.register("updateMergedPr") {
doLast { doLast {
val x = changelog.getUnreleased() if (project.hasProperty("prId")) {
println("x") println("Got pr id: $prId")
// if (project.hasProperty("prId")) { updateMergedPr(prId.toInt())
// println("Got pr id: $prId") } else {
// updateMergedPr(prId.toInt()) error("Cannot get prId")
// } else { }
// error("Cannot get prId")
// }
} }
} }
@@ -497,8 +458,6 @@ val fixVersionsElementType = "VersionBundleElement"
tasks.register("releaseActions") { tasks.register("releaseActions") {
group = "other" group = "other"
doLast { doLast {
if (releaseType == "patch") return@doLast
val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D%20and%20tag:%20%7BIdeaVim%20Released%20In%20EAP%7D%20") val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D%20and%20tag:%20%7BIdeaVim%20Released%20In%20EAP%7D%20")
if (tickets.isNotEmpty()) { if (tickets.isNotEmpty()) {
println("Updating statuses for tickets: $tickets") println("Updating statuses for tickets: $tickets")
@@ -958,12 +917,12 @@ fun changes(): List<Change> {
println("Start changes processing") println("Start changes processing")
for (message in messages) { for (message in messages) {
println("Processing '$message'...") println("Processing '$message'...")
val lowercaseMessage = message.lowercase() val lowercaseMessage = message.toLowerCase()
val regex = "^fix\\((vim-\\d+)\\):".toRegex() val regex = "^fix\\((vim-\\d+)\\):".toRegex()
val findResult = regex.find(lowercaseMessage) val findResult = regex.find(lowercaseMessage)
if (findResult != null) { if (findResult != null) {
println("Message matches") println("Message matches")
val value = findResult.groups[1]!!.value.uppercase() val value = findResult.groups[1]!!.value.toUpperCase()
val shortMessage = message.drop(findResult.range.last + 1).trim() val shortMessage = message.drop(findResult.range.last + 1).trim()
newFixes += Change(value, shortMessage) newFixes += Change(value, shortMessage)
} else { } else {

View File

@@ -1,6 +1,6 @@
Welcome to the IdeaVim wiki! Welcome to the IdeaVim wiki!
- List of IdeaVim plugins: [plugins](IdeaVim%20Plugins) - List of IdeaVim plugins: [plugins](IdeaVim%20Plugins.md)
- Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples) - Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples.md)
- List of "set" commands: ["set" commands](set-commands) - List of "set" commands: ["set" commands](set-commands.md)
- Docs about "select" mode in vim: [select mode](Select-mode) - Docs about "select" mode in vim: [select mode](Select-mode.md)

View File

@@ -82,7 +82,7 @@ Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
### Instructions ### Instructions
[See here](NERDTree-support). [See here](NERDTree-support.md).
</details> </details>
@@ -129,26 +129,8 @@ Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-
</details> </details>
### Instructions ### Instructions
At the moment, the default key binds for this plugin do not get mapped correctly in IdeaVim (see [VIM-2178](https://youtrack.jetbrains.com/issue/VIM-2178)). To enable the default key binds, add the following to your `.ideavimrc` file... https://github.com/terryma/vim-multiple-cursors/blob/master/doc/multiple_cursors.txt
```
" Remap multiple-cursors shortcuts to match terryma/vim-multiple-cursors
nmap <C-n> <Plug>NextWholeOccurrence
xmap <C-n> <Plug>NextWholeOccurrence
nmap g<C-n> <Plug>NextOccurrence
xmap g<C-n> <Plug>NextOccurrence
xmap <C-x> <Plug>SkipOccurrence
xmap <C-p> <Plug>RemoveOccurrence
" Note that the default <A-n> and g<A-n> shortcuts don't work on Mac due to dead keys.
" <A-n> is used to enter accented text e.g. ñ
" Feel free to pick your own mappings that are not affected. I like to use <leader>
nmap <leader><C-n> <Plug>AllWholeOccurrences
xmap <leader><C-n> <Plug>AllWholeOccurrences
nmap <leader>g<C-n> <Plug>AllOccurrences
xmap <leader>g<C-n> <Plug>AllOccurrences
```
</details> </details>

View File

@@ -27,7 +27,7 @@ Plug 'nerdtree'
### Preview ### Preview
<details> <details>
<summary>Click for the preview</summary> <summary>Click to the the preview</summary>
<img src="images/nerdtree.gif" alt="NERDTree example"/> <img src="images/nerdtree.gif" alt="NERDTree example"/>
</details> </details>
@@ -40,33 +40,30 @@ Plug 'nerdtree'
- `:NERDTreeFind` - `:NERDTreeFind`
- `:NERDTreeRefreshRoot` - `:NERDTreeRefreshRoot`
| Key | Description | Map Setting | | Key | Description | Map Setting |
|---------|--------------------------------------------------------|--------------------------------| |---------|---------------------------------------------------------|--------------------------------|
| `o` | Open files, directories and bookmarks | `g:NERDTreeMapActivateNode` | | `o` | Open files, directories and bookmarks | `g:NERDTreeMapActivateNode` |
| `go` | Open selected file, but leave cursor in the NERDTree | `g:NERDTreeMapPreview` | | `go` | Open selected file, but leave cursor in the NERDTree | `g:NERDTreeMapPreview` |
| `t` | Open selected node/bookmark in a new tab | `g:NERDTreeMapOpenInTab` | | `t` | Open selected node/bookmark in a new tab | `g:NERDTreeMapOpenInTab` |
| `T` | Same as 't' but keep the focus on the current tab | `g:NERDTreeMapOpenInTabSilent` | | `T` | Same as 't' but keep the focus on the current tab | `g:NERDTreeMapOpenInTabSilent` |
| `i` | Open selected file in a split window | `g:NERDTreeMapOpenSplit` | | `i` | Open selected file in a split window | `g:NERDTreeMapOpenSplit` |
| `gi` | Same as i, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewSplit` | | `gi` | Same as i, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewSplit` |
| `s` | Open selected file in a new vsplit | `g:NERDTreeMapOpenVSplit` | | `s` | Open selected file in a new vsplit | `g:NERDTreeMapOpenVSplit` |
| `gs` | Same as s, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewVSplit` | | `gs` | Same as s, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewVSplit` |
| `O` | Recursively open the selected directory | `g:NERDTreeMapOpenRecursively` | | `O` | Recursively open the selected directory | `g:NERDTreeMapOpenRecursively` |
| `x` | Close the current nodes parent | `g:NERDTreeMapCloseDir` | | `x` | Close the current nodes parent | `g:NERDTreeMapCloseDir` |
| `X` | Recursively close all children of the current node | `g:NERDTreeMapCloseChildren` | | `X` | Recursively close all children of the current node | `g:NERDTreeMapCloseChildren` |
| `P` | Jump to the root node | `g:NERDTreeMapJumpRoot` | | `P` | Jump to the root node | `g:NERDTreeMapJumpRoot` |
| `p` | Jump to current nodes parent | `g:NERDTreeMapJumpParent` | | `p` | Jump to current nodes parent | `g:NERDTreeMapJumpParent` |
| `K` | Jump up inside directories at the current tree depth | `g:NERDTreeMapJumpFirstChild` | | `K` | Jump up inside directories at the current tree depth | `g:NERDTreeMapJumpFirstChild` |
| `J` | Jump down inside directories at the current tree depth | `g:NERDTreeMapJumpLastChild` | | `J` | Jump down inside directories at the current tree depth | `g:NERDTreeMapJumpLastChild` |
| `<C-J>` | Jump down to next sibling of the current directory | `g:NERDTreeMapJumpNextSibling` | | `<C-J>` | Jump down to next sibling of the current directory | `g:NERDTreeMapJumpNextSibling` |
| `<C-K>` | Jump up to previous sibling of the current directory | `g:NERDTreeMapJumpPrevSibling` | | `<C-K>` | Jump up to previous sibling of the current directory | `g:NERDTreeMapJumpPrevSibling` |
| `r` | Recursively refresh the current directory | `g:NERDTreeMapRefresh` | | `r` | Recursively refresh the current directory | `g:NERDTreeMapRefresh` |
| `R` | Recursively refresh the current root | `g:NERDTreeMapRefreshRoot` | | `R` | Recursively refresh the current root | `g:NERDTreeMapRefreshRoot` |
| `m` | Display the NERDTree menu | `g:NERDTreeMapMenu` | | `m` | Display the NERDTree menu | `g:NERDTreeMapMenu` |
| `q` | Close the NERDTree window | `g:NERDTreeMapQuit` | | `q` | Close the NERDTree window | `g:NERDTreeMapQuit` |
| `A` | Zoom (maximize/minimize) the NERDTree window | `g:NERDTreeMapToggleZoom` | | `A` | Zoom (maximize/minimize) the NERDTree window | `g:NERDTreeMapToggleZoom` |
| `d` | Delete file or directory | `g:NERDTreeMapDelete` |
| `n` | Create File | `g:NERDTreeMapNewFile` |
| `N` | Create Directory | `g:NERDTreeMapNewDir` |
### Troubleshooting ### Troubleshooting

View File

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

View File

@@ -1,268 +1,140 @@
List of Supported Options List of Supported Set Commands
========================= ==============================
The following options can be set with the `:set`, `:setglobal` and `:setlocal` commands. The following `:set` commands can appear in `~/.ideavimrc` or be set manually in the command mode:
They can be added to the `~/.ideavimrc` file, or set manually in Command-line mode.
For more details of each option, please see the Vim documentation.
Every effort is made to make these options compatible with Vim behaviour.
However, some differences are inevitable.
``` 'clipboard' 'cb' clipboard options
'clipboard' 'cb' Defines clipboard behavior Standard clipboard options plus
A comma-separated list of words to control clipboard behaviour:
unnamed The clipboard register '*' is used instead of the
unnamed register
unnamedplus The clipboard register '+' is used instead of the
unnamed register
ideaput Uses the IDEs own paste implementation for put
operations rather than simply inserting the text
'digraph' 'dg' Enable using <BS> to enter digraphs in Insert mode
'gdefault' 'gd' The ":substitute" flag 'g' is by default
'guicursor' 'gcr' Controls the shape of the cursor for different modes
'history' 'hi' Number of command-lines that are remembered
'hlsearch' 'hls' Highlight matches with the last search pattern
'ignorecase' 'ic' Ignore case in search patterns
'incsearch' 'is' Show where search pattern typed so far matches
'iskeyword' 'isk' Defines keywords for commands like 'w', '*', etc.
'keymodel' 'km' Controls selection behaviour with special keys
List of comma separated words, which enable special things that keys
can do. These values can be used:
startsel Using a shifted special key starts selection (either
Select mode or Visual mode, depending on "key" being
present in 'selectmode')
stopsel Using a NOT-shifted special key stops selection.
Automatically enables `stopselect` and `stopvisual`
stopselect Using a NOT-shifted special key stops select mode
and removes selection - IdeaVim ONLY
stopvisual Using a NOT-shifted special key stops visual mode
and removes selection - IdeaVim ONLY
continueselect Using a shifted arrow key doesn't start selection,
but in select mode acts like startsel is enabled
- IdeaVim ONLY
continuevisual Using a shifted arrow key doesn't start selection,
but in visual mode acts like startsel is enabled
- IdeaVim ONLY
Special keys in this context are the cursor keys, <End>, <Home>, `ideaput` (default on) - IdeaVim ONLY
<PageUp> and <PageDown>. enable native idea paste action for put operations
'digraph' 'dg' enable the entering of digraphs in Insert mode
'gdefault' 'gd' the ":substitute" flag 'g' is by default
'history' 'hi' number of command-lines that are remembered
'hlsearch' 'hls' highlight matches with the last search pattern
'ignorecase' 'ic' ignore case in search patterns
'iskeyword' 'isk' defines keywords for commands like 'w', '*', etc.
'incsearch' 'is' show where search pattern typed so far matches
`keymodel` `km` String (default "continueselect,stopselect")
'matchpairs' 'mps' Pairs of characters that "%" can match List of comma separated words, which enable special things that keys
'maxmapdepth' 'mmd' Maximum depth of mappings can do. These values can be used:
'more' 'more' When on, listings pause when the whole screen is filled startsel Using a shifted special[1] key starts selection (either
'nrformats' 'nf' Number formats recognized for CTRL-A command Select mode or Visual mode, depending on "key" being
'operatorfunc' 'opfunc' Name of a function to call with the g@ operator present in 'selectmode').
'scroll' 'scr' Number of lines to scroll with CTRL-U and CTRL-D stopsel Using a NOT-shifted special[1] key stops selection.
'selection' 'sel' What type of selection to use Automatically enables `stopselect` and `stopvisual`
'selectmode' 'slm' Controls when to start Select mode instead of Visual stopselect Using a NOT-shifted special[1] key stops - IdeaVim ONLY
This is a comma-separated list of words: select mode and removes selection.
stopvisual Using a NOT-shifted special[1] key stops - IdeaVim ONLY
mouse When using the mouse visual mode and removes selection.
key When using shifted special[1] keys continueselect Using a shifted arrow key doesn't - IdeaVim ONLY
cmd When using "v", "V", or <C-V> start selection, but in select mode
ideaselection When IDE sets a selection - IdeaVim ONLY acts like startsel is enabled
(e.g.: extend selection, wrap with while, etc.) continuevisual Using a shifted arrow key doesn't - IdeaVim ONLY
start selection, but in visual mode
acts like startsel is enabled
'matchpairs' 'mps' pairs of characters that "%" can match
'maxmapdepth' 'mmd' Maximum depth of mappings
'more' 'more' When on, listings pause when the whole screen is filled.
'nrformats' 'nf' number formats recognized for CTRL-A command
'number' 'nu' print the line number in front of each line
'relativenumber' 'rnu' show the line number relative to the line with
the cursor
'scroll' 'scr' lines to scroll with CTRL-U and CTRL-D
'scrolljump' 'sj' minimum number of lines to scroll
'scrolloff' 'so' minimum number of lines above and below the cursor
'selection' 'sel' what type of selection to use
'shell' 'sh' The shell to use to execute commands with ! and :! `selectmode` `slm` String (default "")
'shellcmdflag' 'shcf' The command flag passed to the shell
'shellxescape' 'sxe' The characters to be escaped when calling a shell
'shellxquote' 'sxq' The quote character to use in a shell command
'showcmd' 'sc' Show (partial) command in the status bar
'showmode' 'smd' Show the current mode in the status bar
'smartcase' 'scs' Use case sensitive search if any character in the
pattern is uppercase
'startofline' 'sol' When on, some commands move the cursor to the first
non-blank of the line
When off, the cursor is kept in the same column
(if possible)
'timeout' 'to' Use timeout for mapped key sequences
'timeoutlen' 'tm' Timeout duration for a mapped key sequence
'viminfo' 'vi' Information to remember after restart
'virtualedit' 've' Placement of the cursor where there is no actual text
A comma-separated list of these words:
block Allow virtual editing in Visual mode (not supported)
insert Allow virtual editing in Insert mode (not supported)
all Allow virtual editing in all modes (not supported)
onemore Allow the cursor to move just past the end of the line
'visualbell' 'vb' When on, prevents beeping on error This is a comma-separated list of words, which specify when to start
'whichwrap' 'ww' Which keys that move the cursor left/right can wrap to Select mode instead of Visual mode, when a selection is started.
other lines Possible values:
A comma-separated list of these flags: mouse when using the mouse
char key modes key when using shifted special[1] keys
b <BS> Normal and Visual cmd when using "v", "V", or <C-V>
s <Space> Normal and Visual ideaselection when IDE sets a selection - IdeaVim ONLY
h "h" Normal and Visual (examples: extend selection, wrap with while, etc.)
l "l" Normal and Visual
< <Left> Normal and Visual
> <Right> Normal and Visual
~ "~" Normal
[ <Left> Insert and Replace
] <Right> Insert and Replace
'wrapscan' 'ws' Search will wrap around the end of file `startofline` `sol` When "on" some commands move the cursor to the first non-blank of the line.
``` When off the cursor is kept in the same column (if possible).
## IdeaVim options mapped to IntelliJ-based IDE settings 'showmode' 'smd' message on the status line to show current mode
'showcmd' 'sc' show (partial) command in the status bar
'sidescroll' 'ss' minimum number of columns to scroll horizontally
'sidescrolloff' 'siso' min. number of columns to left and right of cursor
'smartcase' 'scs' no ignore case when pattern is uppercase
'timeout' 'to' use timeout for mapped key sequences
'timeoutlen' 'tm' timeout duration for a mapped key sequence
'undolevels' 'ul' maximum number of changes that can be undone
'viminfo' 'vi' information to remember after restart
'visualbell' 'vb' use visual bell instead of beeping
'wrapscan' 'ws' searches wrap around the end of file
IdeaVim only commands:
IdeaVim provides its own implementation for handling scroll jump and offset, even though IntelliJ-based IDEs have similar functionality (there are differences in behaviour). `ideamarks` `ideamarks` Boolean (default true)
When IdeaVim is hosted in an IntelliJ-based IDE (but not JetBrains Fleet), the following options map to the equivalent IDE settings:
If true, creation of global mark will trigger creation of IDE's bookmark
and vice versa.
`idearefactormode` `idearefactormode` String(default "select")
Define the mode that would be enabled during
the refactoring (renaming, live template, introduce variable, etc)
Use one of the following values:
- keep - keep the mode that was enabled before starting a refactoring
- select - start refactoring in select mode
- visual - start refactoring in visual mode
This option has effect if you are in normal, insert or replace mode before refactoring start.
Visual or select mode are not changed.
`ideajoin` `ideajoin` Boolean (default false)
If true, join command will be performed via IDE
See wiki/`ideajoin` examples
`ideastatusicon` `ideastatusicon` String(default "enabled")
Define the behavior of IdeaVim icon in the status bar.
Use one of the following values:
- enabled - icon is shown in the status bar
- gray - use the gray version of the icon
- disabled - hide the icon
``` `ideawrite` `ideawrite` String (default "all")
'scrolljump' 'sj' Minimal number of lines to scroll "file" or "all". Defines the behaviour of ":w" command.
'scrolloff' 'so' Minimal number of lines above and below the cursor Value "all" enables execution of ":wa" (save all) command on ":w" (save).
'sidescroll' 'ss' Minimal number of columns to scroll horizontally This feature exists because some IJ options like "Prettier on save" or "ESlint on save"
'sidescrolloff' 'siso' Minimal number of columns to left and right of cursor work only with "save all" action. If this option is set to "all", these actions work
``` also with ":w" command.
`lookupkeys` `lookupkeys` List of strings
List of keys that should be processed by the IDE during the active lookup (autocompletion).
For example, <Tab> and <Enter> are used by the IDE to finish the lookup,
but <C-W> should be passed to IdeaVim.
Default value:
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
"<C-Down>", "<C-Up>", "<PageUp>", "<PageDown>",
"<C-J>", "<C-Q>"
`ideavimsupport` `ideavimsupport` List of strings (default "dialog")
Define the list of additional buffers where IdeaVim is enabled.
- dialog - enable IdeaVim in dialogs
- singleline - enable IdeaVim in single line editors (not suggested)
## IdeaVim options for IntelliJ-based IDE features ----------
[1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown>
Some Vim features cannot be implemented by IdeaVim, and must be implemented by the host IDE, such as showing whitespace and line numbers, and enabling soft-wrap.
The following options modify equivalent settings and features implemented by IntelliJ-based IDEs.
There is some mismatch when trying to map Vim options, most of which are local options, to IDE settings, which are mostly global-local.
The Vim option will always reflect the effective value of the IDE setting for the current editor, and modifying the Vim option will update the local value of the IDE setting.
The default value of the Vim option set during startup is not passed to the IDE setting.
If the IDE setting has a way to modify the local value, such as entries in the _View | Active Editor_ menu, then changing this will update the current editor and be reflected in the Vim option value.
If the IDE setting can only modify its global setting in the main _Settings_ dialog, this change does not always update the current editor (because the local IDE setting has been modified and takes precedence).
IdeaVim tries to make this work more naturally by updating the editor and local Vim option when a global value changes unless the Vim option has been explicitly set in Command-line mode.
In other words, if the local Vim value is explicitly set for a window or buffer, interactively, then it should not be reset.
If the Vim option was explicitly set in `~/.ideavimrc` however, then the value will be reset, because this can be viewed as a "global" value - set once and applied to subsequently opened windows.
(This should not be confused with Vim's concept of global options, which are mainly used to initialise new windows.)
The local Vim option can always be reset to the global IDE setting value by resetting the Vim option to default with the `:set {option}&` syntax.
```
'bomb' 'bomb' Add or remove a byte order mark (BOM) to the
current file. Unlike Vim, the file is modified
immediately, and not when saved
'breakindent' 'bri' Indent soft wrapped lines to match the first
line's indent
'colorcolumn' 'cc' Maps to IntelliJ's visual guide columns
'cursorline' 'cul' Highlight the line containing the cursor
'fileencoding' 'fenc' Change the encoding of the current file. The file
is modified and written immediately, rather than
waiting to be saved
Note that the names of the encoding might not
match Vim's known names
'fileformat' 'ff' Change the file format - dos, unix or mac
The file is modified immediately, rather than
when saved
'list' 'list' Show whitespace. Maps to the editor's local
setting in the View | Active Editor menu
'number' 'nu' Show line numbers. Maps to the editor's local
setting in the View | Active Editor menu
'relativenumber' 'rnu' Show line numbers relative to the current line
'textwidth' 'tw' Set the column at which text is automatically
wrapped
'wrap' 'wrap' Enable soft-wraps. Maps to the editor's local
setting in the View | Active Editor menu
```
## IdeaVim only options
These options are IdeaVim only, and not supported by Vim.
They control integration with the host IDE.
Unless otherwise stated, these options do not have abbreviations.
```
'ideacopypreprocess' boolean (default off)
global or local to buffer
When enabled, the IDE will run custom copy pre-processors over text
copied to registers. These pre-processors can perform transformations
on the text, such as converting escape characters in a string literal
into the actual control characters in a Java file.
This is not usually the expected behaviour, so this option's default
value is off. The equivalent processing for paste is controlled by the
"ideaput" value to the 'clipboard' option.
'ideaglobalmode' boolean (default off)
global
This option will cause IdeaVim to share a single mode across all open
windows. In other words, entering Insert mode in one window will
enable Insert mode in all windows.
'ideajoin' boolean (default off)
global or local to buffer
When enabled, join commands will be handled by the IDE's "smart join"
feature. The IDE can change syntax when joining lines, such as merging
string literals or if statements. See the wiki for more examples. Not
all languages support smart join functionality.
'ideamarks' boolean (default on)
global
Maps Vim's global marks to IDE bookmarks.
'idearefactormode' string (default "select")
global or local to buffer
Specifies the mode to be used when a refactoring selects text to be
edited (e.g. renaming, live template fields, introduce variable, etc):
keep Keep the current mode
select Switch to Select mode
visual Switch to Visual mode
This option is only used when the refactoring is started in Normal,
Insert or Replace mode. Visual or Select modes are not changed.
'ideastatusicon' string (default "enabled")
global
This option controls the behaviour and appearance of the IdeaVim icon
in the status bar:
enabled Show the icon in the status bar
gray Show the gray version of the icon
disabled Hide the icon
'ideavimsupport' string (default "dialog")
global
A comma-separated list of additional buffers or locations where
IdeaVim should be enabled:
dialog Enable IdeaVim in editors hosted in dialogs
singleline Enable IdeaVim in single line editors (not recommended)
The IDE's editor component can be used in many places, such as VCS
commit tool window, or inside dialogs, and even as single line fields.
'ideawrite' string (default "all")
global
This option defines the behaviour of the :w command:
file Save the current file only
all The :w command works like :wa and invokes the Save All
IDE action. This allows options such as "Prettier on
save" or "ESlint on save" to work with the :w command,
but means all files are saved.
'lookupkeys' string (default "<Tab>,<Down>,<Up>,<Enter>,
<Left>,<Right>,<C-Down>,<C-Up>,
<PageUp>,<PageDown>, <C-J>,<C-Q>")
global
Comma-separated list of keys that should be processed by the IDE while
a code completion lookup popup is active. For example, <Tab> and
<Enter> are used by the IDE to complete the lookup and insert text,
but <C-W> should be passed IdeaVim to continue editing the text.
'trackactionids' boolean (default off)
global
When on, IdeaVim will try to track the current IDE action and display
the action name in a notification. This action ID can then be used in
a mapping to the action in the form <Action>(...).
'visualdelay' number (default 100)
global
This option specifies the delay, in milliseconds before converting an
IDE selection into Visual mode.
Some IDE features make a selection to help modify text (e.g. backspace
in Python or Yaml selects an indent and invokes the "remove selection"
action). IdeaVim listens for changes in selection to switch to Visual
mode, and will return to Normal mode when the selection is removed,
even if originally in Insert mode.
By waiting before converting to Visual mode, temporary selections can
be ignored and the current Vim mode maintained.
It is not expected that this value will need to be changed.
```

View File

@@ -1,7 +1,7 @@
# Support Guide # Support Guide
This document is created to help our support team. This document is created to help our support team.
It's not intended to be read by the users as it brings no value to them. It's not intended to be read by the users as it brings to value to them.
## Support channels ## Support channels
@@ -35,4 +35,4 @@ IdeaVim has multiple YouTrack statuses, main are:
# ~.ideavimrc file # ~.ideavimrc file
`~/.ideavimrc` is the file that is used for IdeaVim configuration. It may affect behaviour of the program, `~/.ideavimrc` is the file that is used for IdeaVim configuration. It may affect behaviour of the program,
so it makes sense to additionally request this file in case the issues can't be reproduced. so it makes sense to additionally request this file in case the issues can't be reproduced.

View File

@@ -8,46 +8,33 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
# ideaVersion is the version of the IDE that will be added as a compile-time dependency. The format can be either ideaVersion=2023.3.3
# product version (e.g. 2024.1, 2024.1.1) or build (e.g. 241.15989.150, 241-EAP-SNAPSHOT). The dependency will be
# resolved against the configured repositories, which by default includes Maven releases and snapshots, the CDN used to
# download consumer releases, the plugin marketplace and so on.
# You can find an example list of all CDN based versions for IDEA Community here:
# https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
ideaVersion=2024.2
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-41 version=chylex-28
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.23 remoteRobotVersion=0.11.22
antlrVersion=4.10.1 antlrVersion=4.10.1
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2 kotlin.incremental.useClasspathSnapshot=false
# Running IdeaVim in split mode requires 242. Update this version once 242 has been released, and remove it completely
# when IdeaVim targets 242, at which point runIdeSplitMode will run correctly with the target version.
# See also runIdeSplitMode
splitModeVersion=242-EAP-SNAPSHOT
# Please don't forget to update kotlin version in buildscript section # Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version # Also update kotlinxSerializationVersion version
kotlinVersion=2.0.0 kotlinVersion=1.9.22
publishToken=token publishToken=token
publishChannels=eap publishChannels=eap
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However, # Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
# we exclude this version from the dependency and use our own version of kotlin that is specified above # we exclude this version from the dependency and use our own version of kotlin that is specified above
kotlinxSerializationVersion=1.6.2 kotlinxSerializationVersion=1.5.1
slackUrl= slackUrl=
youtrackToken= youtrackToken=
# Gradle settings # Gradle settings
org.gradle.jvmargs='-Dfile.encoding=UTF-8' org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.caching=true
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary # Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary
kotlin.stdlib.default.dependency=false kotlin.stdlib.default.dependency=false

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -20,17 +20,17 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.25") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
implementation("io.ktor:ktor-client-core:2.3.12") implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-cio:2.3.10") implementation("io.ktor:ktor-client-cio:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.10") implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
implementation("io.ktor:ktor-client-auth:2.3.12") implementation("io.ktor:ktor-client-auth:2.3.7")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // This is needed for jgit to connect to ssh
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.0.202406032230-r") implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
implementation("com.vdurmont:semver4j:3.1.0") implementation("com.vdurmont:semver4j:3.1.0")
} }
@@ -58,6 +58,13 @@ tasks.register("checkNewPluginDependencies", JavaExec::class) {
classpath = sourceSets["main"].runtimeClasspath classpath = sourceSets["main"].runtimeClasspath
} }
tasks.register("updateAffectedRates", JavaExec::class) {
group = "verification"
description = "This job updates Affected Rate field on YouTrack"
mainClass.set("scripts.YouTrackUsersAffectedKt")
classpath = sourceSets["main"].runtimeClasspath
}
tasks.register("calculateNewVersion", JavaExec::class) { tasks.register("calculateNewVersion", JavaExec::class) {
group = "release" group = "release"
mainClass.set("scripts.release.CalculateNewVersionKt") mainClass.set("scripts.release.CalculateNewVersionKt")
@@ -107,13 +114,6 @@ 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

@@ -22,7 +22,7 @@ import kotlinx.serialization.json.jsonPrimitive
*/ */
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
val knownPlugins = setOf( val knownPlugins = listOf(
"IdeaVimExtension", "IdeaVimExtension",
"github.zgqq.intellij-enhance", "github.zgqq.intellij-enhance",
"org.jetbrains.IdeaVim-EasyMotion", "org.jetbrains.IdeaVim-EasyMotion",
@@ -31,12 +31,7 @@ val knownPlugins = setOf(
"com.github.copilot", "com.github.copilot",
"com.github.dankinsoid.multicursor", "com.github.dankinsoid.multicursor",
"com.joshestein.ideavim-quickscope", "com.joshestein.ideavim-quickscope",
"ca.alexgirard.HarpoonIJ", "ca.alexgirard.HarpoonIJ",
"me.kyren223.harpoonforjb", // https://plugins.jetbrains.com/plugin/23771-harpoonforjb
"com.github.erotourtes.harpoon", // https://plugins.jetbrains.com/plugin/21796-harpooner
"me.kyren223.trident", // https://plugins.jetbrains.com/plugin/23818-trident
"com.protoseo.input-source-auto-converter", "com.protoseo.input-source-auto-converter",
// "cc.implicated.intellij.plugins.bunny", // I don't want to include this plugin in the list of IdeaVim plugins as I don't understand what this is for // "cc.implicated.intellij.plugins.bunny", // I don't want to include this plugin in the list of IdeaVim plugins as I don't understand what this is for
@@ -47,15 +42,19 @@ suspend fun main() {
parameter("dependency", "IdeaVIM") parameter("dependency", "IdeaVIM")
parameter("includeOptional", true) parameter("includeOptional", true)
} }
val output = response.body<List<String>>().toSet() val output = response.body<List<String>>()
println(output) println(output)
val newPlugins = (output - knownPlugins).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") } if (knownPlugins != output) {
if (newPlugins.isNotEmpty()) { val newPlugins = (output - knownPlugins).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
// val removedPlugins = (knownPlugins - output.toSet()).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") } val removedPlugins = (knownPlugins - output.toSet()).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
error( error(
""" """
Unregistered plugins: Unregistered plugins:
${newPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" }} ${if (newPlugins.isNotEmpty()) newPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" } else "No unregistered plugins"}
Removed plugins:
${if (removedPlugins.isNotEmpty()) removedPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" } else "No removed plugins"}
""".trimIndent() """.trimIndent()
) )
} }

View File

@@ -8,12 +8,6 @@
package scripts.release package scripts.release
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.revwalk.filter.RevFilter
fun main(args: Array<String>) { fun main(args: Array<String>) {
println("HI!") println("HI!")
val projectDir = args[0] val projectDir = args[0]
@@ -25,12 +19,10 @@ fun main(args: Array<String>) {
check(branch == "master") { check(branch == "master") {
"We should be on master branch" "We should be on master branch"
} }
val mergeBaseCommit = getMergeBaseWithMaster(projectDir, objectId)
println("Base commit $mergeBaseCommit")
withGit(projectDir) { git -> withGit(projectDir) { git ->
val log = git.log().setMaxCount(500).call().toList() val log = git.log().setMaxCount(500).call().toList()
println("First commit hash in log: " + log.first().name + " log size: ${log.size}") println("First commit hash in log: " + log.first().name + " log size: ${log.size}")
val logDiff = log.takeWhile { it.id.name != mergeBaseCommit } val logDiff = log.takeWhile { it.id.name != objectId.name }
val numCommits = logDiff.size val numCommits = logDiff.size
println("Log diff size is $numCommits") println("Log diff size is $numCommits")
check(numCommits < 450) { check(numCommits < 450) {
@@ -43,18 +35,3 @@ fun main(args: Array<String>) {
println("##teamcity[setParameter name='env.ORG_GRADLE_PROJECT_version' value='$nextVersion']") println("##teamcity[setParameter name='env.ORG_GRADLE_PROJECT_version' value='$nextVersion']")
} }
} }
private fun getMergeBaseWithMaster(projectDir: String, tag: ObjectId): String {
withRepo(projectDir) { repo ->
val master = repo.resolve("master")
RevWalk(repo).use { walk ->
val tagRevCommit = walk.parseCommit(tag)
val masterRevCommit = walk.parseCommit(master)
walk.setRevFilter(RevFilter.MERGE_BASE)
walk.markStart(tagRevCommit)
walk.markStart(masterRevCommit)
val mergeBase: RevCommit = walk.next()
return mergeBase.name
}
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package 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

@@ -64,31 +64,6 @@ enum class ReleaseType {
STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0 STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
} }
internal fun getVersionsExistingVersionsFor(
majorVersion: Int,
minorVersion: Int,
projectDir: String,
): Map<Semver, ObjectId> {
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
val git = Git(repository)
println(git.log().call().first())
println(git.tagList().call().first())
return git.tagList().call().mapNotNull { ref ->
runCatching {
// Git has two types of tags: light and annotated. This code detect hash of the commit for both types of tags
val revWalk = RevWalk(repository)
val tag = revWalk.parseAny(ref.objectId)
val commitHash = revWalk.peel(tag).id
val semver = Semver(ref.name.removePrefix("refs/tags/"))
if (semver.major == majorVersion && semver.minor == minorVersion) {
semver to commitHash
} else null
}.getOrNull()
}
.toMap()
}
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> { internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build() val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
val git = Git(repository) val git = Git(repository)

View File

@@ -0,0 +1,62 @@
/*
* 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
import io.ktor.client.call.*
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
val areaWeights = setOf(
Triple("118-53212", "Plugins", 50),
Triple("118-53220", "Vim Script", 30),
Triple("118-54084", "Esc", 100),
)
suspend fun updateRates() {
println("Updating rates of the issues")
areaWeights.forEach { (id, name, weight) ->
val unmappedIssues = unmappedIssues(name)
println("Got ${unmappedIssues.size} for $name area")
unmappedIssues.forEach { issueId ->
print("Trying to update issue $issueId: ")
val response = updateCustomField(issueId) {
put("name", "Affected Rate")
put("\$type", "SimpleIssueCustomField")
put("value", weight)
}
println(response)
}
}
}
private suspend fun unmappedIssues(area: String): List<String> {
val areaProcessed = if (" " in area) "{$area}" else area
val res = issuesQuery(
query = "project: VIM Affected Rate: {No affected rate} Area: $areaProcessed #Unresolved",
fields = "id,idReadable"
)
return res.body<JsonArray>().map { it.jsonObject }.map { it["idReadable"]!!.jsonPrimitive.content }
}
suspend fun getAreasWithoutWeight(): Set<Pair<String, String>> {
val allAreas = getAreaValues()
return allAreas
.filterNot { it.key in areaWeights.map { it.first }.toSet() }
.entries
.map { it.key to it.value }
.toSet()
}
suspend fun main() {
updateRates()
}

20
settings.gradle Normal file
View File

@@ -0,0 +1,20 @@
// Set repository for snapshot versions of gradle plugin
pluginManagement {
repositories {
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
gradlePluginPortal()
}
}
rootProject.name = 'IdeaVIM'
include 'vim-engine'
include 'scripts'
include 'annotation-processors'
include 'tests:java-tests'
include 'tests:property-tests'
include 'tests:long-running-tests'
include 'tests:ui-ij-tests'
include 'tests:ui-py-tests'
include 'tests:ui-fixtures'

View File

@@ -1,21 +0,0 @@
// Set repository for snapshot versions of gradle plugin
pluginManagement {
repositories {
maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
}
gradlePluginPortal()
}
}
rootProject.name = "IdeaVIM"
include("vim-engine")
include("scripts")
include("annotation-processors")
include("tests:java-tests")
include("tests:property-tests")
include("tests:long-running-tests")
include("tests:ui-ij-tests")
include("tests:ui-py-tests")
include("tests:ui-fixtures")

View File

@@ -101,14 +101,10 @@ command:
(WS | COLON)* range? (WS | COLON)* EXECUTE WS* (expr WS*)* (NEW_LINE | BAR)+ (WS | COLON)* range? (WS | COLON)* EXECUTE WS* (expr WS*)* (NEW_LINE | BAR)+
#ExecuteCommand| #ExecuteCommand|
(WS | COLON)* range? (WS | COLON)* lShift (WS | COLON)* range? (WS | COLON)* lShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
(NEW_LINE | BAR)*
#ShiftLeftCommand| #ShiftLeftCommand|
(WS | COLON)* range? (WS | COLON)* rShift (WS | COLON)* range? (WS | COLON)* rShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
(NEW_LINE | BAR)*
#ShiftRightCommand| #ShiftRightCommand|
(WS | COLON)* range? (WS | COLON)* (WS | COLON)* range? (WS | COLON)*
@@ -119,7 +115,6 @@ command:
| PROMPT_REPLACE | P_LOWERCASE | P_UPPERCASE | PRINT | PREVIOUS_TAB | N_UPPERCASE | PREVIOUS_FILE | PLUG | PROMPT_REPLACE | P_LOWERCASE | P_UPPERCASE | PRINT | PREVIOUS_TAB | N_UPPERCASE | PREVIOUS_FILE | PLUG
| ONLY | NO_HL_SEARCH | NEXT_TAB | N_LOWERCASE | NEXT_FILE | M_LOWERCASE | MOVE_TEXT | MARKS | K_LOWERCASE | ONLY | NO_HL_SEARCH | NEXT_TAB | N_LOWERCASE | NEXT_FILE | M_LOWERCASE | MOVE_TEXT | MARKS | K_LOWERCASE
| MARK_COMMAND | JUMPS | J_LOWERCASE | JOIN_LINES | HISTORY | GO_TO_CHAR | SYMBOL | FIND | CLASS | F_LOWERCASE | MARK_COMMAND | JUMPS | J_LOWERCASE | JOIN_LINES | HISTORY | GO_TO_CHAR | SYMBOL | FIND | CLASS | F_LOWERCASE
| CLEARJUMPS
| FILE | EXIT | E_LOWERCASE | EDIT_FILE | DUMP_LINE | DIGRAPH | DEL_MARKS | D_LOWERCASE | DEL_LINES | DELCMD | FILE | EXIT | E_LOWERCASE | EDIT_FILE | DUMP_LINE | DIGRAPH | DEL_MARKS | D_LOWERCASE | DEL_LINES | DELCMD
| T_LOWERCASE | COPY | CMD_CLEAR | BUFFER_LIST | BUFFER_CLOSE | B_LOWERCASE | BUFFER | ASCII | T_LOWERCASE | COPY | CMD_CLEAR | BUFFER_LIST | BUFFER_CLOSE | B_LOWERCASE | BUFFER | ASCII
| ACTIONLIST | ACTION | LOCKVAR | UNLOCKVAR | PACKADD | TABMOVE | ACTIONLIST | ACTION | LOCKVAR | UNLOCKVAR | PACKADD | TABMOVE
@@ -619,7 +614,6 @@ BUFFER_CLOSE: 'bd' | 'bde' | 'bdel' | 'bdele' | 'bdelet' | 'bdelete';
BUFFER_LIST: 'buffers' | 'ls' | 'files'; BUFFER_LIST: 'buffers' | 'ls' | 'files';
CALL: 'cal' | 'call'; CALL: 'cal' | 'call';
CLASS: 'cla' | 'clas' | 'class'; CLASS: 'cla' | 'clas' | 'class';
CLEARJUMPS: 'cle' | 'clea' | 'clear' | 'clearj' | 'clearju' | 'clearjum' | 'clearjump' | 'clearjumps';
CMD: 'com' | 'comm' | 'comma' | 'comman' | 'command'; CMD: 'com' | 'comm' | 'comma' | 'comman' | 'command';
CMD_CLEAR: 'comc' | 'comcl' | 'comcle' | 'comclea' | 'comclear'; CMD_CLEAR: 'comc' | 'comcl' | 'comcle' | 'comclea' | 'comclear';
COPY: 'co' | 'cop' | 'copy'; COPY: 'co' | 'cop' | 'copy';
@@ -830,4 +824,4 @@ AUGROUP_SKIP: NEW_LINE (WS|COLON)* AUGROUP .*? AUGROUP WS+ END -> skip
// All the other symbols // All the other symbols
UNICODE_CHAR: '\u0000'..'\uFFFE'; UNICODE_CHAR: '\u0000'..'\uFFFE';
EMOJI: [\p{Emoji}]; EMOJI: [\p{Emoji}];

View File

@@ -11,6 +11,7 @@ package com.maddyhome.idea.vim;
import com.intellij.openapi.Disposable; import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.ShortcutSet; import com.intellij.openapi.actionSystem.ShortcutSet;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.actionSystem.TypedAction; import com.intellij.openapi.editor.actionSystem.TypedAction;
@@ -79,6 +80,14 @@ public class EventFacade {
action.unregisterCustomShortcutSet(component); action.unregisterCustomShortcutSet(component);
} }
public void addDocumentListener(@NotNull Document document, @NotNull DocumentListener listener) {
document.addDocumentListener(listener);
}
public void removeDocumentListener(@NotNull Document document, @NotNull DocumentListener listener) {
document.removeDocumentListener(listener);
}
public void addEditorFactoryListener(@NotNull EditorFactoryListener listener, @NotNull Disposable parentDisposable) { public void addEditorFactoryListener(@NotNull EditorFactoryListener listener, @NotNull Disposable parentDisposable) {
EditorFactory.getInstance().addEditorFactoryListener(listener, parentDisposable); EditorFactory.getInstance().addEditorFactoryListener(listener, parentDisposable);
} }
@@ -89,12 +98,20 @@ public class EventFacade {
editor.getCaretModel().addCaretListener(listener, disposable); editor.getCaretModel().addCaretListener(listener, disposable);
} }
public void removeCaretListener(@NotNull Editor editor, @NotNull CaretListener listener) {
editor.getCaretModel().removeCaretListener(listener);
}
public void addEditorMouseListener(@NotNull Editor editor, public void addEditorMouseListener(@NotNull Editor editor,
@NotNull EditorMouseListener listener, @NotNull EditorMouseListener listener,
@NotNull Disposable disposable) { @NotNull Disposable disposable) {
editor.addEditorMouseListener(listener, disposable); editor.addEditorMouseListener(listener, disposable);
} }
public void removeEditorMouseListener(@NotNull Editor editor, @NotNull EditorMouseListener listener) {
editor.removeEditorMouseListener(listener);
}
public void addComponentMouseListener(@NotNull Component component, public void addComponentMouseListener(@NotNull Component component,
@NotNull MouseListener mouseListener, @NotNull MouseListener mouseListener,
@NotNull Disposable disposable) { @NotNull Disposable disposable) {
@@ -102,18 +119,30 @@ public class EventFacade {
Disposer.register(disposable, () -> component.removeMouseListener(mouseListener)); Disposer.register(disposable, () -> component.removeMouseListener(mouseListener));
} }
public void removeComponentMouseListener(@NotNull Component component, @NotNull MouseListener mouseListener) {
component.removeMouseListener(mouseListener);
}
public void addEditorMouseMotionListener(@NotNull Editor editor, public void addEditorMouseMotionListener(@NotNull Editor editor,
@NotNull EditorMouseMotionListener listener, @NotNull EditorMouseMotionListener listener,
@NotNull Disposable disposable) { @NotNull Disposable disposable) {
editor.addEditorMouseMotionListener(listener, disposable); editor.addEditorMouseMotionListener(listener, disposable);
} }
public void removeEditorMouseMotionListener(@NotNull Editor editor, @NotNull EditorMouseMotionListener listener) {
editor.removeEditorMouseMotionListener(listener);
}
public void addEditorSelectionListener(@NotNull Editor editor, public void addEditorSelectionListener(@NotNull Editor editor,
@NotNull SelectionListener listener, @NotNull SelectionListener listener,
@NotNull Disposable disposable) { @NotNull Disposable disposable) {
editor.getSelectionModel().addSelectionListener(listener, disposable); editor.getSelectionModel().addSelectionListener(listener, disposable);
} }
public void removeEditorSelectionListener(@NotNull Editor editor, @NotNull SelectionListener listener) {
editor.getSelectionModel().removeSelectionListener(listener);
}
private @NotNull TypedAction getTypedAction() { private @NotNull TypedAction getTypedAction() {
return TypedAction.getInstance(); return TypedAction.getInstance();
} }

View File

@@ -8,23 +8,14 @@
package com.maddyhome.idea.vim package com.maddyhome.idea.vim
import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.ide.plugins.PluginStateListener
import com.intellij.ide.plugins.PluginStateManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.updateSettings.impl.UpdateSettings
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.helper.localEditors
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.ui.JoinEap
import com.maddyhome.idea.vim.ui.JoinEap.EAP_LINK
/** /**
* @author Alex Plate * @author Alex Plate
@@ -37,36 +28,16 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
if (firstInitializationOccurred) return if (firstInitializationOccurred) return
firstInitializationOccurred = true firstInitializationOccurred = true
if (!VimPlugin.getVimState().wasSubscibedToEAPAutomatically && ApplicationManager.getApplication().isEAP && !JoinEap.eapActive()) {
VimPlugin.getVimState().wasSubscibedToEAPAutomatically = true
UpdateSettings.getInstance().storedPluginHosts += EAP_LINK
}
// This code should be executed once // This code should be executed once
VimPlugin.getInstance().initialize() VimPlugin.getInstance().initialize()
// Uninstall survey. Should be registered once for all projects
PluginStateManager.addStateListener(object : PluginStateListener {
override fun install(p0: IdeaPluginDescriptor) {/*Nothing*/
}
override fun uninstall(descriptor: IdeaPluginDescriptor) {
if (descriptor.pluginId == VimPlugin.getPluginId()) {
BrowserUtil.open("https://surveys.jetbrains.com/s3/ideavim-uninstall-feedback")
}
}
})
} }
} }
// This is a temporal workaround for VIM-2487 // This is a temporal workaround for VIM-2487
internal class PyNotebooksCloseWorkaround : ProjectManagerListener { internal class PyNotebooksCloseWorkaround : ProjectManagerListener {
override fun projectClosingBeforeSave(project: Project) { override fun projectClosingBeforeSave(project: Project) {
initInjector()
// TODO: Confirm context in CWM scenario
if (injector.globalIjOptions().closenotebooks) { if (injector.globalIjOptions().closenotebooks) {
injector.editorGroup.getEditors().forEach { vimEditor -> localEditors().forEach { editor ->
val editor = (vimEditor as IjVimEditor).editor
val virtualFile = EditorHelper.getVirtualFile(editor) val virtualFile = EditorHelper.getVirtualFile(editor)
if (virtualFile?.extension == "ipynb") { if (virtualFile?.extension == "ipynb") {
val fileEditorManager = FileEditorManagerEx.getInstanceEx(project) val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)

View File

@@ -14,43 +14,44 @@ import com.maddyhome.idea.vim.key.MappingOwner
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
object RegisterActions { public object RegisterActions {
/** /**
* Register all the key/action mappings for the plugin. * Register all the key/action mappings for the plugin.
*/ */
@JvmStatic @JvmStatic
fun registerActions() { public fun registerActions() {
registerVimCommandActions() registerVimCommandActions()
registerShortcutsWithoutActions() registerEmptyShortcuts() // todo most likely it is not needed
} }
fun findAction(id: String): EditorActionHandlerBase? { public fun findAction(id: String): EditorActionHandlerBase? {
val commandBean = IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
?: EngineCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null ?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
return commandBean.instance return commandBean.instance
} }
fun findActionOrDie(id: String): EditorActionHandlerBase { public fun findActionOrDie(id: String): EditorActionHandlerBase {
return findAction(id) ?: throw RuntimeException("Action $id is not registered") return findAction(id) ?: throw RuntimeException("Action $id is not registered")
} }
@JvmStatic @JvmStatic
fun unregisterActions() { public fun unregisterActions() {
val keyGroup = VimPlugin.getKeyIfCreated() val keyGroup = VimPlugin.getKeyIfCreated()
keyGroup?.unregisterCommandActions() keyGroup?.unregisterCommandActions()
} }
private fun registerVimCommandActions() { private fun registerVimCommandActions() {
val parser = VimPlugin.getKey() val parser = VimPlugin.getKey()
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) } EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
} }
private fun registerShortcutsWithoutActions() { private fun registerEmptyShortcuts() {
val parser = VimPlugin.getKey() val parser = VimPlugin.getKey()
// The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we // The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we
// still need to register the shortcut, to make sure the editor doesn't swallow it. // still need to register the shortcut, to make sure the editor doesn't swallow it.
parser.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System) parser
.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System)
} }
} }

View File

@@ -37,9 +37,8 @@ import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
import com.maddyhome.idea.vim.helper.MacKeyRepeat; import com.maddyhome.idea.vim.helper.MacKeyRepeat;
import com.maddyhome.idea.vim.listener.VimListenerManager; import com.maddyhome.idea.vim.listener.VimListenerManager;
import com.maddyhome.idea.vim.newapi.IjVimInjector; import com.maddyhome.idea.vim.newapi.IjVimInjector;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
import com.maddyhome.idea.vim.ui.StatusBarIconFactory; import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.vimscript.services.VariableService; import com.maddyhome.idea.vim.vimscript.services.VariableService;
import com.maddyhome.idea.vim.yank.YankGroupBase; import com.maddyhome.idea.vim.yank.YankGroupBase;
import org.jdom.Element; import org.jdom.Element;
@@ -47,7 +46,6 @@ import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static com.maddyhome.idea.vim.group.EditorGroup.EDITOR_STORE_ELEMENT; import static com.maddyhome.idea.vim.group.EditorGroup.EDITOR_STORE_ELEMENT;
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT; import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
import static com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc; import static com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc;
@@ -68,7 +66,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
private static final Logger LOG = Logger.getInstance(VimPlugin.class); private static final Logger LOG = Logger.getInstance(VimPlugin.class);
static { static {
IjVimInjectorKt.initInjector(); VimInjectorKt.setInjector(new IjVimInjector());
} }
private final @NotNull VimState state = new VimState(); private final @NotNull VimState state = new VimState();
@@ -125,12 +123,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return (FileGroup)VimInjectorKt.getInjector().getFile(); return (FileGroup)VimInjectorKt.getInjector().getFile();
} }
public static @NotNull IjVimSearchGroup getSearch() { public static @NotNull SearchGroup getSearch() {
return ApplicationManager.getApplication().getService(IjVimSearchGroup.class); return ApplicationManager.getApplication().getService(SearchGroup.class);
} }
public static @Nullable IjVimSearchGroup getSearchIfCreated() { public static @Nullable SearchGroup getSearchIfCreated() {
return ApplicationManager.getApplication().getServiceIfCreated(IjVimSearchGroup.class); return ApplicationManager.getApplication().getServiceIfCreated(SearchGroup.class);
} }
public static @NotNull ProcessGroup getProcess() { public static @NotNull ProcessGroup getProcess() {
@@ -213,22 +211,22 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
public static void setEnabled(final boolean enabled) { public static void setEnabled(final boolean enabled) {
if (isEnabled() == enabled) return; if (isEnabled() == enabled) return;
if (!enabled) {
getInstance().turnOffPlugin(true);
}
getInstance().enabled = enabled; getInstance().enabled = enabled;
if (enabled) {
getInstance().turnOnPlugin();
}
if (enabled) { if (enabled) {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn(); VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
} else { } else {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff(); VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
} }
if (!enabled) {
getInstance().turnOffPlugin(true);
}
if (enabled) {
getInstance().turnOnPlugin();
}
StatusBarIconFactory.Util.INSTANCE.updateIcon(); StatusBarIconFactory.Util.INSTANCE.updateIcon();
} }
@@ -284,13 +282,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
ideavimrcRegistered = true; ideavimrcRegistered = true;
if (!ApplicationManager.getApplication().isUnitTestMode()) { if (!ApplicationManager.getApplication().isUnitTestMode()) {
try { executeIdeaVimRc(editor);
injector.getOptionGroup().startInitVimRc();
executeIdeaVimRc(editor);
}
finally {
injector.getOptionGroup().endInitVimRc();
}
} }
} }
@@ -347,21 +339,20 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
} }
private void turnOffPlugin(boolean unsubscribe) { private void turnOffPlugin(boolean unsubscribe) {
IjVimSearchGroup searchGroup = getSearchIfCreated(); SearchGroup searchGroup = getSearchIfCreated();
if (searchGroup != null) { if (searchGroup != null) {
searchGroup.turnOff(); searchGroup.turnOff();
} }
if (unsubscribe) { if (unsubscribe) {
VimListenerManager.INSTANCE.turnOff(); VimListenerManager.INSTANCE.turnOff();
} }
injector.getCommandLine().fullReset(); ExEntryPanel.fullReset();
// Unregister vim actions in command mode // Unregister vim actions in command mode
RegisterActions.unregisterActions(); RegisterActions.unregisterActions();
if (onOffDisposable != null) { if (onOffDisposable != null) {
Disposer.dispose(onOffDisposable); Disposer.dispose(onOffDisposable);
onOffDisposable = null;
} }
} }

View File

@@ -12,13 +12,13 @@ import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.group.EditorHolderService
@Service(Service.Level.PROJECT) @Service
internal class VimProjectService(val project: Project) : Disposable { internal class VimProjectService(val project: Project) : Disposable {
override fun dispose() { override fun dispose() {
// Not sure if this is a best solution // Not sure if this is a best solution
ExEntryPanel.getInstance().setEditor(null) EditorHolderService.getInstance().editor = null
} }
companion object { companion object {

View File

@@ -32,7 +32,7 @@ import javax.swing.KeyStroke
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper * This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
* way to get ideavim keys for this plugin. See VIM-3085 * way to get ideavim keys for this plugin. See VIM-3085
*/ */
class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx { public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
private val handler = KeyHandler.getInstance() private val handler = KeyHandler.getInstance()
private val traceTime = injector.globalOptions().ideatracetime private val traceTime = injector.globalOptions().ideatracetime
@@ -77,7 +77,7 @@ class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandle
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0 val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers) val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
val startTime = if (traceTime) System.currentTimeMillis() else null val startTime = if (traceTime) System.currentTimeMillis() else null
handler.handleKey(editor.vim, keyStroke, context.vim, handler.keyHandlerState) handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim))
if (startTime != null) { if (startTime != null) {
val duration = System.currentTimeMillis() - startTime val duration = System.currentTimeMillis() - startTime
LOG.info("VimTypedAction '$charTyped': $duration ms") LOG.info("VimTypedAction '$charTyped': $duration ms")

View File

@@ -1,29 +1,27 @@
/* /*
* Copyright 2003-2024 The IdeaVim authors * Copyright 2003-2023 The IdeaVim authors
* *
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at * license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT. * https://opensource.org/licenses/MIT.
*/ */
package com.maddyhome.idea.vim.action
package com.maddyhome.idea.vim.action.ex
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
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.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["<Insert>"], modes = [Mode.CMD_LINE]) @CommandOrMotion(keys = [":"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
class ToggleInsertModeAction : VimActionHandler.SingleExecution() { internal class ExEntryAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val commandLine = injector.commandLine.getActiveCommandLine() ?: return false VimPlugin.getProcess().startExCommand(editor, context, cmd)
commandLine.toggleReplaceMode()
return true return true
} }
} }

View File

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

View File

@@ -1,39 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
/**
* Clear and redraw the screen
*
* Typically, an IDE does not need to redraw the screen - it's editor handles scrolling, etc. However, it can be
* necessary to clear the status line.
*/
@CommandOrMotion(keys = ["<C-L>"], modes = [Mode.NORMAL])
internal class RedrawAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
injector.redrawService.redraw()
return true
}
}

View File

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

View File

@@ -43,10 +43,8 @@ import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.listener.AceJumpService import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.ex.ExTextField
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.InputEvent import java.awt.event.InputEvent
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
@@ -61,14 +59,11 @@ import javax.swing.KeyStroke
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper * This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
* way to get ideavim keys for this plugin. See VIM-3085 * way to get ideavim keys for this plugin. See VIM-3085
*/ */
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ { public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
init {
initInjector()
}
private val traceTime: Boolean private val traceTime: Boolean
get() { get() {
// Make sure the injector is initialized
VimPlugin.getInstance()
return injector.globalOptions().ideatracetime return injector.globalOptions().ideatracetime
} }
@@ -84,8 +79,11 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
// Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler? // Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler?
try { try {
val start = if (traceTime) System.currentTimeMillis() else null val start = if (traceTime) System.currentTimeMillis() else null
val keyHandler = KeyHandler.getInstance() KeyHandler.getInstance().handleKey(
keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState) editor.vim,
keyStroke,
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
)
if (start != null) { if (start != null) {
val duration = System.currentTimeMillis() - start val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut update '$keyStroke': $duration ms") LOG.info("VimShortcut update '$keyStroke': $duration ms")
@@ -258,14 +256,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
return null return null
} }
private fun getEditor(e: AnActionEvent): Editor? { private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
return e.getData(PlatformDataKeys.EDITOR)
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
ExEntryPanel.getInstance().ijEditor
} else {
null
}
}
/** /**
* Every time the key pressed with an active lookup, there is a decision: * Every time the key pressed with an active lookup, there is a decision:
@@ -389,10 +380,6 @@ private class ActionEnableStatus(
} }
} }
override fun toString(): String {
return "ActionEnableStatus(isEnabled=$isEnabled, message='$message', logLevel=$logLevel)"
}
companion object { companion object {
private val LOG = logger<ActionEnableStatus>() private val LOG = logger<ActionEnableStatus>()

View File

@@ -14,7 +14,6 @@ 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
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.api.setChangeMarks import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
@@ -22,76 +21,29 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.argumentCaptured import com.maddyhome.idea.vim.common.argumentCaptured
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.inRepeatMode import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
// todo make it multicaret // todo make it multicaret
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, motionType: SelectionType): Boolean { private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
val func = injector.globalOptions().operatorfunc val operatorFunction = injector.keyGroup.operatorFunction
if (func.isEmpty()) { if (operatorFunction == null) {
VimPlugin.showMessage(MessageHelper.message("E774")) VimPlugin.showMessage(MessageHelper.message("E774"))
return false return false
} }
val scriptContext = CommandLineVimLContext
// The option value is either a function name, which should have a handler, or it might be a lambda expression, or a
// `function` or `funcref` call expression, all of which will return a funcref (with a handler)
var handler = injector.functionService.getFunctionHandlerOrNull(null, func, scriptContext)
if (handler == null) {
val expression = injector.vimscriptParser.parseExpression(func)
if (expression != null) {
try {
val value = expression.evaluate(editor, context, scriptContext)
if (value is VimFuncref) {
handler = value.handler
}
} catch (_: ExException) {
// Get the argument for function('...') or funcref('...') for the error message
val functionName = if (expression is FunctionCallExpression && expression.arguments.isNotEmpty()) {
expression.arguments[0].evaluate(editor, context, scriptContext).toString()
}
else {
func
}
VimPlugin.showMessage("E117: Unknown function: $functionName")
return false
}
}
}
if (handler == null) {
VimPlugin.showMessage("E117: Unknown function: $func")
return false
}
val arg = when (motionType) {
SelectionType.LINE_WISE -> "line"
SelectionType.CHARACTER_WISE -> "char"
SelectionType.BLOCK_WISE -> "block"
}
val saveRepeatHandler = VimRepeater.repeatHandler val saveRepeatHandler = VimRepeater.repeatHandler
injector.markService.setChangeMarks(editor.primaryCaret(), textRange) injector.markService.setChangeMarks(editor.primaryCaret(), textRange)
KeyHandler.getInstance().reset(editor) KeyHandler.getInstance().reset(editor)
val result = operatorFunction.apply(editor, context, selectionType)
val arguments = listOf(SimpleExpression(arg))
handler.executeFunction(arguments, editor, context, scriptContext)
VimRepeater.repeatHandler = saveRepeatHandler VimRepeater.repeatHandler = saveRepeatHandler
return true return result
} }
@CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL])
@@ -101,13 +53,19 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
override val argumentType: Argument.Type = Argument.Type.MOTION override val argumentType: Argument.Type = Argument.Type.MOTION
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val argument = cmd.argument as? Argument.Motion ?: return false val argument = cmd.argument ?: return false
if (!editor.inRepeatMode) { if (!editor.vimStateMachine.isDotRepeatInProgress) {
argumentCaptured = argument argumentCaptured = argument
} }
val range = getMotionRange(editor, context, argument, operatorArguments) val range = getMotionRange(editor, context, argument, operatorArguments)
if (range != null) { if (range != null) {
return doOperatorAction(editor, context, range, argument.getMotionType()) val selectionType = if (argument.motion.isLinewiseMotion()) {
SelectionType.LINE_WISE
} else {
SelectionType.CHARACTER_WISE
}
return doOperatorAction(editor, context, range, selectionType)
} }
return false return false
} }
@@ -115,7 +73,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
private fun getMotionRange( private fun getMotionRange(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
argument: Argument.Motion, argument: Argument,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): TextRange? { ): TextRange? {
// Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start // Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start
@@ -130,7 +88,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
operatorArguments, operatorArguments,
)?.normalize()?.let { )?.normalize()?.let {
// If we're linewise, make sure the end offset isn't just the EOL char // If we're linewise, make sure the end offset isn't just the EOL char
if (argument.getMotionType() == SelectionType.LINE_WISE && it.endOffset < editor.fileSize()) { if (argument.motion.isLinewiseMotion() && it.endOffset < editor.fileSize()) {
TextRange(it.startOffset, it.endOffset + 1) TextRange(it.startOffset, it.endOffset + 1)
} else { } else {
it it

View File

@@ -7,9 +7,9 @@
*/ */
package com.maddyhome.idea.vim.action.change package com.maddyhome.idea.vim.action.change
import com.intellij.openapi.command.CommandProcessor
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
import com.intellij.openapi.command.CommandProcessor
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.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@@ -17,6 +17,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
@CommandOrMotion(keys = ["."], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["."], modes = [Mode.NORMAL])
@@ -24,8 +25,8 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_WRITABLE override val type: Command.Type = Command.Type.OTHER_WRITABLE
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val state = injector.vimState val state = editor.vimStateMachine
var lastCommand = VimRepeater.lastChangeCommand val lastCommand = VimRepeater.lastChangeCommand
if (lastCommand == null && Extension.lastExtensionHandler == null) return false if (lastCommand == null && Extension.lastExtensionHandler == null) return false
@@ -57,7 +58,12 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
) )
} else if (!repeatHandler && lastCommand != null) { } else if (!repeatHandler && lastCommand != null) {
if (cmd.rawCount > 0) { if (cmd.rawCount > 0) {
lastCommand = lastCommand.copy(rawCount = cmd.rawCount) lastCommand.count = cmd.count
val arg = lastCommand.argument
if (arg != null) {
val mot = arg.motion
mot.count = 0
}
} }
state.executingCommand = lastCommand state.executingCommand = lastCommand

View File

@@ -20,7 +20,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.newapi.ijOptions import com.maddyhome.idea.vim.newapi.ijOptions
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() { public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE
override fun runAsMulticaret( override fun runAsMulticaret(
editor: VimEditor, editor: VimEditor,
@@ -40,7 +40,7 @@ class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecuti
): Boolean { ): Boolean {
injector.editorGroup.notifyIdeaJoin(editor) injector.editorGroup.notifyIdeaJoin(editor)
return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false) return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false, operatorArguments)
} }
override fun execute( override fun execute(

View File

@@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.newapi.ijOptions import com.maddyhome.idea.vim.newapi.ijOptions
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() { public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE
override fun execute( override fun execute(
@@ -34,8 +34,8 @@ class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution()
} }
injector.editorGroup.notifyIdeaJoin(editor) injector.editorGroup.notifyIdeaJoin(editor)
var res = true var res = true
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret -> editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true)) { if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) {
res = false res = false
} }
} }

View File

@@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ijOptions
* @author vlan * @author vlan
*/ */
@CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL]) @CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL])
class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() { public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE
override fun executeForAllCarets( override fun executeForAllCarets(
@@ -39,7 +39,7 @@ class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution(
return true return true
} }
var res = true var res = true
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret -> editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!caret.isValid) return@forEach if (!caret.isValid) return@forEach
val range = caretsAndSelections[caret] ?: return@forEach val range = caretsAndSelections[caret] ?: return@forEach
if (!injector.changeGroup.deleteJoinRange( if (!injector.changeGroup.deleteJoinRange(

View File

@@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ijOptions
* @author vlan * @author vlan
*/ */
@CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL]) @CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL])
class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() { public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE
override fun executeForAllCarets( override fun executeForAllCarets(
@@ -39,7 +39,7 @@ class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExec
return true return true
} }
var res = true var res = true
editor.carets().sortedByDescending { it.offset }.forEach { caret -> editor.carets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!caret.isValid) return@forEach if (!caret.isValid) return@forEach
val range = caretsAndSelections[caret] ?: return@forEach val range = caretsAndSelections[caret] ?: return@forEach
if (!injector.changeGroup.deleteJoinRange( if (!injector.changeGroup.deleteJoinRange(

View File

@@ -22,6 +22,11 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.* import java.util.*
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE) {
override val type: Command.Type = Command.Type.DELETE
}
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE) { internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE) {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE
@@ -31,18 +36,6 @@ internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELET
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN) { internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN) {
override val type: Command.Type = Command.Type.MOTION override val type: Command.Type = Command.Type.MOTION
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments
): Boolean {
val undo = injector.undo
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
return super.execute(editor, context, cmd, operatorArguments)
}
} }
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
@@ -55,18 +48,6 @@ internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB) {
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP) { internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP) {
override val type: Command.Type = Command.Type.MOTION override val type: Command.Type = Command.Type.MOTION
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments
): Boolean {
val undo = injector.undo
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
return super.execute(editor, context, cmd, operatorArguments)
}
} }
@CommandOrMotion(keys = ["K"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["K"], modes = [Mode.NORMAL])
@@ -74,7 +55,7 @@ internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(editor, IdeActions.ACTION_QUICK_JAVADOC, context) injector.actionExecutor.executeAction(IdeActions.ACTION_QUICK_JAVADOC, context)
return true return true
} }
} }

View File

@@ -1,30 +1,35 @@
/* /*
* Copyright 2003-2024 The IdeaVim authors * Copyright 2003-2023 The IdeaVim authors
* *
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at * license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT. * https://opensource.org/licenses/MIT.
*/ */
package com.maddyhome.idea.vim.action.ex package com.maddyhome.idea.vim.action.ex
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
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.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import java.util.*
@CommandOrMotion(keys = ["<Up>"], modes = [Mode.CMD_LINE]) /**
class HistoryUpFilterAction : VimActionHandler.SingleExecution() { * Called by KeyHandler to process the contents of the ex entry panel
*
* The mapping for this action means that the ex command is executed as a write action
*/
@CommandOrMotion(keys = ["<CR>", "<C-M>", "<C-J>"], modes = [Mode.CMD_LINE])
public class ProcessExEntryAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override val flags: EnumSet<CommandFlags> = EnumSet.of(CommandFlags.FLAG_COMPLETE_EX)
val commandLine = injector.commandLine.getActiveCommandLine() ?: return false
commandLine.selectHistory(isUp = true, filter = true)
return true override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
return VimPlugin.getProcess().processExEntry(editor, context)
} }
} }

View File

@@ -9,48 +9,41 @@
package com.maddyhome.idea.vim.command package com.maddyhome.idea.vim.command
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.SelectionType
/** /**
* COMPATIBILITY-LAYER: Additional class * COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r * Please see: https://jb.gg/zo8n0r
*/ */
@Deprecated("Use `injector.vimState`") public class CommandState(private val machine: VimStateMachine) {
class CommandState(private val machine: VimStateMachine) {
val mode: Mode public val isOperatorPending: Boolean
get() = machine.isOperatorPending
public val mode: CommandState.Mode
get() { get() {
val myMode = machine.mode val myMode = machine.mode
return when (myMode) { return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> CommandState.Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> CommandState.Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> CommandState.Mode.VISUAL
} }
} }
@Deprecated("Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith( public val commandBuilder: CommandBuilder
"KeyHandler.getInstance().keyHandlerState.commandBuilder", get() = machine.commandBuilder
"com.maddyhome.idea.vim.KeyHandler"
)
)
val commandBuilder: CommandBuilder
get() = KeyHandler.getInstance().keyHandlerState.commandBuilder
@Deprecated("Use `KeyHandler.keyHandlerState.mappingState", ReplaceWith( public val mappingState: MappingState
"KeyHandler.getInstance().keyHandlerState.mappingState", get() = machine.mappingState
"com.maddyhome.idea.vim.KeyHandler"
)
)
val mappingState: MappingState
get() = KeyHandler.getInstance().keyHandlerState.mappingState
enum class Mode { public enum class Mode {
// Basic modes // Basic modes
COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/ COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
@@ -58,15 +51,22 @@ class CommandState(private val machine: VimStateMachine) {
OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
} }
enum class SubMode { public enum class SubMode {
NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
} }
companion object { public companion object {
@JvmStatic @JvmStatic
@Deprecated("Use `injector.vimState`") public fun getInstance(editor: Editor): CommandState {
fun getInstance(editor: Editor): CommandState { return CommandState(editor.vim.vimStateMachine)
return CommandState(injector.vimState)
} }
} }
} }
internal val CommandState.SubMode.engine: SelectionType
get() = when (this) {
CommandState.SubMode.NONE -> error("Unexpected value")
CommandState.SubMode.VISUAL_CHARACTER -> SelectionType.CHARACTER_WISE
CommandState.SubMode.VISUAL_LINE -> SelectionType.LINE_WISE
CommandState.SubMode.VISUAL_BLOCK -> SelectionType.BLOCK_WISE
}

View File

@@ -12,18 +12,18 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) { public class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) {
fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column public fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column
companion object { public companion object {
fun fromOffset(editor: Editor, offset: Int): CharacterPosition { public fun fromOffset(editor: Editor, offset: Int): CharacterPosition {
// logical position "expands" tabs // logical position "expands" tabs
val logicalPosition = editor.offsetToLogicalPosition(offset) val logicalPosition = editor.offsetToLogicalPosition(offset)
val lineStartOffset = editor.vim.getLineStartOffset(logicalPosition.line) val lineStartOffset = editor.vim.getLineStartOffset(logicalPosition.line)
return CharacterPosition(logicalPosition.line, offset - lineStartOffset) return CharacterPosition(logicalPosition.line, offset - lineStartOffset)
} }
fun atCaret(editor: Editor): CharacterPosition { public fun atCaret(editor: Editor): CharacterPosition {
return fromOffset(editor, editor.caretModel.offset) return fromOffset(editor, editor.caretModel.offset)
} }
} }

View File

@@ -9,20 +9,22 @@
package com.maddyhome.idea.vim.common package com.maddyhome.idea.vim.common
import com.intellij.application.options.CodeStyle import com.intellij.application.options.CodeStyle
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
import com.maddyhome.idea.vim.api.VimIndentConfig
internal class IndentConfig private constructor(indentOptions: IndentOptions): VimIndentConfig { internal class IndentConfig private constructor(indentOptions: IndentOptions) {
private val indentSize = indentOptions.INDENT_SIZE private val indentSize = indentOptions.INDENT_SIZE
private val tabSize = indentOptions.TAB_SIZE private val tabSize = indentOptions.TAB_SIZE
private val isUseTabs = indentOptions.USE_TAB_CHARACTER private val isUseTabs = indentOptions.USE_TAB_CHARACTER
override fun getIndentSize(depth: Int): Int = indentSize * depth fun getTotalIndent(count: Int): Int = indentSize * count
override fun createIndentByDepth(depth: Int): String = createIndentBySize(getIndentSize(depth))
override fun createIndentBySize(size: Int): String { fun createIndentByCount(count: Int): String = createIndentBySize(getTotalIndent(count))
fun createIndentBySize(size: Int): String {
val tabCount: Int val tabCount: Int
val spaceCount: Int val spaceCount: Int
if (isUseTabs) { if (isUseTabs) {
@@ -37,12 +39,13 @@ internal class IndentConfig private constructor(indentOptions: IndentOptions): V
companion object { companion object {
@JvmStatic @JvmStatic
fun create(editor: Editor): IndentConfig { fun create(editor: Editor, context: DataContext): IndentConfig {
return create(editor, editor.project) return create(editor, PlatformDataKeys.PROJECT.getData(context))
} }
@JvmStatic @JvmStatic
fun create(editor: Editor, project: Project?): IndentConfig { @JvmOverloads
fun create(editor: Editor, project: Project? = editor.project): IndentConfig {
val indentOptions = if (project != null) { val indentOptions = if (project != null) {
CodeStyle.getIndentOptions(project, editor.document) CodeStyle.getIndentOptions(project, editor.document)
} else { } else {

View File

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

View File

@@ -9,142 +9,40 @@ package com.maddyhome.idea.vim.ex
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimOutputPanelBase import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimExOutput import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel import com.maddyhome.idea.vim.ui.ExOutputPanel
import java.lang.ref.WeakReference
import javax.swing.KeyStroke
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing /**
class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanelBase() { * @author vlan
private var isActiveInTestMode = false */
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
override var text: String? = null
private set
val editor get() = myEditor.get() override fun output(text: String) {
val isActive: Boolean
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getNullablePanel(it) }?.myActive ?: false
} else {
isActiveInTestMode
}
override fun addText(text: String, isNewLine: Boolean) {
if (this.text.isNotEmpty() && isNewLine) this.text += "\n$text" else this.text += text
}
override fun show() {
if (editor == null) return
val currentPanel = injector.outputPanel.getCurrentOutputPanel()
if (currentPanel != null && currentPanel != this) currentPanel.close()
editor!!.vimExOutput = this
val exOutputPanel = ExOutputPanel.getInstance(editor!!)
if (!exOutputPanel.myActive) {
if (ApplicationManager.getApplication().isUnitTestMode) {
isActiveInTestMode = true
} else {
exOutputPanel.activate()
}
}
}
override fun scrollPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollPage()
}
override fun scrollHalfPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollHalfPage()
}
override fun scrollLine() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollLine()
}
override var text: String = ""
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).text } ?: ""
} else {
// ExOutputPanel always returns a non-null string
field
}
set(value) {
// ExOutputPanel will strip a trailing newline. We'll do it now so that tests have the same behaviour. We also
// never pass null to ExOutputPanel, but we do store it for tests, so we know if we're active or not
val newValue = value.removeSuffix("\n")
if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).setText(newValue) }
} else {
field = newValue
isActiveInTestMode = newValue.isNotEmpty()
}
}
override var label: String
get() {
val notNullEditor = editor ?: return ""
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return ""
return panel.myLabel.text
}
set(value) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.myLabel.text = value
}
fun output(text: String) {
this.text = text this.text = text
}
fun clear() {
text = ""
}
override val atEnd: Boolean
get() {
val notNullEditor = editor ?: return false
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return false
return panel.isAtEnd()
}
override fun onBadKey() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.onBadKey()
}
override fun close(key: KeyStroke?) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.close(key)
}
override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) { if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).close() } ExOutputPanel.getInstance(myEditor).setText(text)
}
else {
isActiveInTestMode = false
} }
} }
companion object { override fun clear() {
text = null
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).deactivate(false)
}
}
public companion object {
@JvmStatic @JvmStatic
fun getInstance(editor: Editor): ExOutputModel { public fun getInstance(editor: Editor): ExOutputModel {
var model = editor.vimExOutput var model = editor.vimExOutput
if (model == null) { if (model == null) {
model = ExOutputModel(WeakReference(editor)) model = ExOutputModel(editor)
editor.vimExOutput = model editor.vimExOutput = model
} }
return model return model
} }
@JvmStatic
fun tryGetInstance(editor: Editor) = editor.vimExOutput
} }
} }

View File

@@ -7,41 +7,28 @@
*/ */
package com.maddyhome.idea.vim.extension package com.maddyhome.idea.vim.extension
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.action.change.Extension import com.maddyhome.idea.vim.action.change.Extension
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.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.common.CommandAlias import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.helper.CommandLineHelper
import com.maddyhome.idea.vim.helper.TestInputModel import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.inRepeatMode import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.key.MappingOwner import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.ui.ex.ExEntryPanelService
import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimLContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionDeclaration
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionFlag
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import java.util.*
import javax.swing.KeyStroke import javax.swing.KeyStroke
/** /**
@@ -51,13 +38,13 @@ import javax.swing.KeyStroke
* *
* @author vlan * @author vlan
*/ */
object VimExtensionFacade { public object VimExtensionFacade {
private val LOG = logger<VimExtensionFacade>() private val LOG = logger<VimExtensionFacade>()
/** The 'map' command for mapping keys to handlers defined in extensions. */ /** The 'map' command for mapping keys to handlers defined in extensions. */
@JvmStatic @JvmStatic
fun putExtensionHandlerMapping( public fun putExtensionHandlerMapping(
modes: Set<MappingMode>, modes: Set<MappingMode>,
fromKeys: List<KeyStroke>, fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner, pluginOwner: MappingOwner,
@@ -68,15 +55,13 @@ object VimExtensionFacade {
} }
/**
* COMPATIBILITY-LAYER: Additional method
* Please see: https://jb.gg/zo8n0r
*/
/** The 'map' command for mapping keys to handlers defined in extensions. */
@JvmStatic @JvmStatic
@Deprecated( public fun putExtensionHandlerMapping(
"Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
ReplaceWith(
"VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
"com.maddyhome.idea.vim.VimPlugin"
)
)
fun putExtensionHandlerMapping(
modes: Set<MappingMode>, modes: Set<MappingMode>,
fromKeys: List<KeyStroke>, fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner, pluginOwner: MappingOwner,
@@ -88,7 +73,7 @@ object VimExtensionFacade {
/** The 'map' command for mapping keys to other keys. */ /** The 'map' command for mapping keys to other keys. */
@JvmStatic @JvmStatic
fun putKeyMapping( public fun putKeyMapping(
modes: Set<MappingMode>, modes: Set<MappingMode>,
fromKeys: List<KeyStroke>, fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner, pluginOwner: MappingOwner,
@@ -100,7 +85,7 @@ object VimExtensionFacade {
/** The 'map' command for mapping keys to other keys if there is no other mapping to these keys */ /** The 'map' command for mapping keys to other keys if there is no other mapping to these keys */
@JvmStatic @JvmStatic
fun putKeyMappingIfMissing( public fun putKeyMappingIfMissing(
modes: Set<MappingMode>, modes: Set<MappingMode>,
fromKeys: List<KeyStroke>, fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner, pluginOwner: MappingOwner,
@@ -114,7 +99,7 @@ object VimExtensionFacade {
/** /**
* Equivalent to calling 'command' to set up a user-defined command or alias * Equivalent to calling 'command' to set up a user-defined command or alias
*/ */
fun addCommand( public fun addCommand(
name: String, name: String,
handler: CommandAliasHandler, handler: CommandAliasHandler,
) { ) {
@@ -125,7 +110,7 @@ object VimExtensionFacade {
* Equivalent to calling 'command' to set up a user-defined command or alias * Equivalent to calling 'command' to set up a user-defined command or alias
*/ */
@JvmStatic @JvmStatic
fun addCommand( public fun addCommand(
name: String, name: String,
minimumNumberOfArguments: Int, minimumNumberOfArguments: Int,
maximumNumberOfArguments: Int, maximumNumberOfArguments: Int,
@@ -135,6 +120,12 @@ object VimExtensionFacade {
.setAlias(name, CommandAlias.Call(minimumNumberOfArguments, maximumNumberOfArguments, name, handler)) .setAlias(name, CommandAlias.Call(minimumNumberOfArguments, maximumNumberOfArguments, name, handler))
} }
/** Sets the value of 'operatorfunc' to be used as the operator function in 'g@'. */
@JvmStatic
public fun setOperatorFunction(function: OperatorFunction) {
VimPlugin.getKey().operatorFunction = function
}
/** /**
* Runs normal mode commands similar to ':normal! {commands}'. * Runs normal mode commands similar to ':normal! {commands}'.
* Mappings doesn't work with this function * Mappings doesn't work with this function
@@ -143,16 +134,15 @@ object VimExtensionFacade {
* leaves the editor in the insert mode if it's been activated. * leaves the editor in the insert mode if it's been activated.
*/ */
@JvmStatic @JvmStatic
fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) { public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim) val context = injector.executionContextManager.onEditor(editor.vim)
val keyHandler = KeyHandler.getInstance() keys.forEach { KeyHandler.getInstance().handleKey(editor.vim, it, context, false, false) }
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
} }
/** Returns a single key stroke from the user input similar to 'getchar()'. */ /** Returns a single key stroke from the user input similar to 'getchar()'. */
@JvmStatic @JvmStatic
fun inputKeyStroke(editor: Editor): KeyStroke { public fun inputKeyStroke(editor: Editor): KeyStroke {
if (editor.vim.inRepeatMode) { if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
val input = Extension.consumeKeystroke() val input = Extension.consumeKeystroke()
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input") LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}") return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
@@ -162,7 +152,7 @@ object VimExtensionFacade {
LOG.trace("Unit test mode is active") LOG.trace("Unit test mode is active")
val mappingStack = KeyHandler.getInstance().keyStack val mappingStack = KeyHandler.getInstance().keyStack
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also { mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
if (injector.registerGroup.isRecording) { if (editor.vim.vimStateMachine.isRecording) {
KeyHandler.getInstance().modalEntryKeys += it KeyHandler.getInstance().modalEntryKeys += it
} }
} }
@@ -183,99 +173,38 @@ object VimExtensionFacade {
/** Returns a string typed in the input box similar to 'input()'. */ /** Returns a string typed in the input box similar to 'input()'. */
@JvmStatic @JvmStatic
fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String { public fun inputString(editor: Editor, prompt: String, finishOn: Char?): String {
return (injector.commandLine as ExEntryPanelService).inputString(editor.vim, context.vim, prompt, finishOn) ?: "" return service<CommandLineHelper>().inputString(editor.vim, prompt, finishOn) ?: ""
} }
/** Get the current contents of the given register similar to 'getreg()'. */ /** Get the current contents of the given register similar to 'getreg()'. */
@JvmStatic @JvmStatic
fun getRegister(register: Char): List<KeyStroke>? { public fun getRegister(register: Char): List<KeyStroke>? {
val reg = VimPlugin.getRegister().getRegister(register) ?: return null val reg = VimPlugin.getRegister().getRegister(register) ?: return null
return reg.keys return reg.keys
} }
@JvmStatic @JvmStatic
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? { public fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
val reg = injector.registerGroup.getRegister(register) ?: return null val reg = caret.registerStorage.getRegister(register) ?: return null
return reg.keys return reg.keys
} }
/** Set the current contents of the given register */ /** Set the current contents of the given register */
@JvmStatic @JvmStatic
fun setRegister(register: Char, keys: List<KeyStroke?>?) { public fun setRegister(register: Char, keys: List<KeyStroke?>?) {
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList()) VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList())
} }
/** Set the current contents of the given register */ /** Set the current contents of the given register */
@JvmStatic @JvmStatic
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) { public fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList()) caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
} }
/** Set the current contents of the given register */ /** Set the current contents of the given register */
@JvmStatic @JvmStatic
fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) { public fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type) VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type)
} }
@JvmStatic
fun exportScriptFunction(
scope: Scope?,
name: String,
args: List<String>,
defaultArgs: List<Pair<String, Expression>>,
hasOptionalArguments: Boolean,
flags: EnumSet<FunctionFlag>,
function: ScriptFunction
) {
var functionDeclaration: FunctionDeclaration? = null
val body = listOf(object : Executable {
// This context is set to the function declaration during initialisation and then set to the function execution
// context during execution
override lateinit var vimContext: VimLContext
override var rangeInScript: TextRange = TextRange(0, 0)
override fun execute(editor: VimEditor, context: ExecutionContext): ExecutionResult {
return function.execute(editor, context, functionDeclaration!!.functionVariables)
}
})
functionDeclaration = FunctionDeclaration(
scope,
name,
args,
defaultArgs,
body,
replaceExisting = true,
flags,
hasOptionalArguments
)
functionDeclaration.rangeInScript = TextRange(0, 0)
body.forEach { it.vimContext = functionDeclaration }
injector.functionService.storeFunction(functionDeclaration)
}
}
fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) {
exportScriptFunction(null, name, listOf("type"), emptyList(), false, noneOfEnum()) {
editor, context, args ->
val type = args["type"]?.asString()
val selectionType = when (type) {
"line" -> SelectionType.LINE_WISE
"block" -> SelectionType.BLOCK_WISE
"char" -> SelectionType.CHARACTER_WISE
else -> return@exportScriptFunction ExecutionResult.Error
}
if (function.apply(editor, context, selectionType)) {
ExecutionResult.Success
}
else {
ExecutionResult.Error
}
}
}
fun interface ScriptFunction {
fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
} }

View File

@@ -19,12 +19,12 @@ import com.maddyhome.idea.vim.newapi.ij
* COMPATIBILITY-LAYER: Created a class, renamed original class * COMPATIBILITY-LAYER: Created a class, renamed original class
* Please see: https://jb.gg/zo8n0r * Please see: https://jb.gg/zo8n0r
*/ */
interface VimExtensionHandler : ExtensionHandler { public interface VimExtensionHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
execute(editor.ij, context.ij) execute(editor.ij, context.ij)
} }
fun execute(editor: Editor, context: DataContext) public fun execute(editor: Editor, context: DataContext)
abstract class WithCallback : ExtensionHandler.WithCallback(), VimExtensionHandler public abstract class WithCallback : ExtensionHandler.WithCallback(), VimExtensionHandler
} }

View File

@@ -67,7 +67,7 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(option) { VimPlugin.getOptionGroup().addGlobalOptionChangeListener(option) {
if (injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(null)).asBoolean()) { if (injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(null)).asBoolean()) {
initExtension(extensionBean, name) initExtension(extensionBean, name)
PluginState.Util.enabledExtensions.add(name) PluginState.enabledExtensions.add(name)
} else { } else {
extensionBean.instance.dispose() extensionBean.instance.dispose()
} }

View File

@@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.extension.argtextobj; package com.maddyhome.idea.vim.extension.argtextobj;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.command.*; import com.maddyhome.idea.vim.command.*;
@@ -24,7 +23,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
import com.maddyhome.idea.vim.listener.VimListenerSuppressor; import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
import com.maddyhome.idea.vim.newapi.IjVimCaret; import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor; import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.state.KeyHandlerState; import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode; import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString; import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nls;
@@ -33,6 +32,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.EnumSet;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
@@ -63,8 +63,8 @@ public class VimArgTextObjExtension implements VimExtension {
*/ */
private static class BracketPairs { private static class BracketPairs {
// NOTE: brackets must match by the position, and ordered by rank (highest to lowest). // NOTE: brackets must match by the position, and ordered by rank (highest to lowest).
private final @NotNull String openBrackets; @NotNull private final String openBrackets;
private final @NotNull String closeBrackets; @NotNull private final String closeBrackets;
static class ParseException extends Exception { static class ParseException extends Exception {
public ParseException(@NotNull String message) { public ParseException(@NotNull String message) {
@@ -86,7 +86,8 @@ public class VimArgTextObjExtension implements VimExtension {
* @param bracketPairs comma-separated list of colon-separated bracket pairs. * @param bracketPairs comma-separated list of colon-separated bracket pairs.
* @throws ParseException if a syntax error is detected. * @throws ParseException if a syntax error is detected.
*/ */
static @NotNull BracketPairs fromBracketPairList(final @NotNull String bracketPairs) throws ParseException { @NotNull
static BracketPairs fromBracketPairList(@NotNull final String bracketPairs) throws ParseException {
StringBuilder openBrackets = new StringBuilder(); StringBuilder openBrackets = new StringBuilder();
StringBuilder closeBrackets = new StringBuilder(); StringBuilder closeBrackets = new StringBuilder();
ParseState state = ParseState.OPEN; ParseState state = ParseState.OPEN;
@@ -126,7 +127,7 @@ public class VimArgTextObjExtension implements VimExtension {
return new BracketPairs(openBrackets.toString(), closeBrackets.toString()); return new BracketPairs(openBrackets.toString(), closeBrackets.toString());
} }
BracketPairs(final @NotNull String openBrackets, final @NotNull String closeBrackets) { BracketPairs(@NotNull final String openBrackets, @NotNull final String closeBrackets) {
assert openBrackets.length() == closeBrackets.length(); assert openBrackets.length() == closeBrackets.length();
this.openBrackets = openBrackets; this.openBrackets = openBrackets;
this.closeBrackets = closeBrackets; this.closeBrackets = closeBrackets;
@@ -156,9 +157,10 @@ public class VimArgTextObjExtension implements VimExtension {
} }
} }
private static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")"); public static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")");
private static @Nullable String bracketPairsVariable() { @Nullable
private static String bracketPairsVariable() {
final Object value = VimPlugin.getVariableService().getGlobalVariableValue("argtextobj_pairs"); final Object value = VimPlugin.getVariableService().getGlobalVariableValue("argtextobj_pairs");
if (value instanceof VimString vimValue) { if (value instanceof VimString vimValue) {
return vimValue.getValue(); return vimValue.getValue();
@@ -189,12 +191,13 @@ public class VimArgTextObjExtension implements VimExtension {
this.isInner = isInner; this.isInner = isInner;
} }
@Nullable
@Override @Override
public @Nullable TextRange getRange(@NotNull VimEditor editor, public TextRange getRange(@NotNull VimEditor editor,
@NotNull ImmutableVimCaret caret, @NotNull ImmutableVimCaret caret,
@NotNull ExecutionContext context, @NotNull ExecutionContext context,
int count, int count,
int rawCount) { int rawCount) {
BracketPairs bracketPairs = DEFAULT_BRACKET_PAIRS; BracketPairs bracketPairs = DEFAULT_BRACKET_PAIRS;
final String bracketPairsVar = bracketPairsVariable(); final String bracketPairsVar = bracketPairsVariable();
if (bracketPairsVar != null) { if (bracketPairsVar != null) {
@@ -232,25 +235,28 @@ public class VimArgTextObjExtension implements VimExtension {
return new TextRange(finder.getLeftBound(), finder.getRightBound()); return new TextRange(finder.getLeftBound(), finder.getRightBound());
} }
@NotNull
@Override @Override
public @NotNull TextObjectVisualType getVisualType() { public TextObjectVisualType getVisualType() {
return TextObjectVisualType.CHARACTER_WISE; return TextObjectVisualType.CHARACTER_WISE;
} }
} }
@Override @Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) { public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
IjVimEditor vimEditor = (IjVimEditor) editor;
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner); final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
//noinspection DuplicatedCode //noinspection DuplicatedCode
if (!(editor.getMode() instanceof Mode.OP_PENDING)) { if (!vimStateMachine.isOperatorPending()) {
int count0 = operatorArguments.getCount0();
editor.nativeCarets().forEach((VimCaret caret) -> { editor.nativeCarets().forEach((VimCaret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, caret, context, Math.max(1, count0), count0); final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
if (range != null) { if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) { try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) { if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true); com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true);
} else { } else {
InlayHelperKt.moveToInlayAwareOffset(((IjVimCaret)caret).getCaret(), range.getStartOffset()); InlayHelperKt.moveToInlayAwareOffset(((IjVimCaret)caret).getCaret(), range.getStartOffset());
@@ -259,7 +265,8 @@ public class VimArgTextObjExtension implements VimExtension {
} }
}); });
} else { } else {
keyHandlerState.getCommandBuilder().addAction(textObjectHandler); vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class))));
} }
} }
} }
@@ -269,9 +276,9 @@ public class VimArgTextObjExtension implements VimExtension {
* position * position
*/ */
private static class ArgBoundsFinder { private static class ArgBoundsFinder {
private final @NotNull CharSequence text; @NotNull private final CharSequence text;
private final @NotNull Document document; @NotNull private final Document document;
private final @NotNull BracketPairs brackets; @NotNull private final BracketPairs brackets;
private int leftBound = Integer.MAX_VALUE; private int leftBound = Integer.MAX_VALUE;
private int rightBound = Integer.MIN_VALUE; private int rightBound = Integer.MIN_VALUE;
private int leftBracket; private int leftBracket;
@@ -298,7 +305,7 @@ public class VimArgTextObjExtension implements VimExtension {
* @param position starting position. * @param position starting position.
*/ */
boolean findBoundsAt(int position) throws IllegalStateException { boolean findBoundsAt(int position) throws IllegalStateException {
if (text.isEmpty()) { if (text.length() == 0) {
error = "empty document"; error = "empty document";
return false; return false;
} }

View File

@@ -18,55 +18,57 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.command.TextObjectVisualType import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.CommandAliasHandler import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.Range import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.ex.ranges.toTextRange
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
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.addCommand import com.maddyhome.idea.vim.extension.VimExtensionFacade.addCommand
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.handler.TextObjectActionHandler import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.PsiHelper import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import java.util.*
import com.maddyhome.idea.vim.state.mode.SelectionType
internal class CommentaryExtension : VimExtension { internal class CommentaryExtension : VimExtension {
object Util { companion object {
fun doCommentary( fun doCommentary(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
range: TextRange, range: TextRange,
selectionType: SelectionType, selectionType: SelectionType,
resetCaret: Boolean = true, resetCaret: Boolean,
): Boolean { ): Boolean {
val mode = editor.mode val mode = editor.vimStateMachine.mode
if (mode !is Mode.VISUAL) { if (mode !is Mode.VISUAL) {
editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset) editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset)
} }
return runWriteAction { 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. Be ready to fall back to 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)
} else { } else {
@@ -75,12 +77,11 @@ internal class CommentaryExtension : VimExtension {
val project = editor.ij.project!! val project = editor.ij.project!!
val callback = { afterCommenting(mode, editor, resetCaret, range) } val callback = { afterCommenting(mode, editor, resetCaret, range) }
actions.any { executeActionWithCallbackOnSuccess(editor, it, project, context, callback) } actions.any { executeActionWithCallbackOnSuccess(it, project, context, callback) }
} }
} }
private fun executeActionWithCallbackOnSuccess( private fun executeActionWithCallbackOnSuccess(
editor: VimEditor,
action: String, action: String,
project: Project, project: Project,
context: ExecutionContext, context: ExecutionContext,
@@ -89,7 +90,7 @@ internal class CommentaryExtension : VimExtension {
val res = Ref.create<Boolean>(false) val res = Ref.create<Boolean>(false)
AsyncActionExecutionService.getInstance(project).withExecutionAfterAction( AsyncActionExecutionService.getInstance(project).withExecutionAfterAction(
action, action,
{ res.set(injector.actionExecutor.executeAction(editor, name = action, context = context)) }, { res.set(injector.actionExecutor.executeAction(action, context)) },
{ if (res.get()) callback() }) { if (res.get()) callback() })
return res.get() return res.get()
} }
@@ -112,17 +113,12 @@ internal class CommentaryExtension : VimExtension {
// first non-whitespace character, then the caret is in the right place. If it's inserted at the first column, // first non-whitespace character, then the caret is in the right place. If it's inserted at the first column,
// then the caret is now in a bit of a weird place. We can't detect this scenario, so we just have to accept // then the caret is now in a bit of a weird place. We can't detect this scenario, so we just have to accept
// the difference // the difference
// TODO: If we don't move the caret to the start offset, we should maintain the current logical position
if (resetCaret) { if (resetCaret) {
editor.primaryCaret().moveToOffset(range.startOffset) editor.primaryCaret().moveToOffset(range.startOffset)
} }
} }
} }
companion object {
private const val OPERATOR_FUNC = "CommentaryOperatorFunc"
}
override fun getName() = "commentary" override fun getName() = "commentary"
override fun init() { override fun init() {
@@ -149,16 +145,6 @@ internal class CommentaryExtension : VimExtension {
putKeyMapping(MappingMode.N, injector.parser.parseKeys("<Plug>(CommentLine)"), owner, plugCommentaryLineKeys, true) putKeyMapping(MappingMode.N, injector.parser.parseKeys("<Plug>(CommentLine)"), owner, plugCommentaryLineKeys, true)
addCommand("Commentary", CommentaryCommandAliasHandler()) addCommand("Commentary", CommentaryCommandAliasHandler())
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, CommentaryOperatorFunction())
}
private class CommentaryOperatorFunction : OperatorFunction {
// todo make it multicaret
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
return Util.doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
}
} }
/** /**
@@ -167,21 +153,29 @@ internal class CommentaryExtension : VimExtension {
* E.g. handles the `gc` in `gc_`, by setting the operator function, then invoking `g@` to receive the `_` motion to * E.g. handles the `gc` in `gc_`, by setting the operator function, then invoking `g@` to receive the `_` motion to
* invoke the operator. This object is both the mapping handler and the operator function. * invoke the operator. This object is both the mapping handler and the operator function.
*/ */
private class CommentaryOperatorHandler : ExtensionHandler { private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC setOperatorFunction(this)
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
} }
// todo make it multicaret
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
return doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
}
} }
private class CommentaryMappingHandler : ExtensionHandler { private class CommentaryMappingHandler : ExtensionHandler {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val keyState = KeyHandler.getInstance().keyHandlerState val commandState = editor.vimStateMachine
keyState.commandBuilder.addAction(CommentaryTextObjectMotionHandler)
val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
commandState.commandBuilder.completeCommandPart(Argument(command))
} }
} }
@@ -244,14 +238,8 @@ internal class CommentaryExtension : VimExtension {
* Used like `:1,3Commentary` or `g/fun/Commentary` * Used like `:1,3Commentary` or `g/fun/Commentary`
*/ */
private class CommentaryCommandAliasHandler : CommandAliasHandler { private class CommentaryCommandAliasHandler : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) { override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
Util.doCommentary( doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false)
editor,
context,
range.getLineRange(editor, editor.primaryCaret()).toTextRange(editor),
SelectionType.LINE_WISE,
false
)
} }
} }
} }

View File

@@ -19,22 +19,24 @@ import com.intellij.openapi.util.Key
import com.maddyhome.idea.vim.api.ExecutionContext 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.getOffset import com.maddyhome.idea.vim.api.getOffset
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.api.setChangeMarks import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
import com.maddyhome.idea.vim.state.mode.selectionType
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
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.helper.fileSize import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
@@ -43,8 +45,6 @@ import com.maddyhome.idea.vim.mark.VimMarkConstants
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
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
/** /**
@@ -72,13 +72,30 @@ internal class VimExchangeExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("X"), owner, injector.parser.parseKeys(EXCHANGE_CMD), true) putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("X"), owner, injector.parser.parseKeys(EXCHANGE_CMD), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxc"), owner, injector.parser.parseKeys(EXCHANGE_CLEAR_CMD), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxc"), owner, injector.parser.parseKeys(EXCHANGE_CLEAR_CMD), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxx"), owner, injector.parser.parseKeys(EXCHANGE_LINE_CMD), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxx"), owner, injector.parser.parseKeys(EXCHANGE_LINE_CMD), true)
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
} }
object Util { companion object {
@NonNls
const val EXCHANGE_CMD = "<Plug>(Exchange)"
@NonNls
const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
@NonNls
const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
val EXCHANGE_KEY = Key<Exchange>("exchange") val EXCHANGE_KEY = Key<Exchange>("exchange")
// End mark has always greater of eq offset than start mark
class Exchange(val type: SelectionType, val start: Mark, val end: Mark, val text: String) {
private var myHighlighter: RangeHighlighter? = null
fun setHighlighter(highlighter: RangeHighlighter) {
myHighlighter = highlighter
}
fun getHighlighter(): RangeHighlighter? = myHighlighter
}
fun clearExchange(editor: Editor) { fun clearExchange(editor: Editor) {
editor.getUserData(EXCHANGE_KEY)?.getHighlighter()?.let { editor.getUserData(EXCHANGE_KEY)?.getHighlighter()?.let {
editor.markupModel.removeHighlighter(it) editor.markupModel.removeHighlighter(it)
@@ -87,25 +104,18 @@ internal class VimExchangeExtension : VimExtension {
} }
} }
companion object {
@NonNls private const val EXCHANGE_CMD = "<Plug>(Exchange)"
@NonNls private const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
@NonNls private const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
@NonNls private const val OPERATOR_FUNC = "ExchangeOperatorFunc"
}
private class ExchangeHandler(private val isLine: Boolean) : ExtensionHandler { private class ExchangeHandler(private val isLine: Boolean) : ExtensionHandler {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC setOperatorFunction(Operator(false))
executeNormalWithoutMapping(injector.parser.parseKeys(if (isLine) "g@_" else "g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys(if (isLine) "g@_" else "g@"), editor.ij)
} }
} }
private class ExchangeClearHandler : ExtensionHandler { private class ExchangeClearHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
Util.clearExchange(editor.ij) clearExchange(editor.ij)
} }
} }
@@ -115,12 +125,12 @@ internal class VimExchangeExtension : VimExtension {
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 ?: CHARACTER_WISE)
} }
} }
} }
private class Operator(private val isVisual: Boolean = false) : OperatorFunction { private class Operator(private val isVisual: Boolean) : 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)
fun SelectionType.getString() = when (this) { fun SelectionType.getString() = when (this) {
SelectionType.CHARACTER_WISE -> "v" SelectionType.CHARACTER_WISE -> "v"
@@ -138,7 +148,7 @@ internal class VimExchangeExtension : VimExtension {
else -> HighlighterTargetArea.EXACT_RANGE else -> HighlighterTargetArea.EXACT_RANGE
} }
val isVisualLine = ex.type == SelectionType.LINE_WISE val isVisualLine = ex.type == SelectionType.LINE_WISE
val endAdj = if (!(isVisualLine) && (hlArea == HighlighterTargetArea.EXACT_RANGE || isVisual)) 1 else 0 val endAdj = if (!(isVisualLine) && (hlArea == HighlighterTargetArea.EXACT_RANGE || (isVisual))) 1 else 0
return ijEditor.markupModel.addRangeHighlighter( return ijEditor.markupModel.addRangeHighlighter(
ijEditor.getMarkOffset(ex.start), ijEditor.getMarkOffset(ex.start),
(ijEditor.getMarkOffset(ex.end) + endAdj).coerceAtMost(ijEditor.fileSize), (ijEditor.getMarkOffset(ex.end) + endAdj).coerceAtMost(ijEditor.fileSize),
@@ -148,12 +158,12 @@ internal class VimExchangeExtension : VimExtension {
) )
} }
val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: SelectionType.CHARACTER_WISE) val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: CHARACTER_WISE)
val exchange1 = ijEditor.getUserData(Util.EXCHANGE_KEY) val exchange1 = ijEditor.getUserData(EXCHANGE_KEY)
if (exchange1 == null) { if (exchange1 == null) {
val highlighter = highlightExchange(currentExchange) val highlighter = highlightExchange(currentExchange)
currentExchange.setHighlighter(highlighter) currentExchange.setHighlighter(highlighter)
ijEditor.putUserData(Util.EXCHANGE_KEY, currentExchange) ijEditor.putUserData(EXCHANGE_KEY, currentExchange)
return true return true
} else { } else {
val cmp = compareExchanges(exchange1, currentExchange) val cmp = compareExchanges(exchange1, currentExchange)
@@ -179,7 +189,7 @@ internal class VimExchangeExtension : VimExtension {
} }
} }
exchange(ijEditor, ex1, ex2, reverse, expand) exchange(ijEditor, ex1, ex2, reverse, expand)
Util.clearExchange(ijEditor) clearExchange(ijEditor)
return true return true
} }
} }
@@ -344,14 +354,4 @@ internal class VimExchangeExtension : VimExtension {
} }
} }
} }
// End mark has always greater of eq offset than start mark
class Exchange(val type: SelectionType, val start: Mark, val end: Mark, val text: String) {
private var myHighlighter: RangeHighlighter? = null
fun setHighlighter(highlighter: RangeHighlighter) {
myHighlighter = highlighter
}
fun getHighlighter(): RangeHighlighter? = myHighlighter
}
} }

View File

@@ -10,7 +10,7 @@ package com.maddyhome.idea.vim.extension.highlightedyank
import com.intellij.ide.ui.LafManager import com.intellij.ide.ui.LafManager
import com.intellij.ide.ui.LafManagerListener import com.intellij.ide.ui.LafManagerListener
import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.markup.EffectType import com.intellij.openapi.editor.markup.EffectType
@@ -19,26 +19,25 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimProjectService
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.VimYankListener
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.VimNlsSafe import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.listener.VimYankListener
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
internal const val DEFAULT_HIGHLIGHT_DURATION: Int = 300 internal const val DEFAULT_HIGHLIGHT_DURATION: Long = 300
@NonNls @NonNls
private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_duration" private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_duration"
@@ -77,131 +76,113 @@ internal class HighlightColorResetter : LafManagerListener {
* *
* When a new text is yanked or user starts editing, the old highlighting would be deleted. * When a new text is yanked or user starts editing, the old highlighting would be deleted.
*/ */
internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeListener { internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
private val highlightHandler = HighlightHandler() private val highlightHandler = HighlightHandler()
private var initialised = false
override fun getName() = "highlightedyank" override fun getName() = "highlightedyank"
override fun init() { override fun init() {
// Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called VimPlugin.getYank().addListener(this)
injector.listenersNotifier.modeChangeListeners.add(this) VimPlugin.getChange().addInsertListener(this)
injector.listenersNotifier.yankListeners.add(this)
// Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when
// IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the
// _extension_ is disabled, make sure we're properly initialised each time we're called.
registerIdeaVimDisabledCallback()
initialised = true
}
private fun registerIdeaVimDisabledCallback() {
// TODO: IdeaVim should help with lifecycle management here - VIM-3419
// IdeaVim should possibly unregister extensions, but it would also need to re-register them. We have to do this
// state management manually for now
Disposer.register(VimPlugin.getInstance().onOffDisposable) {
highlightHandler.clearYankHighlighters()
initialised = false
}
} }
override fun dispose() { override fun dispose() {
// Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads VimPlugin.getYank().removeListener(this)
injector.listenersNotifier.modeChangeListeners.remove(this) VimPlugin.getChange().removeInsertListener(this)
injector.listenersNotifier.yankListeners.remove(this)
highlightHandler.clearYankHighlighters()
initialised = false
} }
override fun yankPerformed(editor: VimEditor, range: TextRange) { override fun yankPerformed(editor: VimEditor, range: TextRange) {
ensureInitialised()
highlightHandler.highlightYankRange(editor.ij, range) highlightHandler.highlightYankRange(editor.ij, range)
} }
override fun modeChanged(editor: VimEditor, oldMode: Mode) { override fun insertModeStarted(editor: Editor) {
if (editor.mode !is Mode.INSERT) return highlightHandler.clearAllYankHighlighters()
ensureInitialised()
highlightHandler.clearYankHighlighters()
}
private fun ensureInitialised() {
if (!initialised) {
registerIdeaVimDisabledCallback()
initialised = true
}
} }
private class HighlightHandler { private class HighlightHandler {
private val alarm = Alarm(ThreadToUse.SWING_THREAD) private var editor: Editor? = null
private var lastEditor: Editor? = null private val yankHighlighters: MutableSet<RangeHighlighter> = mutableSetOf()
private val highlighters = mutableSetOf<RangeHighlighter>()
fun highlightYankRange(editor: Editor, range: TextRange) { fun highlightYankRange(editor: Editor, range: TextRange) {
// from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted // from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted
clearYankHighlighters() clearAllYankHighlighters()
lastEditor = editor this.editor = editor
val project = editor.project
val attributes = getHighlightTextAttributes(editor) if (project != null) {
for (i in 0 until range.size()) { Disposer.register(
val highlighter = editor.markupModel.addRangeHighlighter( VimProjectService.getInstance(project),
range.startOffsets[i], ) {
range.endOffsets[i], this.editor = null
HighlighterLayer.SELECTION, yankHighlighters.clear()
attributes,
HighlighterTargetArea.EXACT_RANGE,
)
highlighters.add(highlighter)
}
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
val timeout = extractUsersHighlightDuration()
if (timeout >= 0) {
// Note modality. This is important when highlighting an editor when a modal dialog is open, such as the resolve
// conflict diff view
alarm.addRequest(
{ clearYankHighlighters() },
timeout,
ModalityState.any()
)
}
}
fun clearYankHighlighters() {
alarm.cancelAllRequests()
// Make sure the last editor we saved is still alive before we use it. We can't just use the list of open editors
// because this list is empty when IdeaVim is disabled, so we're unable to clean up
lastEditor?.let { editor ->
if (!editor.isDisposed) {
highlighters.forEach { highlighter -> editor.markupModel.removeHighlighter(highlighter) }
} }
} }
lastEditor = null
highlighters.clear() if (range.isMultiple) {
for (i in 0 until range.size()) {
highlightSingleRange(editor, range.startOffsets[i]..range.endOffsets[i])
}
} else {
highlightSingleRange(editor, range.startOffset..range.endOffset)
}
} }
private fun getHighlightTextAttributes(editor: Editor) = TextAttributes( fun clearAllYankHighlighters() {
yankHighlighters.forEach { highlighter ->
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
}
yankHighlighters.clear()
}
private fun highlightSingleRange(editor: Editor, range: ClosedRange<Int>) {
val highlighter = editor.markupModel.addRangeHighlighter(
range.start,
range.endInclusive,
HighlighterLayer.SELECTION,
getHighlightTextAttributes(),
HighlighterTargetArea.EXACT_RANGE,
)
yankHighlighters.add(highlighter)
setClearHighlightRangeTimer(highlighter)
}
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
val timeout = extractUsersHighlightDuration()
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
if (timeout >= 0) {
Executors.newSingleThreadScheduledExecutor().schedule(
{
ApplicationManager.getApplication().invokeLater {
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
}
},
timeout,
TimeUnit.MILLISECONDS,
)
}
}
private fun getHighlightTextAttributes() = TextAttributes(
null, null,
extractUsersHighlightColor(), extractUsersHighlightColor(),
editor.colorsScheme.getColor(EditorColors.CARET_COLOR), editor?.colorsScheme?.getColor(EditorColors.CARET_COLOR),
EffectType.SEARCH_MATCH, EffectType.SEARCH_MATCH,
Font.PLAIN, Font.PLAIN,
) )
private fun extractUsersHighlightDuration(): Int { private fun extractUsersHighlightDuration(): Long {
return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) { return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) {
// toVimNumber will return 0 for an invalid string. Let's force it to throw it.toLong()
when (it) {
is VimString -> it.value.toInt()
else -> it.toVimNumber().value
}
} }
} }
private fun extractUsersHighlightColor(): Color { private fun extractUsersHighlightColor(): Color {
return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value -> return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value ->
val rgba = value.asString() val rgba = value
.substring(4) .substring(4)
.filter { it != '(' && it != ')' && !it.isWhitespace() } .filter { it != '(' && it != ')' && !it.isWhitespace() }
.split(',') .split(',')
@@ -211,11 +192,12 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
} }
} }
private fun <T> extractVariable(variable: String, default: T, extractFun: (value: VimDataType) -> T): T { private fun <T> extractVariable(variable: String, default: T, extractFun: (value: String) -> T): T {
val value = VimPlugin.getVariableService().getGlobalVariableValue(variable) val value = VimPlugin.getVariableService().getGlobalVariableValue(variable)
if (value != null) {
if (value is VimString) {
return try { return try {
extractFun(value) extractFun(value.value)
} catch (e: Exception) { } catch (e: Exception) {
@VimNlsSafe val message = MessageHelper.message( @VimNlsSafe val message = MessageHelper.message(
"highlightedyank.invalid.value.of.0.1", "highlightedyank.invalid.value.of.0.1",

View File

@@ -14,7 +14,6 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiComment import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
@@ -41,10 +40,10 @@ import com.maddyhome.idea.vim.handler.toMotionOrError
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.PsiHelper import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -92,31 +91,35 @@ internal class Matchit : VimExtension {
private class MatchitHandler(private val reverse: Boolean) : ExtensionHandler { private class MatchitHandler(private val reverse: Boolean) : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val keyHandler = KeyHandler.getInstance() val commandState = editor.vimStateMachine
val keyState = keyHandler.keyHandlerState val count = commandState.commandBuilder.count
// Reset the command count so it doesn't transfer onto subsequent commands. // Reset the command count so it doesn't transfer onto subsequent commands.
keyState.commandBuilder.resetCount() editor.vimStateMachine.commandBuilder.resetCount()
// Normally we want to jump to the start of the matching pair. But when moving forward in operator // Normally we want to jump to the start of the matching pair. But when moving forward in operator
// pending mode, we want to include the entire match. isInOpPending makes that distinction. // pending mode, we want to include the entire match. isInOpPending makes that distinction.
if (editor.mode is Mode.OP_PENDING) { val isInOpPending = commandState.isOperatorPending
if (isInOpPending) {
val matchitAction = MatchitAction() val matchitAction = MatchitAction()
matchitAction.reverse = reverse matchitAction.reverse = reverse
matchitAction.isInOpPending = true matchitAction.isInOpPending = true
keyState.commandBuilder.addAction(matchitAction) commandState.commandBuilder.completeCommandPart(
Argument(
Command(
count,
matchitAction,
Command.Type.MOTION,
EnumSet.noneOf(CommandFlags::class.java),
),
),
)
} else { } else {
editor.sortedCarets().forEach { caret -> editor.sortedCarets().forEach { caret ->
injector.jumpService.saveJumpLocation(editor) injector.jumpService.saveJumpLocation(editor)
caret.moveToOffset( caret.moveToOffset(getMatchitOffset(editor.ij, caret.ij, count, isInOpPending, reverse))
getMatchitOffset(
editor.ij,
caret.ij,
operatorArguments.count0,
isInOpPending = false,
reverse
))
} }
} }
} }
@@ -350,7 +353,7 @@ private object FileTypePatterns {
private val DEFAULT_PAIRS = setOf('(', ')', '[', ']', '{', '}') private val DEFAULT_PAIRS = setOf('(', ')', '[', ']', '{', '}')
private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPending: Boolean, reverse: Boolean): Int { private fun getMatchitOffset(editor: Editor, caret: Caret, count: Int, isInOpPending: Boolean, reverse: Boolean): Int {
val virtualFile = EditorHelper.getVirtualFile(editor) val virtualFile = EditorHelper.getVirtualFile(editor)
var caretOffset = caret.offset var caretOffset = caret.offset
@@ -363,9 +366,9 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPe
val currentChar = editor.document.charsSequence[caretOffset] val currentChar = editor.document.charsSequence[caretOffset]
var motionOffset: Int? = null var motionOffset: Int? = null
if (count0 > 0) { if (count > 0) {
// Matchit doesn't affect the percent motion, so we fall back to the default behavior. // Matchit doesn't affect the percent motion, so we fall back to the default behavior.
motionOffset = VimPlugin.getMotion().moveCaretToLinePercent(editor.vim, caret.vim, count0) motionOffset = VimPlugin.getMotion().moveCaretToLinePercent(editor.vim, caret.vim, count)
} else { } else {
// Check the simplest case first. // Check the simplest case first.
if (DEFAULT_PAIRS.contains(currentChar)) { if (DEFAULT_PAIRS.contains(currentChar)) {
@@ -396,7 +399,8 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPe
private fun getMotionOffset(motion: Motion): Int? { private fun getMotionOffset(motion: Motion): Int? {
return when (motion) { return when (motion) {
is Motion.AdjustedOffset, is Motion.AbsoluteOffset -> motion.offset is Motion.AbsoluteOffset -> motion.offset
is Motion.AdjustedOffset -> motion.offset
is Motion.Error, is Motion.NoMotion -> null is Motion.Error, is Motion.NoMotion -> null
} }
} }

View File

@@ -30,11 +30,11 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.group.visual.vimSetSelection import com.maddyhome.idea.vim.group.visual.vimSetSelection
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.SearchHelper
import com.maddyhome.idea.vim.helper.SearchOptions import com.maddyhome.idea.vim.helper.SearchOptions
import com.maddyhome.idea.vim.helper.endOffsetInclusive import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.findWordUnderCursor
import com.maddyhome.idea.vim.helper.inVisualMode import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.userData import com.maddyhome.idea.vim.helper.userData
@@ -235,7 +235,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
val text = if (editor.inVisualMode) { val text = if (editor.inVisualMode) {
primaryCaret.selectedText ?: return primaryCaret.selectedText ?: return
} else { } else {
val range = findWordUnderCursor(editor, primaryCaret) ?: return val range = SearchHelper.findWordUnderCursor(editor, primaryCaret) ?: return
if (range.startOffset > primaryCaret.offset) return if (range.startOffset > primaryCaret.offset) return
IjVimEditor(editor).getText(range) IjVimEditor(editor).getText(range)
} }
@@ -300,7 +300,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
} }
private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? { private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? {
val range = findWordUnderCursor(editor, caret) ?: return null val range = SearchHelper.findWordUnderCursor(editor, caret) ?: return null
if (range.startOffset > caret.offset) return null if (range.startOffset > caret.offset) return null
enterVisualMode(editor.vim) enterVisualMode(editor.vim)
@@ -327,6 +327,6 @@ internal class VimMultipleCursorsExtension : VimExtension {
private fun makePattern(text: String, whole: Boolean): String { private fun makePattern(text: String, whole: Boolean): String {
// Pattern is "very nomagic" (ignore regex chars) and "force case sensitive". This is vim-multiple-cursors behaviour // Pattern is "very nomagic" (ignore regex chars) and "force case sensitive". This is vim-multiple-cursors behaviour
return "\\V\\C" + if (whole) "\\<$text\\>" else text return "\\V\\C" + SearchHelper.makeSearchPattern(text, whole)
} }
} }

View File

@@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.CommandAlias import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.ex.ranges.Range import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.group.KeyGroup import com.maddyhome.idea.vim.group.KeyGroup
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
@@ -130,32 +130,32 @@ internal class NerdTree : VimExtension {
addCommand("NERDTreeFind", IjCommandHandler("SelectInProjectView")) addCommand("NERDTreeFind", IjCommandHandler("SelectInProjectView"))
addCommand("NERDTreeRefreshRoot", IjCommandHandler("Synchronize")) addCommand("NERDTreeRefreshRoot", IjCommandHandler("Synchronize"))
synchronized(Util.monitor) { synchronized(monitor) {
Util.commandsRegistered = true commandsRegistered = true
ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) } ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
} }
} }
class IjCommandHandler(private val actionId: String) : CommandAliasHandler { class IjCommandHandler(private val actionId: String) : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) { override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
Util.callAction(editor, actionId, context) callAction(editor, actionId, context)
} }
} }
class ToggleHandler : CommandAliasHandler { class ToggleHandler : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) { override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
val project = editor.ij.project ?: return val project = editor.ij.project ?: return
val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
if (toolWindow.isVisible) { if (toolWindow.isVisible) {
toolWindow.hide() toolWindow.hide()
} else { } else {
Util.callAction(editor, "ActivateProjectToolWindow", context) callAction(editor, "ActivateProjectToolWindow", context)
} }
} }
} }
class CloseHandler : CommandAliasHandler { class CloseHandler : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) { override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
val project = editor.ij.project ?: return val project = editor.ij.project ?: return
val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
if (toolWindow.isVisible) { if (toolWindow.isVisible) {
@@ -187,8 +187,8 @@ internal class NerdTree : VimExtension {
// TODO I'm not sure is this activity runs at all? Should we use [RunOnceUtil] instead? // TODO I'm not sure is this activity runs at all? Should we use [RunOnceUtil] instead?
class NerdStartupActivity : ProjectActivity { class NerdStartupActivity : ProjectActivity {
override suspend fun execute(project: Project) { override suspend fun execute(project: Project) {
synchronized(Util.monitor) { synchronized(monitor) {
if (!Util.commandsRegistered) return if (!commandsRegistered) return
installDispatcher(project) installDispatcher(project)
} }
} }
@@ -214,7 +214,7 @@ internal class NerdTree : VimExtension {
val action = nextNode.actionHolder val action = nextNode.actionHolder
when (action) { when (action) {
is NerdAction.ToIj -> Util.callAction(null, action.name, e.dataContext.vim) is NerdAction.ToIj -> callAction(null, action.name, e.dataContext.vim)
is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) } is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) }
} }
} }
@@ -356,7 +356,7 @@ internal class NerdTree : VimExtension {
currentWindow?.split(SwingConstants.VERTICAL, true, file, true) currentWindow?.split(SwingConstants.VERTICAL, true, file, true)
// FIXME: 22.01.2021 This solution bouncing a bit // FIXME: 22.01.2021 This solution bouncing a bit
Util.callAction(null, "ActivateProjectToolWindow", context.vim) callAction(null, "ActivateProjectToolWindow", context.vim)
}, },
) )
registerCommand( registerCommand(
@@ -368,7 +368,7 @@ internal class NerdTree : VimExtension {
val currentWindow = splitters.currentWindow val currentWindow = splitters.currentWindow
currentWindow?.split(SwingConstants.HORIZONTAL, true, file, true) currentWindow?.split(SwingConstants.HORIZONTAL, true, file, true)
Util.callAction(null, "ActivateProjectToolWindow", context.vim) callAction(null, "ActivateProjectToolWindow", context.vim)
}, },
) )
registerCommand( registerCommand(
@@ -477,13 +477,6 @@ internal class NerdTree : VimExtension {
"r", "r",
NerdAction.ToIj("SynchronizeCurrentFile"), NerdAction.ToIj("SynchronizeCurrentFile"),
) )
registerCommand("NERDTreeMapToggleHidden", "I", NerdAction.ToIj("ProjectView.ShowExcludedFiles"))
registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile"))
registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir"))
registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete"))
registerCommand("NERDTreeMapCopy", "y", NerdAction.ToIj("\$Copy"))
registerCommand("NERDTreeMapPaste", "v", NerdAction.ToIj("\$Paste"))
registerCommand("NERDTreeMapRename", "<C-r>", NerdAction.ToIj("RenameElement"))
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize")) registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu")) registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow")) registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
@@ -509,11 +502,23 @@ internal class NerdTree : VimExtension {
} }
}, },
) )
for (c in ('a'..'z') + ('A'..'Z')) {
val ks = KeyStroke.getKeyStroke(c)
if (ks !in actionsRoot) {
registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
}
}
} }
object Util { companion object {
const val pluginName = "NERDTree"
internal val monitor = Any() internal val monitor = Any()
internal var commandsRegistered = false internal var commandsRegistered = false
private val LOG = logger<NerdTree>()
fun callAction(editor: VimEditor?, name: String, context: ExecutionContext) { fun callAction(editor: VimEditor?, name: String, context: ExecutionContext) {
val action = ActionManager.getInstance().getAction(name) ?: run { val action = ActionManager.getInstance().getAction(name) ?: run {
VimPlugin.showMessage(MessageHelper.message("action.not.found.0", name)) VimPlugin.showMessage(MessageHelper.message("action.not.found.0", name))
@@ -528,52 +533,45 @@ internal class NerdTree : VimExtension {
} }
} }
} }
}
companion object { private fun addCommand(alias: String, handler: CommandAliasHandler) {
const val pluginName = "NERDTree" VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
private val LOG = logger<NerdTree>() }
private fun registerCommand(variable: String, default: String, action: NerdAction) {
val variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable)
val mappings = if (variableValue is VimString) {
variableValue.value
} else {
default
}
actionsRoot.addLeafs(mappings, action)
}
private fun registerCommand(default: String, action: NerdAction) {
actionsRoot.addLeafs(default, action)
}
private val actionsRoot: RootNode<NerdAction> = RootNode()
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
return if (node is CommandPartNode<NerdAction>) {
val res = node.keys.toMutableSet()
res += node.values.map { collectShortcuts(it) }.flatten()
res
} else {
emptySet()
}
}
private fun installDispatcher(project: Project) {
val dispatcher = NerdDispatcher.getInstance(project)
val shortcuts = collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(pluginName)) }
dispatcher.registerCustomShortcutSet(
KeyGroup.toShortcutSet(shortcuts),
(ProjectView.getInstance(project) as ProjectViewImpl).component,
)
}
} }
} }
private fun addCommand(alias: String, handler: CommandAliasHandler) {
VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
}
private fun registerCommand(variable: String, default: String, action: NerdAction) {
val variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable)
val mappings = if (variableValue is VimString) {
variableValue.value
} else {
default
}
actionsRoot.addLeafs(mappings, action)
}
private fun registerCommand(default: String, action: NerdAction) {
actionsRoot.addLeafs(default, action)
}
private val actionsRoot: RootNode<NerdAction> = RootNode("NERDTree")
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
return if (node is CommandPartNode<NerdAction>) {
val res = node.children.keys.toMutableSet()
res += node.children.values.map { collectShortcuts(it) }.flatten()
res
} else {
emptySet()
}
}
private fun installDispatcher(project: Project) {
val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
val shortcuts =
collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
dispatcher.registerCustomShortcutSet(
KeyGroup.toShortcutSet(shortcuts),
(ProjectView.getInstance(project) as ProjectViewImpl).component,
)
}

View File

@@ -9,11 +9,10 @@
package com.maddyhome.idea.vim.extension.paragraphmotion package com.maddyhome.idea.vim.extension.paragraphmotion
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
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.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
@@ -21,10 +20,8 @@ import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.helper.vimForEachCaret import com.maddyhome.idea.vim.helper.vimForEachCaret
import com.maddyhome.idea.vim.key.MappingOwner
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 javax.swing.KeyStroke
internal class ParagraphMotion : VimExtension { internal class ParagraphMotion : VimExtension {
override fun getName(): String = "vim-paragraph-motion" override fun getName(): String = "vim-paragraph-motion"
@@ -33,8 +30,8 @@ internal class ParagraphMotion : VimExtension {
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), owner, ParagraphMotionHandler(1), false) VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), owner, ParagraphMotionHandler(1), false)
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), owner, ParagraphMotionHandler(-1), false) VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), owner, ParagraphMotionHandler(-1), false)
putKeyMappingIfMissingFromAndToKeys(MappingMode.NXO, injector.parser.parseKeys("}"), owner, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), true) putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("}"), owner, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), true)
putKeyMappingIfMissingFromAndToKeys(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true) putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true)
} }
private class ParagraphMotionHandler(private val count: Int) : ExtensionHandler { private class ParagraphMotionHandler(private val count: Int) : ExtensionHandler {
@@ -48,21 +45,7 @@ internal class ParagraphMotion : VimExtension {
} }
fun moveCaretToNextParagraph(editor: VimEditor, caret: Caret, count: Int): Int? { fun moveCaretToNextParagraph(editor: VimEditor, caret: Caret, count: Int): Int? {
return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true) return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true)?.let(editor::getLineEndForOffset)
?.let { editor.normalizeOffset(it, true) }
} }
} }
// For VIM-3306
@Suppress("SameParameterValue")
private fun putKeyMappingIfMissingFromAndToKeys(
modes: Set<MappingMode>,
fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner,
toKeys: List<KeyStroke>,
recursive: Boolean,
) {
val filteredModes = modes.filterNotTo(HashSet()) { VimPlugin.getKey().hasmapfrom(it, fromKeys) }
putKeyMappingIfMissing(filteredModes, fromKeys, pluginOwner, toKeys, recursive)
}
} }

View File

@@ -8,36 +8,37 @@
package com.maddyhome.idea.vim.extension.replacewithregister package com.maddyhome.idea.vim.extension.replacewithregister
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
import com.maddyhome.idea.vim.state.mode.isLine
import com.maddyhome.idea.vim.state.mode.selectionType
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
import com.maddyhome.idea.vim.extension.VimExtensionFacade import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper 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.SelectionType
import com.maddyhome.idea.vim.state.mode.isLine
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
internal class ReplaceWithRegister : VimExtension { internal class ReplaceWithRegister : VimExtension {
@@ -52,19 +53,17 @@ internal class ReplaceWithRegister : VimExtension {
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_OPERATOR), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_OPERATOR), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("grr"), owner, injector.parser.parseKeys(RWR_LINE), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("grr"), owner, injector.parser.parseKeys(RWR_LINE), true)
putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_VISUAL), true) putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_VISUAL), true)
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
} }
private class RwrVisual : ExtensionHandler { private class RwrVisual : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val typeInEditor = editor.mode.selectionType ?: SelectionType.CHARACTER_WISE val typeInEditor = editor.mode.selectionType ?: CHARACTER_WISE
editor.sortedCarets().forEach { caret -> editor.sortedCarets().forEach { caret ->
val selectionStart = caret.selectionStart val selectionStart = caret.selectionStart
val selectionEnd = caret.selectionEnd val selectionEnd = caret.selectionEnd
val visualSelection = caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor) val visualSelection = caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor)
doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor)) doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor))
} }
editor.exitVisualMode() editor.exitVisualMode()
} }
@@ -74,7 +73,7 @@ internal class ReplaceWithRegister : VimExtension {
override val isRepeatable: Boolean = true override val isRepeatable: Boolean = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC setOperatorFunction(Operator())
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
} }
} }
@@ -92,7 +91,7 @@ internal class ReplaceWithRegister : VimExtension {
val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor) val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor)
caretsAndSelections += visualSelection caretsAndSelections += visualSelection
doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE)) doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE))
} }
editor.sortedCarets().forEach { caret -> editor.sortedCarets().forEach { caret ->
@@ -113,14 +112,14 @@ internal class ReplaceWithRegister : VimExtension {
editor.primaryCaret() to VimSelection.create( editor.primaryCaret() to VimSelection.create(
range.startOffset, range.startOffset,
range.endOffset - 1, range.endOffset - 1,
selectionType ?: SelectionType.CHARACTER_WISE, selectionType ?: CHARACTER_WISE,
editor, editor,
), ),
), ),
selectionType ?: SelectionType.CHARACTER_WISE, selectionType ?: CHARACTER_WISE,
) )
// todo multicaret // todo multicaret
doReplace(ijEditor, context.ij, editor.primaryCaret(), visualSelection) doReplace(ijEditor, editor.primaryCaret(), visualSelection)
return true return true
} }
@@ -133,44 +132,52 @@ internal class ReplaceWithRegister : VimExtension {
} }
companion object { companion object {
@NonNls private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator" @NonNls
@NonNls private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine" private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
@NonNls private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
@NonNls private const val OPERATOR_FUNC = "ReplaceWithRegisterOperatorFunc" @NonNls
} private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine"
}
@NonNls
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) { private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
val registerGroup = injector.registerGroup
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret() private fun doReplace(editor: Editor, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
val savedRegister = registerGroup.getRegister(lastRegisterChar) ?: return val registerGroup = injector.registerGroup
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
var usedType = savedRegister.type val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
var usedText = savedRegister.text
if (usedType.isLine && usedText?.endsWith('\n') == true) { var usedType = savedRegister.type
// Code from original plugin implementation. Correct text for linewise selected text var usedText = savedRegister.text
usedText = usedText.dropLast(1) if (usedType.isLine && usedText?.endsWith('\n') == true) {
usedType = SelectionType.CHARACTER_WISE // Code from original plugin implementation. Correct text for linewise selected text
} usedText = usedText.dropLast(1)
usedType = SelectionType.CHARACTER_WISE
val textData = PutData.TextData(usedText, usedType, savedRegister.transferableData, savedRegister.name) }
val putData = PutData( val textData = PutData.TextData(usedText, usedType, savedRegister.transferableData, savedRegister.name)
textData,
visualSelection, val putData = PutData(
1, textData,
insertTextBeforeCaret = true, visualSelection,
rawIndent = true, 1,
caretAfterInsertedText = false, insertTextBeforeCaret = true,
putToLine = -1, rawIndent = true,
) caretAfterInsertedText = false,
val vimEditor = editor.vim putToLine = -1,
ClipboardOptionHelper.IdeaputDisabler().use { )
VimPlugin.getPut().putText( ClipboardOptionHelper.IdeaputDisabler().use {
vimEditor, VimPlugin.getPut().putText(
context.vim, IjVimEditor(editor),
putData, injector.executionContextManager.onEditor(editor.vim),
saveToRegister = false putData,
) operatorArguments = OperatorArguments(
editor.vimStateMachine?.isOperatorPending ?: false,
0,
editor.vim.mode,
),
saveToRegister = false
)
}
}
} }
} }

View File

@@ -36,7 +36,6 @@ import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import java.awt.Font import java.awt.Font
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import java.util.*
import javax.swing.Timer import javax.swing.Timer
@@ -49,20 +48,17 @@ internal class IdeaVimSneakExtension : VimExtension {
override fun init() { override fun init() {
val highlightHandler = HighlightHandler() val highlightHandler = HighlightHandler()
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD), MappingMode.NXO) mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD))
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD))
// vim-sneak uses `Z` for visual mode because `S` conflict with vim-sneak plugin VIM-3330
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.NO)
mapToFunctionAndProvideKeys("Z", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.X)
// workaround to support ; and , commands // workaround to support ; and , commands
mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"), MappingMode.NXO) mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"))
mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"), MappingMode.NXO) mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"))
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"), MappingMode.NXO) mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"))
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"), MappingMode.NXO) mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"))
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO) mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL))
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO) mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE))
} }
private class SneakHandler( private class SneakHandler(
@@ -118,7 +114,7 @@ internal class IdeaVimSneakExtension : VimExtension {
var lastSymbols: String = "" var lastSymbols: String = ""
fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? { fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? {
val caret = editor.primaryCaret() val caret = editor.primaryCaret()
val position = caret.offset val position = caret.offset.point
val chars = editor.text() val chars = editor.text()
val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo) val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo)
if (foundPosition != null) { if (foundPosition != null) {
@@ -277,18 +273,16 @@ internal class IdeaVimSneakExtension : VimExtension {
* Map some <Plug>(keys) command to given handler * Map some <Plug>(keys) command to given handler
* and create mapping to <Plug>(prefix)[keys] * and create mapping to <Plug>(prefix)[keys]
*/ */
private fun VimExtension.mapToFunctionAndProvideKeys( private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: ExtensionHandler) {
keys: String, handler: ExtensionHandler, mappingModes: EnumSet<MappingMode>
) {
VimExtensionFacade.putExtensionHandlerMapping( VimExtensionFacade.putExtensionHandlerMapping(
mappingModes, MappingMode.NXO,
injector.parser.parseKeys(command(keys)), injector.parser.parseKeys(command(keys)),
owner, owner,
handler, handler,
false false
) )
VimExtensionFacade.putExtensionHandlerMapping( VimExtensionFacade.putExtensionHandlerMapping(
mappingModes, MappingMode.NXO,
injector.parser.parseKeys(commandFromOriginalPlugin(keys)), injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
owner, owner,
handler, handler,
@@ -300,17 +294,17 @@ private fun VimExtension.mapToFunctionAndProvideKeys(
// - The shortcut should not be registered if any of these mappings is overridden in .ideavimrc // - The shortcut should not be registered if any of these mappings is overridden in .ideavimrc
// - The shortcut should not be registered if some other shortcut for this key exists // - The shortcut should not be registered if some other shortcut for this key exists
val fromKeys = injector.parser.parseKeys(keys) val fromKeys = injector.parser.parseKeys(keys)
val filteredModes = mappingModes.filterNotTo(HashSet()) { val filteredModes = MappingMode.NXO.filterNotTo(HashSet()) {
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(command(keys))) VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(command(keys)))
} }
val filteredModes2 = mappingModes.filterNotTo(HashSet()) { val filteredModes2 = MappingMode.NXO.filterNotTo(HashSet()) {
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys))) VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys)))
} }
val filteredFromModes = mappingModes.filterNotTo(HashSet()) { val filteredFromModes = MappingMode.NXO.filterNotTo(HashSet()) {
injector.keyGroup.hasmapfrom(it, fromKeys) injector.keyGroup.hasmapfrom(it, fromKeys)
} }
val doubleFiltered = mappingModes val doubleFiltered = MappingMode.NXO
.filter { it in filteredModes2 && it in filteredModes && it in filteredFromModes } .filter { it in filteredModes2 && it in filteredModes && it in filteredFromModes }
.toSet() .toSet()
putKeyMapping(doubleFiltered, fromKeys, owner, injector.parser.parseKeys(command(keys)), true) putKeyMapping(doubleFiltered, fromKeys, owner, injector.parser.parseKeys(command(keys)), true)

View File

@@ -7,7 +7,6 @@
*/ */
package com.maddyhome.idea.vim.extension.surround package com.maddyhome.idea.vim.extension.surround
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
@@ -18,7 +17,6 @@ import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
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.api.setChangeMarks import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
@@ -26,17 +24,14 @@ import com.maddyhome.idea.vim.command.OperatorArguments
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
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegisterForCaret
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
@@ -47,11 +42,11 @@ 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.mode
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
import javax.swing.KeyStroke import javax.swing.KeyStroke
import com.maddyhome.idea.vim.state.mode.returnTo
/** /**
* Port of vim-surround. * Port of vim-surround.
@@ -83,15 +78,13 @@ internal class VimSurroundExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("ds"), owner, injector.parser.parseKeys("<Plug>DSurround"), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("ds"), owner, injector.parser.parseKeys("<Plug>DSurround"), true)
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true) putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
} }
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
} }
private class YSurroundHandler : ExtensionHandler { private class YSurroundHandler : ExtensionHandler {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
} }
} }
@@ -103,7 +96,7 @@ internal class VimSurroundExtension : VimExtension {
val ijEditor = editor.ij val ijEditor = editor.ij
val c = getChar(ijEditor) val c = getChar(ijEditor)
if (c.code == 0) return if (c.code == 0) return
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return val pair = getOrInputPair(c, ijEditor) ?: return
editor.forEachCaret { editor.forEachCaret {
val line = it.getBufferPosition().line val line = it.getBufferPosition().line
@@ -117,9 +110,6 @@ internal class VimSurroundExtension : VimExtension {
// it.moveToOffset(lineStartOffset) // it.moveToOffset(lineStartOffset)
} }
// Jump back to start // Jump back to start
if (editor.mode !is Mode.NORMAL) {
editor.mode = Mode.NORMAL()
}
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
} }
@@ -141,7 +131,7 @@ internal class VimSurroundExtension : VimExtension {
} }
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
editor.exitVisualMode() executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
} }
} }
} }
@@ -156,7 +146,7 @@ internal class VimSurroundExtension : VimExtension {
val charTo = getChar(editor.ij) val charTo = getChar(editor.ij)
if (charTo.code == 0) return if (charTo.code == 0) return
val newSurround = getOrInputPair(charTo, editor.ij, context.ij) ?: return val newSurround = getOrInputPair(charTo, editor.ij) ?: return
runWriteAction { change(editor, context, charFrom, newSurround) } runWriteAction { change(editor, context, charFrom, newSurround) }
} }
@@ -233,12 +223,12 @@ internal class VimSurroundExtension : VimExtension {
val searchHelper = injector.searchHelper val searchHelper = injector.searchHelper
return when (char) { return when (char) {
't' -> searchHelper.findBlockTagRange(editor, caret, 1, true) 't' -> searchHelper.findBlockTagRange(editor, caret, 1, true)
'(', ')', 'b' -> findBlockRange(editor, caret, '(', 1, true) '(', ')', 'b' -> searchHelper.findBlockRange(editor, caret, '(', 1, true)
'[', ']' -> findBlockRange(editor, caret, '[', 1, true) '[', ']' -> searchHelper.findBlockRange(editor, caret, '[', 1, true)
'{', '}', 'B' -> findBlockRange(editor, caret, '{', 1, true) '{', '}', 'B' -> searchHelper.findBlockRange(editor, caret, '{', 1, true)
'<', '>' -> findBlockRange(editor, caret, '<', 1, true) '<', '>' -> searchHelper.findBlockRange(editor, caret, '<', 1, true)
'`', '\'', '"' -> { '`', '\'', '"' -> {
val caretOffset = caret.offset val caretOffset = caret.offset.point
val text = editor.text() val text = editor.text()
if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) { if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) {
TextRange(caretOffset - 1, caretOffset + 1) TextRange(caretOffset - 1, caretOffset + 1)
@@ -275,23 +265,23 @@ internal class VimSurroundExtension : VimExtension {
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction { private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = vimEditor.ij val editor = vimEditor.ij
val c = getChar(ijEditor) val c = getChar(editor)
if (c.code == 0) return true if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false val pair = getOrInputPair(c, editor) ?: return false
runWriteAction { runWriteAction {
val change = VimPlugin.getChange() val change = VimPlugin.getChange()
if (supportsMultipleCursors) { if (supportsMultipleCursors) {
ijEditor.runWithEveryCaretAndRestore { editor.runWithEveryCaretAndRestore {
applyOnce(ijEditor, change, pair, count) applyOnce(editor, change, pair, count)
} }
} }
else { else {
applyOnce(ijEditor, change, pair, count) applyOnce(editor, change, pair, count)
// Jump back to start // Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
} }
} }
return true return true
@@ -311,10 +301,8 @@ internal class VimSurroundExtension : VimExtension {
private fun getSurroundRange(caret: VimCaret): TextRange? { private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor val editor = caret.editor
if (editor.mode is Mode.CMD_LINE) { val ijEditor = editor.ij
editor.mode = (editor.mode as Mode.CMD_LINE).returnTo() return when (ijEditor.vim.mode) {
}
return when (editor.mode) {
is Mode.NORMAL -> injector.markService.getChangeMarks(caret) is Mode.NORMAL -> injector.markService.getChangeMarks(caret)
is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) } is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) }
else -> null else -> null
@@ -327,9 +315,7 @@ private val LOG = logger<VimSurroundExtension>()
private const val REGISTER = '"' private const val REGISTER = '"'
private const val OPERATOR_FUNC = "SurroundOperatorFunc" private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val SURROUND_PAIRS = mapOf( private val SURROUND_PAIRS = mapOf(
'b' to ("(" to ")"), 'b' to ("(" to ")"),
@@ -355,11 +341,8 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
null null
} }
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? { private fun inputTagPair(editor: Editor): Pair<String, String>? {
val tagInput = inputString(editor, context, "<", '>') val tagInput = inputString(editor, "<", '>')
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput) val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) { return if (matcher.find()) {
val tagName = matcher.group(1) val tagName = matcher.group(1)
@@ -372,21 +355,17 @@ private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, Str
private fun inputFunctionName( private fun inputFunctionName(
editor: Editor, editor: Editor,
context: DataContext,
withInternalSpaces: Boolean, withInternalSpaces: Boolean,
): Pair<String, String>? { ): Pair<String, String>? {
val functionNameInput = inputString(editor, context, "function: ", null) val functionNameInput = inputString(editor, "function: ", null)
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
if (functionNameInput.isEmpty()) return null if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")" return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
} }
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) { private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor, context) '<', 't' -> inputTagPair(editor)
'f' -> inputFunctionName(editor, context, false) 'f' -> inputFunctionName(editor, false)
'F' -> inputFunctionName(editor, context, true) 'F' -> inputFunctionName(editor, true)
else -> getSurroundPair(c) else -> getSurroundPair(c)
} }

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