1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-09-18 07:24:48 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
54c16c97b9 VIM-3238 Fix recording a macro that replays another macro 2024-02-07 17:52:58 +01:00
1050 changed files with 75268 additions and 110490 deletions

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
* text=auto eol=lf

View File

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

View File

@@ -18,10 +18,10 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 300
- name: Set up JDK 17
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '17'
java-version: '11'
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

View File

@@ -18,10 +18,10 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 300
- name: Set up JDK 17
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '17'
java-version: '11'
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

View File

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

View File

@@ -9,13 +9,20 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Apply Patch
run: |
git apply src/test/java/ui/octopus.patch
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
java-version: 11
- name: Setup FFmpeg
run: brew install ffmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
with:
# 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
@@ -23,7 +30,7 @@ jobs:
- name: Run Idea
run: |
mkdir -p build/reports
gradle runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
gradle :runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@@ -31,10 +38,10 @@ jobs:
max-attempts: 20
retry-delay: 10s
- name: Tests
run: gradle :tests:ui-ij-tests:testUi
run: gradle :testUi
- name: Move video
if: always()
run: mv tests/ui-ij-tests/video build/reports
run: mv video build/reports
- name: Move sandbox logs
if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log
@@ -45,7 +52,6 @@ jobs:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-ij-tests/build/reports
sandbox-idea-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest

View File

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

View File

@@ -13,9 +13,13 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
java-version: 11
- name: Setup FFmpeg
run: brew install ffmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
with:
# 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
@@ -23,7 +27,7 @@ jobs:
- name: Run Idea
run: |
mkdir -p build/reports
gradle runIdeForUiTests > build/reports/idea.log &
gradle :runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@@ -31,10 +35,10 @@ jobs:
max-attempts: 20
retry-delay: 10s
- name: Tests
run: gradle :tests:ui-ij-tests:testUi
run: gradle :testUi
- name: Move video
if: always()
run: mv tests/ui-ij-tests/video build/reports
run: mv video build/reports
- name: Move sandbox logs
if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log
@@ -45,7 +49,6 @@ jobs:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-ij-tests/build/reports
sandbox-idea-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest

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

View File

@@ -7,12 +7,15 @@ on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'
# Workflow run on push is disabled to avoid conflicts when merging PR
# push:
# branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
if: false
if: github.repository == 'JetBrains/ideavim'
steps:
- uses: actions/checkout@v3
@@ -22,10 +25,10 @@ jobs:
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
- name: Get tags
run: git fetch --tags origin
- name: Set up JDK 17
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '17'
java-version: '11'
distribution: 'adopt'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
@@ -36,7 +39,7 @@ jobs:
echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-changelog)" >> $GITHUB_ENV
- name: Update changelog
run: ./gradlew --no-configuration-cache updateChangelog
run: ./gradlew updateChangelog
env:
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}

View File

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

4
.gitignore vendored
View File

@@ -10,8 +10,6 @@
!/.idea/runConfigurations
!/.idea/codeStyles
!/.idea/vcs.xml
!/.idea/misc.xml
!/.idea/.name
**/build/
**/out/
@@ -24,7 +22,7 @@
.teamcity/*.iml
# 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
# Generated JSONs for lazy classloading
/vim-engine/src/main/resources/ksp-generated

1
.idea/.name generated
View File

@@ -1 +0,0 @@
IdeaVim

View File

@@ -6,7 +6,6 @@
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</value>
</option>
<option name="LINE_SEPARATOR" value="&#10;" />
<JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="my" />
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />

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

@@ -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>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

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

View File

@@ -11,7 +11,6 @@ import _Self.subprojects.GitHub
import _Self.subprojects.OldTests
import _Self.subprojects.Releases
import _Self.vcsRoots.GitHubPullRequest
import _Self.vcsRoots.ReleasesVcsRoot
import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
@@ -22,11 +21,10 @@ object Project : Project({
// VCS roots
vcsRoot(GitHubPullRequest)
vcsRoot(ReleasesVcsRoot)
// Active tests
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("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased)

View File

@@ -34,6 +34,7 @@ object Compatibility : IdeaVimBuildType({
java --version
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}org.jetbrains.IdeaVim-EasyMotion' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}io.github.mishkun.ideavimsneak' [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
# Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city

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

@@ -25,7 +25,7 @@ object LongRunning : IdeaVimBuildType({
steps {
gradle {
tasks = "clean :tests:long-running-tests:testLongRunning"
tasks = "clean testLongRunning"
buildFile = ""
enableStacktrace = true
}

View File

@@ -39,7 +39,7 @@ object Nvim : IdeaVimBuildType({
""".trimIndent()
}
gradle {
tasks = "clean test -Dnvim"
tasks = "clean testWithNeovim"
buildFile = ""
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

@@ -24,7 +24,7 @@ object PropertyBased : IdeaVimBuildType({
steps {
gradle {
tasks = "clean :tests:property-tests:testPropertyBased"
tasks = "clean testPropertyBased"
buildFile = ""
enableStacktrace = true
}

View File

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

View File

@@ -46,8 +46,8 @@ object Qodana : IdeaVimBuildType({
version = Qodana.JVMVersion.LATEST
}
reportAsTests = true
additionalDockerArguments = "-e QODANA_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJvcmdhbml6YXRpb24iOiIzUFZrQSIsInByb2plY3QiOiIzN1FlQSIsInRva2VuIjoiM0t2bXoifQ.uohp81tM7iAfvvB6k8faarfpV-OjusAaEbWQ8iNrOgs"
additionalQodanaArguments = "--baseline qodana.sarif.json"
cloudToken = "credentialsJSON:6b79412e-9198-4862-9223-c5019488f903"
}
}
@@ -63,6 +63,7 @@ object Qodana : IdeaVimBuildType({
timezone = "SERVER"
}
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.buildSteps.gradle
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.BuildFailureOnMetric
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.failOnMetricChange
object ReleaseMajor : ReleasePlugin("major")
object ReleaseMinor : ReleasePlugin("minor")
@@ -95,14 +97,14 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber"
}
// gradle {
// name = "Update change log"
// tasks = "scripts:changelogUpdateUnreleased"
// }
// gradle {
// name = "Commit preparation changes"
// tasks = "scripts:commitChanges"
// }
gradle {
name = "Update change log"
tasks = "scripts:changelogUpdateUnreleased"
}
gradle {
name = "Commit preparation changes"
tasks = "scripts:commitChanges"
}
gradle {
name = "Add release tag"
tasks = "scripts:addReleaseTag"
@@ -115,24 +117,33 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Publish release"
tasks = "publishPlugin"
}
// script {
// name = "Checkout master branch"
// scriptContent = """
// echo Checkout master
// git checkout master
// """.trimIndent()
// }
// gradle {
// name = "Update change log in master"
// tasks = "scripts:changelogUpdateUnreleased"
// }
// gradle {
// name = "Commit preparation changes in master"
// tasks = "scripts:commitChanges"
// }
script {
name = "Checkout master branch"
scriptContent = """
echo Checkout master
git checkout master
""".trimIndent()
}
gradle {
name = "Update change log in master"
tasks = "scripts:changelogUpdateUnreleased"
}
gradle {
name = "Commit preparation changes in master"
tasks = "scripts:commitChanges"
}
script {
name = "Push changes to the repo"
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
echo checkout release branch
git branch --set-upstream-to=origin/release release
@@ -145,10 +156,10 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Run Integrations"
tasks = "releaseActions"
}
// gradle {
// name = "Slack Notification"
// tasks = "slackNotification"
// }
gradle {
name = "Slack Notification"
tasks = "slackNotification"
}
}
features {
@@ -156,4 +167,16 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
teamcitySshKey = "IdeaVim ssh keys"
}
}
failureConditions {
failOnMetricChange {
metric = BuildFailureOnMetric.MetricType.ARTIFACT_SIZE
threshold = 5
units = BuildFailureOnMetric.MetricUnit.PERCENTS
comparison = BuildFailureOnMetric.MetricComparison.DIFF
compareTo = build {
buildRule = lastSuccessful()
}
}
}
})

View File

@@ -16,10 +16,10 @@ object GitHub : Project({
name = "Pull Requests checks"
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"
description = "Test GitHub pull requests $desc"

View File

@@ -1,11 +1,8 @@
package _Self.subprojects
import _Self.buildTypes.CreateNewReleaseBranchFromMaster
import _Self.buildTypes.PrintReleaseBranch
import _Self.buildTypes.PublishVimEngine
import _Self.buildTypes.ReleaseDev
import _Self.buildTypes.ReleaseEap
import _Self.buildTypes.ReleaseEapFromBranch
import _Self.buildTypes.ReleaseMajor
import _Self.buildTypes.ReleaseMinor
import _Self.buildTypes.ReleasePatch
@@ -41,8 +38,4 @@ object Releases : Project({
buildType(ReleaseEap)
buildType(ReleaseDev)
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
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.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.
@@ -11,18 +13,6 @@ To apply the patch, change the buildType with id = 'IdeaVimTests_Latest_EAP'
accordingly, and delete the patch script.
*/
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 {
gradle {
tasks = "clean test"

View File

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

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

View File

@@ -495,34 +495,6 @@ Contributors:
[![icon][github]](https://github.com/emanuelgestosa)
&nbsp;
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
Previous contributors:

View File

@@ -23,20 +23,13 @@ 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
usual beta standards.
## End of changelog file maintenance
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
Latest Fixes: https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20sort%20by:%20updated%20
## 2.9.0, 2024-02-20
## To Be Released
### Fixes:
* [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot
### Merged PRs:
* [805](https://github.com/JetBrains/ideavim/pull/805) by [chylex](https://github.com/chylex): VIM-3238 Fix recording a macro that replays another macro
* [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex
## 2.8.0, 2024-01-30

View File

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

View File

@@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.22")
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.17")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

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

View File

@@ -32,6 +32,7 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.exceptions.MissingVersionException
import org.kohsuke.github.GHUser
import java.net.HttpURLConnection
import java.net.URL
@@ -48,14 +49,14 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.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("io.ktor:ktor-client-core:2.3.12")
classpath("io.ktor:ktor-client-cio:2.3.10")
classpath("io.ktor:ktor-client-auth:2.3.12")
classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
classpath("io.ktor:ktor-client-core:2.3.7")
classpath("io.ktor:ktor-client-cio:2.3.7")
classpath("io.ktor:ktor-client-auth:2.3.7")
classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@@ -63,20 +64,39 @@ buildscript {
}
plugins {
antlr
java
kotlin("jvm") version "1.9.22"
application
id("java-test-fixtures")
id("org.jetbrains.intellij") version "1.17.3"
id("org.jetbrains.intellij") version "1.17.0"
id("org.jetbrains.changelog") version "2.2.0"
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
// id("org.jlleitschuh.gradle.ktlint") version "11.3.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 "1.9.22-1.0.17"
}
ksp {
arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
arg("vimscript_functions_file", "intellij_vimscript_functions.json")
arg("ex_commands_file", "intellij_ex_commands.json")
arg("commands_file", "intellij_commands.json")
}
afterEvaluate {
// tasks.named("kspKotlin").configure { dependsOn("clean") }
tasks.named("kspKotlin").configure { dependsOn("generateGrammarSource") }
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
tasks.named("kspTestKotlin").configure { enabled = false }
}
// Import variables from gradle.properties file
val javaVersion: String by project
val kotlinVersion: String by project
@@ -85,7 +105,7 @@ val ideaType: String by project
val downloadIdeaSources: String by project
val instrumentPluginCode: String by project
val remoteRobotVersion: String by project
val splitModeVersion: String by project
val antlrVersion: String by project
val publishChannels: String by project
val publishToken: String by project
@@ -93,8 +113,6 @@ val publishToken: String by project
val slackUrl: String by project
val youtrackToken: String by project
val releaseType: String? by project
repositories {
mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
@@ -103,10 +121,12 @@ repositories {
dependencies {
api(project(":vim-engine"))
ksp(project(":annotation-processors"))
compileOnly(project(":annotation-processors"))
implementation(project(":annotation-processors"))
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.1.0")
runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
antlr("org.antlr:antlr4:$antlrVersion")
// --------- Test dependencies ----------
@@ -125,14 +145,19 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:ide-launcher:$remoteRobotVersion")
testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
}
configurations {
@@ -141,25 +166,77 @@ configurations {
}
}
tasks {
test {
// --- Compilation
// This can be moved to other test registration when issue with tests in gradle will be fixed
tasks.register<Test>("testWithNeovim") {
group = "verification"
systemProperty("ideavim.nvim.test", "true")
exclude("/ui/**")
exclude("**/longrunning/**")
exclude("**/propertybased/**")
useJUnitPlatform()
}
tasks.register<Test>("testPropertyBased") {
group = "verification"
// include("**/propertybased/**")
useJUnitPlatform()
}
tasks.register<Test>("testLongRunning") {
group = "verification"
// include("**/longrunning/**")
useJUnitPlatform()
}
tasks {
// Issue in gradle 7.3
val test by getting(Test::class) {
isScanForTestClasses = false
// Only run tests from classes that end with "Test"
include("**/*Test.class")
include("**/*test.class")
include("**/*Tests.class")
exclude("**/ParserTest.class")
// Set teamcity env variable locally to run additional tests for leaks.
// By default, this test runs on TC only, but this test doesn't take a lot of time,
// so we can turn it on for local development
if (environment["TEAMCITY_VERSION"] == null) {
println("Set env TEAMCITY_VERSION to X to enable project leak checks from the platform")
println("Set env TEAMCITY_VERSION to X")
environment("TEAMCITY_VERSION" to "X")
}
}
systemProperty("ideavim.nvim.test", System.getProperty("nvim") ?: false)
val testWithNeovim by getting(Test::class) {
isScanForTestClasses = false
// Only run tests from classes that end with "Test"
include("**/*Test.class")
include("**/*test.class")
include("**/*Tests.class")
exclude("**/ParserTest.class")
exclude("**/longrunning/**")
exclude("**/propertybased/**")
}
val testPropertyBased by getting(Test::class) {
isScanForTestClasses = false
// Only run tests from classes that end with "Test"
include("**/propertybased/*Test.class")
include("**/propertybased/*test.class")
include("**/propertybased/*Tests.class")
}
val testLongRunning by getting(Test::class) {
isScanForTestClasses = false
// Only run tests from classes that end with "Test"
include("**/longrunning/**/*Test.class")
include("**/longrunning/**/*test.class")
include("**/longrunning/**/*Tests.class")
exclude("**/longrunning/**/ParserTest.class")
}
compileJava {
// CodeQL can't resolve the 'by project' property, so we need to give it a hint. This is the minimum version we need
// so doesn't have to match exactly
// Hint for the CodeQL autobuilder: sourceCompatibility = 17
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
@@ -176,7 +253,6 @@ tasks {
// allWarningsAsErrors = true
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
@@ -184,27 +260,91 @@ tasks {
// allWarningsAsErrors = true
}
}
runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
kotlin {
explicitApi()
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
gradle.projectsEvaluated {
tasks.compileJava {
// options.compilerArgs.add("-Werror")
options.compilerArgs.add("-Xlint:deprecation")
}
}
// --- Intellij plugin
intellij {
version.set(ideaVersion)
pluginName.set("IdeaVim")
updateSinceUntilBuild.set(false)
downloadSources.set(downloadIdeaSources.toBoolean())
instrumentCode.set(instrumentPluginCode.toBoolean())
intellijRepository.set("https://www.jetbrains.com/intellij-repository")
plugins.set(listOf("AceJump:3.8.11"))
}
tasks {
downloadRobotServerPlugin {
version.set(remoteRobotVersion)
}
publishPlugin {
channels.set(publishChannels.split(","))
token.set(publishToken)
}
signPlugin {
certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN"))
privateKey.set(providers.environmentVariable("PRIVATE_KEY"))
password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
}
runIdeForUiTests {
systemProperty("robot-server.port", "8082")
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.show.tips.on.startup.default.value", "false")
}
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
runPluginVerifier {
downloadDir.set("${project.buildDir}/pluginVerifier/ides")
teamCityOutputFormat.set(true)
// ideVersions.set(listOf("IC-2021.3.4"))
}
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")
@@ -227,84 +367,54 @@ tasks {
dependsOn(createOpenApiSourceJar)
from(createOpenApiSourceJar) { into("lib/src") }
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
gradle.projectsEvaluated {
tasks.compileJava {
// options.compilerArgs.add("-Werror")
options.compilerArgs.add("-Xlint:deprecation")
}
}
// --- Intellij plugin
intellij {
version.set(ideaVersion)
type.set(ideaType)
pluginName.set("IdeaVim")
updateSinceUntilBuild.set(false)
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(","))
token.set(publishToken)
}
signPlugin {
certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN"))
privateKey.set(providers.environmentVariable("PRIVATE_KEY"))
password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
}
runPluginVerifier {
downloadDir.set("${project.buildDir}/pluginVerifier/ides")
teamCityOutputFormat.set(true)
}
patchPluginXml {
val pluginVersion = version
// Don't forget to update plugin.xml
sinceBuild.set("241.15989.150")
patchPluginXml {
sinceBuild.set("233.11799.30")
// Get the latest available change notes from the changelog file
changeNotes.set(
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
provider {
with(changelog) {
val log = try {
getUnreleased()
} catch (e: MissingVersionException) {
getOrNull(pluginVersion.toString()) ?: getLatest()
}
renderItem(
log,
org.jetbrains.changelog.Changelog.OutputType.HTML,
)
}
},
)
}
}
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")
// --- Linting
//ktlint {
// version.set("0.48.2")
//}
// --- Tests
tasks {
test {
useJUnitPlatform()
exclude("**/propertybased/**")
exclude("**/longrunning/**")
exclude("/ui/**")
}
}
afterEvaluate {
// tasks.named("kspKotlin").configure { dependsOn("clean") }
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
tasks.named("kspTestKotlin").configure { enabled = false }
tasks.register<Test>("testUi") {
group = "verification"
useJUnitPlatform()
include("/ui/**")
}
// --- Changelog
changelog {
@@ -323,6 +433,16 @@ koverMerged {
enable()
}
kover {
instrumentation {
// set of test tasks names to exclude from instrumentation. The results of their execution will not be presented in the report
excludeTasks += "testPropertyBased"
excludeTasks += "testLongRunning"
excludeTasks += "testWithNeovim"
excludeTasks += "testUi"
}
}
// --- Slack notification
tasks.register("slackNotification") {
@@ -399,14 +519,12 @@ val prId: String by project
tasks.register("updateMergedPr") {
doLast {
val x = changelog.getUnreleased()
println("x")
// if (project.hasProperty("prId")) {
// println("Got pr id: $prId")
// updateMergedPr(prId.toInt())
// } else {
// error("Cannot get prId")
// }
if (project.hasProperty("prId")) {
println("Got pr id: $prId")
updateMergedPr(prId.toInt())
} else {
error("Cannot get prId")
}
}
}
@@ -430,9 +548,7 @@ val fixVersionsElementType = "VersionBundleElement"
tasks.register("releaseActions") {
group = "other"
doLast {
if (releaseType == "patch") return@doLast
val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D%20and%20tag:%20%7BIdeaVim%20Released%20In%20EAP%7D%20")
val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D")
if (tickets.isNotEmpty()) {
println("Updating statuses for tickets: $tickets")
setYoutrackStatus(tickets, "Fixed")
@@ -520,8 +636,7 @@ fun addReleaseToYoutrack(name: String): String {
println("Creating new release version in YouTrack: $name")
return runBlocking {
val response =
client.post("https://youtrack.jetbrains.com/api/admin/projects/$vimProjectId/customFields/$fixVersionsFieldId/bundle/values?fields=id,name") {
val response = client.post("https://youtrack.jetbrains.com/api/admin/projects/$vimProjectId/customFields/$fixVersionsFieldId/bundle/values?fields=id,name") {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
val request = buildJsonObject {
@@ -538,8 +653,7 @@ fun getVersionIdByName(name: String): String? {
val client = httpClient()
return runBlocking {
val response =
client.get("https://youtrack.jetbrains.com/api/admin/projects/$vimProjectId/customFields/$fixVersionsFieldId/bundle/values?fields=id,name&query=$name")
val response = client.get("https://youtrack.jetbrains.com/api/admin/projects/$vimProjectId/customFields/$fixVersionsFieldId/bundle/values?fields=id,name&query=$name")
response.body<JsonArray>().singleOrNull()?.jsonObject?.get("id")?.jsonPrimitive?.content
}
}
@@ -577,8 +691,7 @@ fun setYoutrackStatus(tickets: Collection<String>, status: String) {
runBlocking {
for (ticket in tickets) {
println("Try to set $ticket to $status")
val response =
client.post("https://youtrack.jetbrains.com/api/issues/$ticket?fields=customFields(id,name,value(id,name))") {
val response = client.post("https://youtrack.jetbrains.com/api/issues/$ticket?fields=customFields(id,name,value(id,name))") {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
val request = buildJsonObject {
@@ -617,8 +730,7 @@ fun setYoutrackFixVersion(tickets: Collection<String>, version: String) {
runBlocking {
for (ticket in tickets) {
println("Try to set fix version $version for $ticket")
val response =
client.post("https://youtrack.jetbrains.com/api/issues/$ticket?fields=customFields(id,name,value(id,name))") {
val response = client.post("https://youtrack.jetbrains.com/api/issues/$ticket?fields=customFields(id,name,value(id,name))") {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
val request = buildJsonObject {
@@ -656,8 +768,7 @@ fun getYoutrackStatus(ticket: String): String {
val client = httpClient()
return runBlocking {
val response =
client.get("https://youtrack.jetbrains.com/api/issues/$ticket/customFields/123-129?fields=value(name)")
val response = client.get("https://youtrack.jetbrains.com/api/issues/$ticket/customFields/123-129?fields=value(name)")
response.body<JsonObject>()["value"]!!.jsonObject.getValue("name").jsonPrimitive.content
}
}

View File

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

View File

@@ -27,7 +27,7 @@ Plug 'nerdtree'
### Preview
<details>
<summary>Click for the preview</summary>
<summary>Click to the the preview</summary>
<img src="images/nerdtree.gif" alt="NERDTree example"/>
</details>
@@ -41,7 +41,7 @@ Plug 'nerdtree'
- `:NERDTreeRefreshRoot`
| Key | Description | Map Setting |
|---------|--------------------------------------------------------|--------------------------------|
|---------|---------------------------------------------------------|--------------------------------|
| `o` | Open files, directories and bookmarks | `g:NERDTreeMapActivateNode` |
| `go` | Open selected file, but leave cursor in the NERDTree | `g:NERDTreeMapPreview` |
| `t` | Open selected node/bookmark in a new tab | `g:NERDTreeMapOpenInTab` |
@@ -64,9 +64,6 @@ Plug 'nerdtree'
| `m` | Display the NERDTree menu | `g:NERDTreeMapMenu` |
| `q` | Close the NERDTree window | `g:NERDTreeMapQuit` |
| `A` | Zoom (maximize/minimize) the NERDTree window | `g:NERDTreeMapToggleZoom` |
| `d` | Delete file or directory | `g:NERDTreeMapDelete` |
| `n` | Create File | `g:NERDTreeMapNewFile` |
| `N` | Create Directory | `g:NERDTreeMapNewDir` |
### Troubleshooting

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.
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.
The following `:set` commands can appear in `~/.ideavimrc` or be set manually in the command mode:
```
'clipboard' 'cb' Defines clipboard behavioue
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
'clipboard' 'cb' clipboard options
Standard clipboard options plus
`ideaput` (default on) - IdeaVim ONLY
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")
'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
startsel Using a shifted special[1] 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.
present in 'selectmode').
stopsel Using a NOT-shifted special[1] 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
stopselect Using a NOT-shifted special[1] key stops - IdeaVim ONLY
select mode and removes selection.
stopvisual Using a NOT-shifted special[1] key stops - IdeaVim ONLY
visual mode and removes selection.
continueselect Using a shifted arrow key doesn't - IdeaVim ONLY
start selection, but in select mode
acts like startsel is enabled
continuevisual Using a shifted arrow key doesn't - IdeaVim ONLY
start selection, but in visual mode
acts like startsel is enabled
Special keys in this context are the cursor keys, <End>, <Home>,
<PageUp> and <PageDown>.
'matchpairs' 'mps' Pairs of characters that "%" can match
'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
'operatorfunc' 'opfunc' Name of a function to call with the g@ operator
'scroll' 'scr' Number of lines to scroll with CTRL-U and CTRL-D
'selection' 'sel' What type of selection to use
'selectmode' 'slm' Controls when to start Select mode instead of Visual
This is a comma-separated list of words:
'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
mouse When using the mouse
key When using shifted special[1] keys
cmd When using "v", "V", or <C-V>
ideaselection When IDE sets a selection - IdeaVim ONLY
(e.g.: extend selection, wrap with while, etc.)
`selectmode` `slm` String (default "")
'shell' 'sh' The shell to use to execute commands with ! and :!
'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
This is a comma-separated list of words, which specify when to start
Select mode instead of Visual mode, when a selection is started.
Possible values:
mouse when using the mouse
key when using shifted special[1] keys
cmd when using "v", "V", or <C-V>
ideaselection when IDE sets a selection - IdeaVim ONLY
(examples: extend selection, wrap with while, etc.)
'visualbell' 'vb' When on, prevents beeping on error
'whichwrap' 'ww' Which keys that move the cursor left/right can wrap to
other lines
A comma-separated list of these flags:
char key modes
b <BS> Normal and Visual
s <Space> Normal and Visual
h "h" Normal and Visual
l "l" Normal and Visual
< <Left> Normal and Visual
> <Right> Normal and Visual
~ "~" Normal
[ <Left> Insert and Replace
] <Right> Insert and Replace
`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).
'wrapscan' 'ws' Search will wrap around the end of file
```
'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 options mapped to IntelliJ-based IDE settings
IdeaVim provides its own implementation for handling scroll jump and offset, even though IntelliJ-based IDEs have similar functionality (there are differences in behaviour).
When IdeaVim is hosted in an IntelliJ-based IDE (but not JetBrains Fleet), the following options map to the equivalent IDE settings:
```
'scrolljump' 'sj' Minimal number of lines to scroll
'scrolloff' 'so' Minimal number of lines above and below the cursor
'sidescroll' 'ss' Minimal number of columns to scroll horizontally
'sidescrolloff' 'siso' Minimal number of columns to left and right of cursor
```
IdeaVim only commands:
## IdeaVim options for IntelliJ-based IDE features
`ideamarks` `ideamarks` Boolean (default true)
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.
If true, creation of global mark will trigger creation of IDE's bookmark
and vice versa.
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.
`idearefactormode` `idearefactormode` String(default "select")
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).
Define the mode that would be enabled during
the refactoring (renaming, live template, introduce variable, etc)
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.
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
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.)
This option has effect if you are in normal, insert or replace mode before refactoring start.
Visual or select mode are not changed.
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
```
`ideajoin` `ideajoin` Boolean (default false)
## IdeaVim only options
If true, join command will be performed via IDE
See wiki/`ideajoin` examples
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.
`ideastatusicon` `ideastatusicon` String(default "enabled")
```
'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.
Define the behavior of IdeaVim icon in the status bar.
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.
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
'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.
`ideawrite` `ideawrite` String (default "all")
"file" or "all". Defines the behaviour of ":w" command.
Value "all" enables execution of ":wa" (save all) command on ":w" (save).
This feature exists because some IJ options like "Prettier on save" or "ESlint on save"
work only with "save all" action. If this option is set to "all", these actions work
also with ":w" command.
'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.
`lookupkeys` `lookupkeys` List of strings
'ideamarks' boolean (default on)
global
Maps Vim's global marks to IDE bookmarks.
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>"
'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
`ideavimsupport` `ideavimsupport` List of strings (default "dialog")
This option is only used when the refactoring is started in Normal,
Insert or Replace mode. Visual or Select modes are not changed.
Define the list of additional buffers where IdeaVim is enabled.
'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
- dialog - enable IdeaVim in dialogs
- singleline - enable IdeaVim in single line editors (not suggested)
'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.
```
----------
[1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown>

View File

@@ -8,30 +8,16 @@
# 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
# product version (e.g. 2024.1, 2024.1.1) or build (e.g. 241.15989.150, 241-EAP-SNAPSHOT). The dependency will be
# resolved against the configured repositories, which by default includes Maven releases and snapshots, the CDN used to
# download consumer releases, the plugin marketplace and so on.
# You can find an example list of all CDN based versions for IDEA Community here:
# https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
ideaVersion=2024.1.1
ideaVersion=2023.3.2
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true
version=chylex-36
version=SNAPSHOT
javaVersion=17
remoteRobotVersion=0.11.22
remoteRobotVersion=0.11.21
antlrVersion=4.10.1
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2
# Running IdeaVim in split mode requires 242. Update this version once 242 has been released, and remove it completely
# when IdeaVim targets 242, at which point runIdeSplitMode will run correctly with the target version.
# See also runIdeSplitMode
splitModeVersion=242-EAP-SNAPSHOT
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version
@@ -41,14 +27,13 @@ publishChannels=eap
# 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
kotlinxSerializationVersion=1.6.2
kotlinxSerializationVersion=1.5.1
slackUrl=
youtrackToken=
# Gradle settings
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.caching=true
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary
kotlin.stdlib.default.dependency=false

Binary file not shown.

View File

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

5
gradlew vendored
View File

@@ -130,14 +130,11 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -20,17 +20,17 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
implementation("io.ktor:ktor-client-core:2.3.12")
implementation("io.ktor:ktor-client-cio:2.3.10")
implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
implementation("io.ktor:ktor-client-auth:2.3.12")
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-cio:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
implementation("io.ktor:ktor-client-auth:2.3.7")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.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")
}
@@ -58,6 +58,13 @@ tasks.register("checkNewPluginDependencies", JavaExec::class) {
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) {
group = "release"
mainClass.set("scripts.release.CalculateNewVersionKt")
@@ -107,13 +114,6 @@ tasks.register("calculateNewEapVersion", JavaExec::class) {
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) {
group = "release"
mainClass.set("scripts.release.CalculateNewDevVersionKt")

View File

@@ -22,7 +22,7 @@ import kotlinx.serialization.json.jsonPrimitive
*/
@Suppress("SpellCheckingInspection")
val knownPlugins = setOf(
val knownPlugins = listOf(
"IdeaVimExtension",
"github.zgqq.intellij-enhance",
"org.jetbrains.IdeaVim-EasyMotion",
@@ -31,12 +31,7 @@ val knownPlugins = setOf(
"com.github.copilot",
"com.github.dankinsoid.multicursor",
"com.joshestein.ideavim-quickscope",
"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",
// "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("includeOptional", true)
}
val output = response.body<List<String>>().toSet()
val output = response.body<List<String>>()
println(output)
if (knownPlugins != output) {
val newPlugins = (output - knownPlugins).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
if (newPlugins.isNotEmpty()) {
// 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(
"""
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()
)
}

View File

@@ -8,12 +8,6 @@
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>) {
println("HI!")
val projectDir = args[0]
@@ -25,12 +19,10 @@ fun main(args: Array<String>) {
check(branch == "master") {
"We should be on master branch"
}
val mergeBaseCommit = getMergeBaseWithMaster(projectDir, objectId)
println("Base commit $mergeBaseCommit")
withGit(projectDir) { git ->
val log = git.log().setMaxCount(500).call().toList()
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
println("Log diff size is $numCommits")
check(numCommits < 450) {
@@ -43,18 +35,3 @@ fun main(args: Array<String>) {
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
}
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> {
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
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()
}

16
settings.gradle Normal file
View File

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

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)+
#ExecuteCommand|
(WS | COLON)* range? (WS | COLON)* lShift
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
(NEW_LINE | BAR)*
(WS | COLON)* range? (WS | COLON)* lShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
#ShiftLeftCommand|
(WS | COLON)* range? (WS | COLON)* rShift
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
(NEW_LINE | BAR)*
(WS | COLON)* range? (WS | COLON)* rShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
#ShiftRightCommand|
(WS | COLON)* range? (WS | COLON)*
@@ -119,7 +115,6 @@ command:
| 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
| 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
| T_LOWERCASE | COPY | CMD_CLEAR | BUFFER_LIST | BUFFER_CLOSE | B_LOWERCASE | BUFFER | ASCII
| ACTIONLIST | ACTION | LOCKVAR | UNLOCKVAR | PACKADD | TABMOVE
@@ -619,7 +614,6 @@ BUFFER_CLOSE: 'bd' | 'bde' | 'bdel' | 'bdele' | 'bdelet' | 'bdelete';
BUFFER_LIST: 'buffers' | 'ls' | 'files';
CALL: 'cal' | 'call';
CLASS: 'cla' | 'clas' | 'class';
CLEARJUMPS: 'cle' | 'clea' | 'clear' | 'clearj' | 'clearju' | 'clearjum' | 'clearjump' | 'clearjumps';
CMD: 'com' | 'comm' | 'comma' | 'comman' | 'command';
CMD_CLEAR: 'comc' | 'comcl' | 'comcle' | 'comclea' | 'comclear';
COPY: 'co' | 'cop' | 'copy';

View File

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

View File

@@ -14,7 +14,7 @@ import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.injector
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
/**
@@ -36,10 +36,8 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
// This is a temporal workaround for VIM-2487
internal class PyNotebooksCloseWorkaround : ProjectManagerListener {
override fun projectClosingBeforeSave(project: Project) {
// TODO: Confirm context in CWM scenario
if (injector.globalIjOptions().closenotebooks) {
injector.editorGroup.getEditors().forEach { vimEditor ->
val editor = (vimEditor as IjVimEditor).editor
localEditors().forEach { editor ->
val virtualFile = EditorHelper.getVirtualFile(editor)
if (virtualFile?.extension == "ipynb") {
val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)

View File

@@ -14,43 +14,44 @@ import com.maddyhome.idea.vim.key.MappingOwner
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
object RegisterActions {
public object RegisterActions {
/**
* Register all the key/action mappings for the plugin.
*/
@JvmStatic
fun registerActions() {
public fun registerActions() {
registerVimCommandActions()
registerShortcutsWithoutActions()
registerEmptyShortcuts() // todo most likely it is not needed
}
fun findAction(id: String): EditorActionHandlerBase? {
val commandBean = IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id }
?: EngineCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
public fun findAction(id: String): EditorActionHandlerBase? {
val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
return commandBean.instance
}
fun findActionOrDie(id: String): EditorActionHandlerBase {
public fun findActionOrDie(id: String): EditorActionHandlerBase {
return findAction(id) ?: throw RuntimeException("Action $id is not registered")
}
@JvmStatic
fun unregisterActions() {
public fun unregisterActions() {
val keyGroup = VimPlugin.getKeyIfCreated()
keyGroup?.unregisterCommandActions()
}
private fun registerVimCommandActions() {
val parser = VimPlugin.getKey()
IntellijCommandProvider.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()
// 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.
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,8 +37,8 @@ import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
import com.maddyhome.idea.vim.helper.MacKeyRepeat;
import com.maddyhome.idea.vim.listener.VimListenerManager;
import com.maddyhome.idea.vim.newapi.IjVimInjector;
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.vimscript.services.VariableService;
import com.maddyhome.idea.vim.yank.YankGroupBase;
import org.jdom.Element;
@@ -46,7 +46,6 @@ import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static com.maddyhome.idea.vim.group.EditorGroup.EDITOR_STORE_ELEMENT;
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
import static com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc;
@@ -124,12 +123,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return (FileGroup)VimInjectorKt.getInjector().getFile();
}
public static @NotNull IjVimSearchGroup getSearch() {
return ApplicationManager.getApplication().getService(IjVimSearchGroup.class);
public static @NotNull SearchGroup getSearch() {
return ApplicationManager.getApplication().getService(SearchGroup.class);
}
public static @Nullable IjVimSearchGroup getSearchIfCreated() {
return ApplicationManager.getApplication().getServiceIfCreated(IjVimSearchGroup.class);
public static @Nullable SearchGroup getSearchIfCreated() {
return ApplicationManager.getApplication().getServiceIfCreated(SearchGroup.class);
}
public static @NotNull ProcessGroup getProcess() {
@@ -212,22 +211,22 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
public static void setEnabled(final boolean enabled) {
if (isEnabled() == enabled) return;
if (!enabled) {
getInstance().turnOffPlugin(true);
}
getInstance().enabled = enabled;
if (enabled) {
getInstance().turnOnPlugin();
}
if (enabled) {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
} else {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
}
if (!enabled) {
getInstance().turnOffPlugin(true);
}
if (enabled) {
getInstance().turnOnPlugin();
}
StatusBarIconFactory.Util.INSTANCE.updateIcon();
}
@@ -283,14 +282,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
ideavimrcRegistered = true;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
try {
injector.getOptionGroup().startInitVimRc();
executeIdeaVimRc(editor);
}
finally {
injector.getOptionGroup().endInitVimRc();
}
}
}
/**
@@ -346,21 +339,20 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
}
private void turnOffPlugin(boolean unsubscribe) {
IjVimSearchGroup searchGroup = getSearchIfCreated();
SearchGroup searchGroup = getSearchIfCreated();
if (searchGroup != null) {
searchGroup.turnOff();
}
if (unsubscribe) {
VimListenerManager.INSTANCE.turnOff();
}
injector.getCommandLine().fullReset();
ExEntryPanel.fullReset();
// Unregister vim actions in command mode
RegisterActions.unregisterActions();
if (onOffDisposable != null) {
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.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 {
override fun dispose() {
// Not sure if this is a best solution
ExEntryPanel.getInstance().editor = null
EditorHolderService.getInstance().editor = null
}
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
* 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 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 keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
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) {
val duration = System.currentTimeMillis() - startTime
LOG.info("VimTypedAction '$charTyped': $duration ms")

View File

@@ -1,32 +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
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action.ex
package com.maddyhome.idea.vim.action
import com.intellij.vim.annotations.CommandOrMotion
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.VimEditor
import com.maddyhome.idea.vim.api.injector
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.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
@CommandOrMotion(keys = [":"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
internal class ExEntryAction : VimActionHandler.SingleExecution() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_START_EX)
override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
if (editor.isOneLineMode()) return false
injector.processGroup.startExEntry(editor, context, cmd)
VimPlugin.getProcess().startExCommand(editor, context, cmd)
return true
}
}

View File

@@ -8,6 +8,6 @@
package com.maddyhome.idea.vim.action
object IntellijCommandProvider : CommandProvider {
public object IntellijCommandProvider : CommandProvider {
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

@@ -44,8 +44,7 @@ import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.ui.ex.ExTextField
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
@@ -60,7 +59,7 @@ import javax.swing.KeyStroke
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
* way to get ideavim keys for this plugin. See VIM-3085
*/
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
private val traceTime: Boolean
get() {
// Make sure the injector is initialized
@@ -80,8 +79,11 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
// Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler?
try {
val start = if (traceTime) System.currentTimeMillis() else null
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState)
KeyHandler.getInstance().handleKey(
editor.vim,
keyStroke,
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
)
if (start != null) {
val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut update '$keyStroke': $duration ms")
@@ -254,14 +256,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
return null
}
private fun getEditor(e: AnActionEvent): Editor? {
return e.getData(PlatformDataKeys.EDITOR)
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
ExEntryPanel.getInstance().editor
} else {
null
}
}
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
/**
* Every time the key pressed with an active lookup, there is a decision:
@@ -385,10 +380,6 @@ private class ActionEnableStatus(
}
}
override fun toString(): String {
return "ActionEnableStatus(isEnabled=$isEnabled, message='$message', logLevel=$logLevel)"
}
companion object {
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.VimCaret
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.setChangeMarks
import com.maddyhome.idea.vim.command.Argument
@@ -22,7 +21,6 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange
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.visual.VimSelection
import com.maddyhome.idea.vim.handler.VimActionHandler
@@ -31,67 +29,21 @@ import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
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
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
val func = injector.globalOptions().operatorfunc
if (func.isEmpty()) {
val operatorFunction = injector.keyGroup.operatorFunction
if (operatorFunction == null) {
VimPlugin.showMessage(MessageHelper.message("E774"))
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 (ex: ExException) {
// Get the argument for function('...') or funcref('...') for the error message
val functionName = if (expression is FunctionCallExpression && expression.arguments.size > 0) {
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 (selectionType) {
SelectionType.LINE_WISE -> "line"
SelectionType.CHARACTER_WISE -> "char"
SelectionType.BLOCK_WISE -> "block"
}
val saveRepeatHandler = VimRepeater.repeatHandler
injector.markService.setChangeMarks(editor.primaryCaret(), textRange)
KeyHandler.getInstance().reset(editor)
val arguments = listOf(SimpleExpression(arg))
handler.executeFunction(arguments, editor, context, scriptContext)
val result = operatorFunction.apply(editor, context, selectionType)
VimRepeater.repeatHandler = saveRepeatHandler
return true
return result
}
@CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL])

View File

@@ -7,9 +7,9 @@
*/
package com.maddyhome.idea.vim.action.change
import com.intellij.openapi.command.CommandProcessor
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.intellij.openapi.command.CommandProcessor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
@@ -58,11 +58,11 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
)
} else if (!repeatHandler && lastCommand != null) {
if (cmd.rawCount > 0) {
lastCommand.rawCount = cmd.count
lastCommand.count = cmd.count
val arg = lastCommand.argument
if (arg != null) {
val mot = arg.motion
mot.rawCount = 0
mot.count = 0
}
}
state.executingCommand = lastCommand

View File

@@ -20,7 +20,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.newapi.ijOptions
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
override val type: Command.Type = Command.Type.DELETE
override fun runAsMulticaret(
editor: VimEditor,

View File

@@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.newapi.ijOptions
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE
override fun execute(
@@ -34,7 +34,7 @@ class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution()
}
injector.editorGroup.notifyIdeaJoin(editor)
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, operatorArguments)) {
res = false
}

View File

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

View File

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

View File

@@ -36,18 +36,6 @@ internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELET
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN) {
override val type: Command.Type = Command.Type.MOTION
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments
): Boolean {
val undo = injector.undo
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
return super.execute(editor, context, cmd, operatorArguments)
}
}
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
@@ -60,18 +48,6 @@ internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB) {
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP) {
override val type: Command.Type = Command.Type.MOTION
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments
): Boolean {
val undo = injector.undo
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
return super.execute(editor, context, cmd, operatorArguments)
}
}
@CommandOrMotion(keys = ["K"], modes = [Mode.NORMAL])

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action.ex
import com.intellij.vim.annotations.CommandOrMotion
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.VimEditor
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.handler.VimActionHandler
import java.util.*
/**
* 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 flags: EnumSet<CommandFlags> = EnumSet.of(CommandFlags.FLAG_COMPLETE_EX)
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
return VimPlugin.getProcess().processExEntry(editor, context)
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.command
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.SelectionType
/**
* COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r
*/
public class CommandState(private val machine: VimStateMachine) {
public val isOperatorPending: Boolean
get() = machine.isOperatorPending
public val mode: CommandState.Mode
get() {
val myMode = machine.mode
return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> CommandState.Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> CommandState.Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> CommandState.Mode.VISUAL
}
}
public val commandBuilder: CommandBuilder
get() = machine.commandBuilder
public val mappingState: MappingState
get() = machine.mappingState
public enum class Mode {
// Basic modes
COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
// Additional modes
OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
}
public enum class SubMode {
NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
}
public companion object {
@JvmStatic
public fun getInstance(editor: Editor): CommandState {
return CommandState(editor.vim.vimStateMachine)
}
}
}
internal val CommandState.SubMode.engine: SelectionType
get() = when (this) {
CommandState.SubMode.NONE -> error("Unexpected value")
CommandState.SubMode.VISUAL_CHARACTER -> SelectionType.CHARACTER_WISE
CommandState.SubMode.VISUAL_LINE -> SelectionType.LINE_WISE
CommandState.SubMode.VISUAL_BLOCK -> SelectionType.BLOCK_WISE
}

View File

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

View File

@@ -9,6 +9,8 @@
package com.maddyhome.idea.vim.common
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.project.Project
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
@@ -37,12 +39,13 @@ internal class IndentConfig private constructor(indentOptions: IndentOptions) {
companion object {
@JvmStatic
fun create(editor: Editor): IndentConfig {
return create(editor, editor.project)
fun create(editor: Editor, context: DataContext): IndentConfig {
return create(editor, PlatformDataKeys.PROJECT.getData(context))
}
@JvmStatic
fun create(editor: Editor, project: Project?): IndentConfig {
@JvmOverloads
fun create(editor: Editor, project: Project? = editor.project): IndentConfig {
val indentOptions = if (project != null) {
CodeStyle.getIndentOptions(project, editor.document)
} else {

View File

@@ -13,56 +13,30 @@ import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
private var isActiveInTestMode = false
override val isActive: Boolean
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.isPanelActive(myEditor)
} else {
isActiveInTestMode
}
/**
* @author vlan
*/
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
override var text: String? = null
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).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) {
ExOutputPanel.getInstance(myEditor).setText(newValue ?: "")
} else {
field = newValue
isActiveInTestMode = !newValue.isNullOrEmpty()
}
}
private set
override fun output(text: String) {
this.text = text
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(text)
}
}
override fun clear() {
text = null
}
override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).close()
}
else {
isActiveInTestMode = false
ExOutputPanel.getInstance(myEditor).deactivate(false)
}
}
companion object {
public companion object {
@JvmStatic
fun getInstance(editor: Editor): ExOutputModel {
public fun getInstance(editor: Editor): ExOutputModel {
var model = editor.vimExOutput
if (model == null) {
model = ExOutputModel(editor)
@@ -70,8 +44,5 @@ class ExOutputModel private constructor(private val myEditor: Editor) : VimExOut
}
return model
}
@JvmStatic
fun tryGetInstance(editor: Editor) = editor.vimExOutput
}
}

View File

@@ -7,40 +7,28 @@
*/
package com.maddyhome.idea.vim.extension
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
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.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.common.CommandAlias
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.noneOfEnum
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry
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.util.*
import javax.swing.KeyStroke
/**
@@ -50,13 +38,13 @@ import javax.swing.KeyStroke
*
* @author vlan
*/
object VimExtensionFacade {
public object VimExtensionFacade {
private val LOG = logger<VimExtensionFacade>()
/** The 'map' command for mapping keys to handlers defined in extensions. */
@JvmStatic
fun putExtensionHandlerMapping(
public fun putExtensionHandlerMapping(
modes: Set<MappingMode>,
fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner,
@@ -67,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
@Deprecated(
"Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
ReplaceWith(
"VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
"com.maddyhome.idea.vim.VimPlugin"
)
)
fun putExtensionHandlerMapping(
public fun putExtensionHandlerMapping(
modes: Set<MappingMode>,
fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner,
@@ -87,7 +73,7 @@ object VimExtensionFacade {
/** The 'map' command for mapping keys to other keys. */
@JvmStatic
fun putKeyMapping(
public fun putKeyMapping(
modes: Set<MappingMode>,
fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner,
@@ -99,7 +85,7 @@ object VimExtensionFacade {
/** The 'map' command for mapping keys to other keys if there is no other mapping to these keys */
@JvmStatic
fun putKeyMappingIfMissing(
public fun putKeyMappingIfMissing(
modes: Set<MappingMode>,
fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner,
@@ -113,7 +99,7 @@ object VimExtensionFacade {
/**
* Equivalent to calling 'command' to set up a user-defined command or alias
*/
fun addCommand(
public fun addCommand(
name: String,
handler: CommandAliasHandler,
) {
@@ -124,7 +110,7 @@ object VimExtensionFacade {
* Equivalent to calling 'command' to set up a user-defined command or alias
*/
@JvmStatic
fun addCommand(
public fun addCommand(
name: String,
minimumNumberOfArguments: Int,
maximumNumberOfArguments: Int,
@@ -134,6 +120,12 @@ object VimExtensionFacade {
.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}'.
* Mappings doesn't work with this function
@@ -142,15 +134,14 @@ object VimExtensionFacade {
* leaves the editor in the insert mode if it's been activated.
*/
@JvmStatic
fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim)
val keyHandler = KeyHandler.getInstance()
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
val context = injector.executionContextManager.onEditor(editor.vim)
keys.forEach { KeyHandler.getInstance().handleKey(editor.vim, it, context, false, false) }
}
/** Returns a single key stroke from the user input similar to 'getchar()'. */
@JvmStatic
fun inputKeyStroke(editor: Editor): KeyStroke {
public fun inputKeyStroke(editor: Editor): KeyStroke {
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
val input = Extension.consumeKeystroke()
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
@@ -161,7 +152,7 @@ object VimExtensionFacade {
LOG.trace("Unit test mode is active")
val mappingStack = KeyHandler.getInstance().keyStack
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
if (injector.registerGroup.isRecording) {
if (editor.vim.vimStateMachine.isRecording) {
KeyHandler.getInstance().modalEntryKeys += it
}
}
@@ -182,99 +173,38 @@ object VimExtensionFacade {
/** Returns a string typed in the input box similar to 'input()'. */
@JvmStatic
fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
public fun inputString(editor: Editor, prompt: String, finishOn: Char?): String {
return service<CommandLineHelper>().inputString(editor.vim, prompt, finishOn) ?: ""
}
/** Get the current contents of the given register similar to 'getreg()'. */
@JvmStatic
fun getRegister(register: Char): List<KeyStroke>? {
public fun getRegister(register: Char): List<KeyStroke>? {
val reg = VimPlugin.getRegister().getRegister(register) ?: return null
return reg.keys
}
@JvmStatic
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
val reg = injector.registerGroup.getRegister(register) ?: return null
public fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
val reg = caret.registerStorage.getRegister(register) ?: return null
return reg.keys
}
/** Set the current contents of the given register */
@JvmStatic
fun setRegister(register: Char, keys: List<KeyStroke?>?) {
public fun setRegister(register: Char, keys: List<KeyStroke?>?) {
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList())
}
/** Set the current contents of the given register */
@JvmStatic
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList())
public fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
}
/** Set the current contents of the given register */
@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)
}
@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
* Please see: https://jb.gg/zo8n0r
*/
interface VimExtensionHandler : ExtensionHandler {
public interface VimExtensionHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
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) {
if (injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(null)).asBoolean()) {
initExtension(extensionBean, name)
PluginState.Util.enabledExtensions.add(name)
PluginState.enabledExtensions.add(name)
} else {
extensionBean.instance.dispose()
}

View File

@@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.extension.argtextobj;
import com.intellij.openapi.editor.Document;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.command.*;
@@ -24,7 +23,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.state.KeyHandlerState;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
import org.jetbrains.annotations.Nls;
@@ -245,18 +244,19 @@ public class VimArgTextObjExtension implements VimExtension {
@Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
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);
//noinspection DuplicatedCode
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
if (!vimStateMachine.isOperatorPending()) {
editor.nativeCarets().forEach((VimCaret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (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);
} else {
InlayHelperKt.moveToInlayAwareOffset(((IjVimCaret)caret).getCaret(), range.getStartOffset());
@@ -265,7 +265,7 @@ public class VimArgTextObjExtension implements VimExtension {
}
});
} else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class))));
}
}

View File

@@ -18,32 +18,30 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiTreeUtil
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
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.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.state.mode.Mode
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.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.toTextRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.extension.ExtensionHandler
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.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
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.helper.PsiHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
@@ -51,19 +49,17 @@ import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.*
internal class CommentaryExtension : VimExtension {
object Util {
companion object {
fun doCommentary(
editor: VimEditor,
context: ExecutionContext,
range: TextRange,
selectionType: SelectionType,
resetCaret: Boolean = true,
resetCaret: Boolean,
): Boolean {
val mode = editor.vimStateMachine.mode
if (mode !is Mode.VISUAL) {
@@ -71,7 +67,8 @@ internal class CommentaryExtension : VimExtension {
}
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) {
listOf(IdeActions.ACTION_COMMENT_LINE, IdeActions.ACTION_COMMENT_BLOCK)
} else {
@@ -116,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,
// 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
// TODO: If we don't move the caret to the start offset, we should maintain the current logical position
if (resetCaret) {
editor.primaryCaret().moveToOffset(range.startOffset)
}
}
}
companion object {
private const val OPERATOR_FUNC = "CommentaryOperatorFunc"
}
override fun getName() = "commentary"
override fun init() {
@@ -153,16 +145,6 @@ internal class CommentaryExtension : VimExtension {
putKeyMapping(MappingMode.N, injector.parser.parseKeys("<Plug>(CommentLine)"), owner, plugCommentaryLineKeys, true)
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)
}
}
/**
@@ -171,23 +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
* 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 fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC
setOperatorFunction(this)
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 {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
val commandState = editor.vimStateMachine
val keyState = KeyHandler.getInstance().keyHandlerState
keyState.commandBuilder.completeCommandPart(Argument(command))
val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
commandState.commandBuilder.completeCommandPart(Argument(command))
}
}
@@ -250,14 +238,8 @@ internal class CommentaryExtension : VimExtension {
* Used like `:1,3Commentary` or `g/fun/Commentary`
*/
private class CommentaryCommandAliasHandler : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
Util.doCommentary(
editor,
context,
range.getLineRange(editor, editor.primaryCaret()).toTextRange(editor),
SelectionType.LINE_WISE,
false
)
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
doCommentary(editor, context, ranges.getTextRange(editor, -1), 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.VimEditor
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.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode
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.extension.ExtensionHandler
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.getRegister
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
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.exportOperatorFunction
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.moveToInlayAwareOffset
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.ij
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
/**
@@ -72,13 +72,30 @@ internal class VimExchangeExtension : VimExtension {
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("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")
// 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) {
editor.getUserData(EXCHANGE_KEY)?.getHighlighter()?.let {
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 {
override val isRepeatable = true
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)
}
}
private class ExchangeClearHandler : ExtensionHandler {
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
// Leave visual mode to create selection marks
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 SelectionType.getString() = when (this) {
SelectionType.CHARACTER_WISE -> "v"
@@ -138,7 +148,7 @@ internal class VimExchangeExtension : VimExtension {
else -> HighlighterTargetArea.EXACT_RANGE
}
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(
ijEditor.getMarkOffset(ex.start),
(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 exchange1 = ijEditor.getUserData(Util.EXCHANGE_KEY)
val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: CHARACTER_WISE)
val exchange1 = ijEditor.getUserData(EXCHANGE_KEY)
if (exchange1 == null) {
val highlighter = highlightExchange(currentExchange)
currentExchange.setHighlighter(highlighter)
ijEditor.putUserData(Util.EXCHANGE_KEY, currentExchange)
ijEditor.putUserData(EXCHANGE_KEY, currentExchange)
return true
} else {
val cmp = compareExchanges(exchange1, currentExchange)
@@ -179,7 +189,7 @@ internal class VimExchangeExtension : VimExtension {
}
}
exchange(ijEditor, ex1, ex2, reverse, expand)
Util.clearExchange(ijEditor)
clearExchange(ijEditor)
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.LafManagerListener
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.markup.EffectType
@@ -19,24 +19,25 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
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.VimProjectService
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.VimExtension
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.listener.VimInsertListener
import com.maddyhome.idea.vim.listener.VimYankListener
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import org.jetbrains.annotations.NonNls
import java.awt.Color
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
private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_duration"
@@ -77,128 +78,111 @@ internal class HighlightColorResetter : LafManagerListener {
*/
internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
private val highlightHandler = HighlightHandler()
private var initialised = false
override fun getName() = "highlightedyank"
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)
VimPlugin.getChange().addInsertListener(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() {
// Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads
VimPlugin.getYank().removeListener(this)
VimPlugin.getChange().removeInsertListener(this)
highlightHandler.clearYankHighlighters()
initialised = false
}
override fun yankPerformed(editor: VimEditor, range: TextRange) {
ensureInitialised()
highlightHandler.highlightYankRange(editor.ij, range)
}
override fun insertModeStarted(editor: Editor) {
ensureInitialised()
highlightHandler.clearYankHighlighters()
}
private fun ensureInitialised() {
if (!initialised) {
registerIdeaVimDisabledCallback()
initialised = true
}
highlightHandler.clearAllYankHighlighters()
}
private class HighlightHandler {
private val alarm = Alarm(ThreadToUse.SWING_THREAD)
private var lastEditor: Editor? = null
private val highlighters = mutableSetOf<RangeHighlighter>()
private var editor: Editor? = null
private val yankHighlighters: MutableSet<RangeHighlighter> = mutableSetOf()
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
clearYankHighlighters()
clearAllYankHighlighters()
lastEditor = editor
this.editor = editor
val project = editor.project
if (project != null) {
Disposer.register(
VimProjectService.getInstance(project),
) {
this.editor = null
yankHighlighters.clear()
}
}
val attributes = getHighlightTextAttributes(editor)
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)
}
}
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.startOffsets[i],
range.endOffsets[i],
range.start,
range.endInclusive,
HighlighterLayer.SELECTION,
attributes,
getHighlightTextAttributes(),
HighlighterTargetArea.EXACT_RANGE,
)
highlighters.add(highlighter)
yankHighlighters.add(highlighter)
setClearHighlightRangeTimer(highlighter)
}
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
val timeout = extractUsersHighlightDuration()
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
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() },
Executors.newSingleThreadScheduledExecutor().schedule(
{
ApplicationManager.getApplication().invokeLater {
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
}
},
timeout,
ModalityState.any()
TimeUnit.MILLISECONDS,
)
}
}
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()
}
private fun getHighlightTextAttributes(editor: Editor) = TextAttributes(
private fun getHighlightTextAttributes() = TextAttributes(
null,
extractUsersHighlightColor(),
editor.colorsScheme.getColor(EditorColors.CARET_COLOR),
editor?.colorsScheme?.getColor(EditorColors.CARET_COLOR),
EffectType.SEARCH_MATCH,
Font.PLAIN,
)
private fun extractUsersHighlightDuration(): Int {
private fun extractUsersHighlightDuration(): Long {
return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) {
// toVimNumber will return 0 for an invalid string. Let's force it to throw
when (it) {
is VimString -> it.value.toInt()
else -> it.toVimNumber().value
}
it.toLong()
}
}
private fun extractUsersHighlightColor(): Color {
return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value ->
val rgba = value.asString()
val rgba = value
.substring(4)
.filter { it != '(' && it != ')' && !it.isWhitespace() }
.split(',')
@@ -208,11 +192,12 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
}
}
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)
if (value != null) {
if (value is VimString) {
return try {
extractFun(value)
extractFun(value.value)
} catch (e: Exception) {
@VimNlsSafe val message = MessageHelper.message(
"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.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
@@ -41,6 +40,7 @@ import com.maddyhome.idea.vim.handler.toMotionOrError
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
@@ -91,23 +91,22 @@ internal class Matchit : VimExtension {
private class MatchitHandler(private val reverse: Boolean) : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val keyHandler = KeyHandler.getInstance()
val keyState = keyHandler.keyHandlerState
val count = keyState.commandBuilder.count
val commandState = editor.vimStateMachine
val count = commandState.commandBuilder.count
// 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
// pending mode, we want to include the entire match. isInOpPending makes that distinction.
val isInOpPending = keyHandler.isOperatorPending(editor.mode, keyState)
val isInOpPending = commandState.isOperatorPending
if (isInOpPending) {
val matchitAction = MatchitAction()
matchitAction.reverse = reverse
matchitAction.isInOpPending = true
keyState.commandBuilder.completeCommandPart(
commandState.commandBuilder.completeCommandPart(
Argument(
Command(
count,
@@ -218,8 +217,6 @@ private object FileTypePatterns {
return if (fileTypeName in htmlLikeFileTypes) {
this.htmlPatterns
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
this.javaPatterns
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
this.rubyPatterns
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
@@ -234,7 +231,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns
} else {
this.htmlPatterns
return null
}
}
@@ -245,7 +242,6 @@ private object FileTypePatterns {
)
private val htmlPatterns = createHtmlPatterns()
private val javaPatterns = createJavaPatterns()
private val rubyPatterns = createRubyPatterns()
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
private val phpPatterns = createPhpPatterns()
@@ -275,14 +271,6 @@ private object FileTypePatterns {
)
}
private fun createJavaPatterns(): LanguagePatterns {
return (
LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") +
LanguagePatterns("\\bdo\\b", "\\bwhile\\b") +
LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b")
)
}
private fun createRubyPatterns(): LanguagePatterns {
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
// We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries.

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.group.visual.vimSetSelection
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.SearchHelper
import com.maddyhome.idea.vim.helper.SearchOptions
import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.findWordUnderCursor
import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.userData
@@ -235,7 +235,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
val text = if (editor.inVisualMode) {
primaryCaret.selectedText ?: return
} else {
val range = findWordUnderCursor(editor, primaryCaret) ?: return
val range = SearchHelper.findWordUnderCursor(editor, primaryCaret) ?: return
if (range.startOffset > primaryCaret.offset) return
IjVimEditor(editor).getText(range)
}
@@ -300,7 +300,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
}
private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? {
val range = findWordUnderCursor(editor, caret) ?: return null
val range = SearchHelper.findWordUnderCursor(editor, caret) ?: return null
if (range.startOffset > caret.offset) return null
enterVisualMode(editor.vim)
@@ -327,6 +327,6 @@ internal class VimMultipleCursorsExtension : VimExtension {
private fun makePattern(text: String, whole: Boolean): String {
// Pattern is "very nomagic" (ignore regex chars) and "force case sensitive". This is vim-multiple-cursors behaviour
return "\\V\\C" + 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.common.CommandAlias
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.group.KeyGroup
import com.maddyhome.idea.vim.helper.MessageHelper
@@ -130,32 +130,32 @@ internal class NerdTree : VimExtension {
addCommand("NERDTreeFind", IjCommandHandler("SelectInProjectView"))
addCommand("NERDTreeRefreshRoot", IjCommandHandler("Synchronize"))
synchronized(Util.monitor) {
Util.commandsRegistered = true
synchronized(monitor) {
commandsRegistered = true
ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
}
}
class IjCommandHandler(private val actionId: String) : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
Util.callAction(editor, actionId, context)
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
callAction(editor, actionId, context)
}
}
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 toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
if (toolWindow.isVisible) {
toolWindow.hide()
} else {
Util.callAction(editor, "ActivateProjectToolWindow", context)
callAction(editor, "ActivateProjectToolWindow", context)
}
}
}
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 toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
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?
class NerdStartupActivity : ProjectActivity {
override suspend fun execute(project: Project) {
synchronized(Util.monitor) {
if (!Util.commandsRegistered) return
synchronized(monitor) {
if (!commandsRegistered) return
installDispatcher(project)
}
}
@@ -214,7 +214,7 @@ internal class NerdTree : VimExtension {
val action = nextNode.actionHolder
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) }
}
}
@@ -356,7 +356,7 @@ internal class NerdTree : VimExtension {
currentWindow?.split(SwingConstants.VERTICAL, true, file, true)
// FIXME: 22.01.2021 This solution bouncing a bit
Util.callAction(null, "ActivateProjectToolWindow", context.vim)
callAction(null, "ActivateProjectToolWindow", context.vim)
},
)
registerCommand(
@@ -368,7 +368,7 @@ internal class NerdTree : VimExtension {
val currentWindow = splitters.currentWindow
currentWindow?.split(SwingConstants.HORIZONTAL, true, file, true)
Util.callAction(null, "ActivateProjectToolWindow", context.vim)
callAction(null, "ActivateProjectToolWindow", context.vim)
},
)
registerCommand(
@@ -477,10 +477,6 @@ internal class NerdTree : VimExtension {
"r",
NerdAction.ToIj("SynchronizeCurrentFile"),
)
registerCommand("NERDTreeMapToggleHidden", "I", NerdAction.ToIj("ProjectView.ShowExcludedFiles"))
registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile"))
registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir"))
registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete"))
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
@@ -508,9 +504,14 @@ internal class NerdTree : VimExtension {
)
}
object Util {
companion object {
const val pluginName = "NERDTree"
internal val monitor = Any()
internal var commandsRegistered = false
private val LOG = logger<NerdTree>()
fun callAction(editor: VimEditor?, name: String, context: ExecutionContext) {
val action = ActionManager.getInstance().getAction(name) ?: run {
VimPlugin.showMessage(MessageHelper.message("action.not.found.0", name))
@@ -525,13 +526,6 @@ internal class NerdTree : VimExtension {
}
}
}
}
companion object {
const val pluginName = "NERDTree"
private val LOG = logger<NerdTree>()
}
}
private fun addCommand(alias: String, handler: CommandAliasHandler) {
VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
@@ -551,9 +545,9 @@ 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()
@@ -565,11 +559,12 @@ private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
}
private fun installDispatcher(project: Project) {
val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
val shortcuts =
collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
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,
)
}
}
}

View File

@@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.extension.paragraphmotion
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.VimEditor
import com.maddyhome.idea.vim.api.injector
@@ -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.putKeyMappingIfMissing
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.vim
import javax.swing.KeyStroke
internal class ParagraphMotion : VimExtension {
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>(ParagraphPrevMotion)"), owner, ParagraphMotionHandler(-1), false)
putKeyMappingIfMissingFromAndToKeys(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>(ParagraphNextMotion)"), true)
putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true)
}
private class ParagraphMotionHandler(private val count: Int) : ExtensionHandler {
@@ -52,17 +49,4 @@ internal class ParagraphMotion : VimExtension {
?.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,37 +8,37 @@
package com.maddyhome.idea.vim.extension.replacewithregister
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
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.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.extension.ExtensionHandler
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.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.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.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
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
internal class ReplaceWithRegister : VimExtension {
@@ -53,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("grr"), owner, injector.parser.parseKeys(RWR_LINE), true)
putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_VISUAL), true)
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
}
private class RwrVisual : ExtensionHandler {
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 ->
val selectionStart = caret.selectionStart
val selectionEnd = caret.selectionEnd
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()
}
@@ -75,7 +73,7 @@ internal class ReplaceWithRegister : VimExtension {
override val isRepeatable: Boolean = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC
setOperatorFunction(Operator())
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
}
@@ -93,7 +91,7 @@ internal class ReplaceWithRegister : VimExtension {
val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor)
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 ->
@@ -114,14 +112,14 @@ internal class ReplaceWithRegister : VimExtension {
editor.primaryCaret() to VimSelection.create(
range.startOffset,
range.endOffset - 1,
selectionType ?: SelectionType.CHARACTER_WISE,
selectionType ?: CHARACTER_WISE,
editor,
),
),
selectionType ?: SelectionType.CHARACTER_WISE,
selectionType ?: CHARACTER_WISE,
)
// todo multicaret
doReplace(ijEditor, context.ij, editor.primaryCaret(), visualSelection)
doReplace(ijEditor, editor.primaryCaret(), visualSelection)
return true
}
@@ -134,17 +132,19 @@ internal class ReplaceWithRegister : VimExtension {
}
companion object {
@NonNls private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
@NonNls private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine"
@NonNls private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
@NonNls private const val OPERATOR_FUNC = "ReplaceWithRegisterOperatorFunc"
}
}
@NonNls
private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
@NonNls
private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine"
@NonNls
private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
private fun doReplace(editor: Editor, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
val registerGroup = injector.registerGroup
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
val savedRegister = registerGroup.getRegister(lastRegisterChar) ?: return
val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
var usedType = savedRegister.type
var usedText = savedRegister.text
@@ -165,15 +165,13 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
caretAfterInsertedText = false,
putToLine = -1,
)
val vimEditor = editor.vim
val keyHandler = KeyHandler.getInstance()
ClipboardOptionHelper.IdeaputDisabler().use {
VimPlugin.getPut().putText(
vimEditor,
context.vim,
IjVimEditor(editor),
injector.executionContextManager.onEditor(editor.vim),
putData,
operatorArguments = OperatorArguments(
keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState),
editor.vimStateMachine?.isOperatorPending ?: false,
0,
editor.vim.mode,
),
@@ -181,3 +179,5 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
)
}
}
}
}

View File

@@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.extension.sneak
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.editor.colors.EditorColors
@@ -18,7 +19,6 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimProjectService
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
@@ -30,17 +30,16 @@ import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionHandler
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.newapi.ij
import java.awt.Font
import java.awt.event.KeyEvent
import java.util.*
import javax.swing.Timer
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
private const val DEFAULT_HIGHLIGHT_DURATION_SNEAK = 300
private const val DEFAULT_HIGHLIGHT_DURATION_SNEAK: Long = 300
// By [Mikhail Levchenko](https://github.com/Mishkun)
// Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
@@ -49,20 +48,17 @@ internal class IdeaVimSneakExtension : VimExtension {
override fun init() {
val highlightHandler = HighlightHandler()
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD), MappingMode.NXO)
// 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)
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD))
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD))
// workaround to support ; and , commands
mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"), MappingMode.NXO)
mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"), MappingMode.NXO)
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"), MappingMode.NXO)
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"), MappingMode.NXO)
mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"))
mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"))
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"))
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"))
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO)
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO)
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL))
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE))
}
private class SneakHandler(
@@ -118,7 +114,7 @@ internal class IdeaVimSneakExtension : VimExtension {
var lastSymbols: String = ""
fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? {
val caret = editor.primaryCaret()
val position = caret.offset
val position = caret.offset.point
val chars = editor.text()
val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo)
if (foundPosition != null) {
@@ -254,13 +250,11 @@ internal class IdeaVimSneakExtension : VimExtension {
}
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
val timer = Timer(DEFAULT_HIGHLIGHT_DURATION_SNEAK) {
if (editor?.isDisposed != true) {
editor?.markupModel?.removeHighlighter(highlighter)
Executors.newSingleThreadScheduledExecutor().schedule({
ApplicationManager.getApplication().invokeLater {
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
}
}
timer.isRepeats = false
timer.start()
}, DEFAULT_HIGHLIGHT_DURATION_SNEAK, TimeUnit.MILLISECONDS)
}
private fun getHighlightTextAttributes() = TextAttributes(
@@ -277,51 +271,21 @@ internal class IdeaVimSneakExtension : VimExtension {
* Map some <Plug>(keys) command to given handler
* and create mapping to <Plug>(prefix)[keys]
*/
private fun VimExtension.mapToFunctionAndProvideKeys(
keys: String, handler: ExtensionHandler, mappingModes: EnumSet<MappingMode>
) {
private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: ExtensionHandler) {
VimExtensionFacade.putExtensionHandlerMapping(
mappingModes,
MappingMode.NXO,
injector.parser.parseKeys(command(keys)),
owner,
handler,
false
)
VimExtensionFacade.putExtensionHandlerMapping(
mappingModes,
injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
VimExtensionFacade.putKeyMapping(
MappingMode.NXO,
injector.parser.parseKeys(keys),
owner,
handler,
false
)
// This is a combination to meet the following requirements:
// - Now we should support mappings from sneak `Sneak_s` and mappings from the previous version of the plugin `(sneak-s)`
// - 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
val fromKeys = injector.parser.parseKeys(keys)
val filteredModes = mappingModes.filterNotTo(HashSet()) {
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(command(keys)))
}
val filteredModes2 = mappingModes.filterNotTo(HashSet()) {
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys)))
}
val filteredFromModes = mappingModes.filterNotTo(HashSet()) {
injector.keyGroup.hasmapfrom(it, fromKeys)
}
val doubleFiltered = mappingModes
.filter { it in filteredModes2 && it in filteredModes && it in filteredFromModes }
.toSet()
putKeyMapping(doubleFiltered, fromKeys, owner, injector.parser.parseKeys(command(keys)), true)
putKeyMapping(
doubleFiltered,
fromKeys,
owner,
injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
injector.parser.parseKeys(command(keys)),
true
)
}
private fun command(keys: String) = "<Plug>(sneak-$keys)"
private fun commandFromOriginalPlugin(keys: String) = "<Plug>Sneak_$keys"

View File

@@ -1,30 +0,0 @@
package com.maddyhome.idea.vim.extension.surround
import com.intellij.util.text.CharSequenceSubSequence
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
override val length = text.length * count
override fun get(index: Int): Char {
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
return text[index % text.length]
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return CharSequenceSubSequence(this, startIndex, endIndex)
}
override fun toString(): String {
return text.repeat(count)
}
companion object {
fun of(text: CharSequence, count: Int): CharSequence {
return when (count) {
0 -> ""
1 -> text
else -> RepeatedCharSequence(text, count)
}
}
}
}

View File

@@ -7,18 +7,15 @@
*/
package com.maddyhome.idea.vim.extension.surround
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine
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.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode
@@ -26,26 +23,22 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegisterForCaret
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
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.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
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.mode
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent
@@ -81,15 +74,13 @@ internal class VimSurroundExtension : VimExtension {
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)
}
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
}
private class YSurroundHandler : ExtensionHandler {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC
setOperatorFunction(Operator())
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
}
@@ -101,7 +92,7 @@ internal class VimSurroundExtension : VimExtension {
val ijEditor = editor.ij
val c = getChar(ijEditor)
if (c.code == 0) return
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return
val pair = getOrInputPair(c, ijEditor) ?: return
editor.forEachCaret {
val line = it.getBufferPosition().line
@@ -110,7 +101,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it, count = operatorArguments.count1)
performSurround(pair, range, it)
}
// it.moveToOffset(lineStartOffset)
}
@@ -130,13 +121,15 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
return
}
runWriteAction {
// Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
editor.ij.caretModel.moveToOffset(selectionStart)
}
}
}
@@ -151,16 +144,12 @@ internal class VimSurroundExtension : VimExtension {
val charTo = getChar(editor.ij)
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) }
}
companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
}
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
// Save old register values for carets
val surroundings = editor.sortedCarets()
.map {
@@ -228,12 +217,12 @@ internal class VimSurroundExtension : VimExtension {
val searchHelper = injector.searchHelper
return when (char) {
't' -> searchHelper.findBlockTagRange(editor, caret, 1, true)
'(', ')', 'b' -> findBlockRange(editor, caret, '(', 1, true)
'[', ']' -> findBlockRange(editor, caret, '[', 1, true)
'{', '}', 'B' -> findBlockRange(editor, caret, '{', 1, true)
'<', '>' -> findBlockRange(editor, caret, '<', 1, true)
'(', ')', 'b' -> searchHelper.findBlockRange(editor, caret, '(', 1, true)
'[', ']' -> searchHelper.findBlockRange(editor, caret, '[', 1, true)
'{', '}', 'B' -> searchHelper.findBlockRange(editor, caret, '{', 1, true)
'<', '>' -> searchHelper.findBlockRange(editor, caret, '<', 1, true)
'`', '\'', '"' -> {
val caretOffset = caret.offset
val caretOffset = caret.offset.point
val text = editor.text()
if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) {
TextRange(caretOffset - 1, caretOffset + 1)
@@ -268,42 +257,21 @@ internal class VimSurroundExtension : VimExtension {
}
}
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = vimEditor.ij
private class Operator : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij
val c = getChar(ijEditor)
if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
runWriteAction {
val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
ijEditor.runWithEveryCaretAndRestore {
applyOnce(ijEditor, change, pair, count)
}
}
else {
applyOnce(ijEditor, change, pair, count)
val pair = getOrInputPair(c, ijEditor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor.currentCaret()) ?: return false
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
}
}
return true
}
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
// XXX: Will it work with line-wise or block-wise selections?
val primaryCaret = editor.caretModel.primaryCaret
val range = getSurroundRange(primaryCaret.vim)
if (range != null) {
val start = RepeatedCharSequence.of(pair.first, count)
val end = RepeatedCharSequence.of(pair.second, count)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
}
}
private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor
val ijEditor = editor.ij
@@ -320,8 +288,6 @@ private val LOG = logger<VimSurroundExtension>()
private const val REGISTER = '"'
private const val OPERATOR_FUNC = "SurroundOperatorFunc"
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val SURROUND_PAIRS = mapOf(
@@ -348,8 +314,8 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
null
}
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
val tagInput = inputString(editor, context, "<", '>')
private fun inputTagPair(editor: Editor): Pair<String, String>? {
val tagInput = inputString(editor, "<", '>')
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) {
val tagName = matcher.group(1)
@@ -362,18 +328,17 @@ private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, Str
private fun inputFunctionName(
editor: Editor,
context: DataContext,
withInternalSpaces: Boolean,
): Pair<String, String>? {
val functionNameInput = inputString(editor, context, "function: ", null)
val functionNameInput = inputString(editor, "function: ", null)
if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
}
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor, context)
'f' -> inputFunctionName(editor, context, false)
'F' -> inputFunctionName(editor, context, true)
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor)
'f' -> inputFunctionName(editor, false)
'F' -> inputFunctionName(editor, true)
else -> getSurroundPair(c)
}
@@ -389,15 +354,15 @@ private fun getChar(editor: Editor): Char {
return res
}
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
runWriteAction {
val editor = caret.editor
val change = VimPlugin.getChange()
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
val isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine()
val rightSurround = (if (tagsOnNewLines) {
val rightSurround = if (tagsOnNewLines) {
if (isEOF && !hasNewLine) {
"\n" + pair.second
} else {
@@ -405,7 +370,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
}
} else {
pair.second
}).let { RepeatedCharSequence.of(it, count) }
}
change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)

View File

@@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.extension.textobjentire;
import com.intellij.openapi.editor.Caret;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.api.ExecutionContext;
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
import com.maddyhome.idea.vim.api.VimEditor;
@@ -24,7 +23,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.state.KeyHandlerState;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -134,18 +133,17 @@ public class VimTextObjEntireExtension implements VimExtension {
@Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(editor);
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
//noinspection DuplicatedCode
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
if (!vimStateMachine.isOperatorPending()) {
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) {
if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(new IjVimCaret(caret), range.getStartOffset(), range.getEndOffset() - 1, true);
} else {
InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
@@ -155,7 +153,7 @@ public class VimTextObjEntireExtension implements VimExtension {
});
} else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION,
EnumSet.noneOf(CommandFlags.class))));
}

View File

@@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.extension.textobjindent;
import com.intellij.openapi.editor.Caret;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.api.ExecutionContext;
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
import com.maddyhome.idea.vim.api.VimEditor;
@@ -25,7 +24,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.state.KeyHandlerState;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -132,11 +131,10 @@ public class VimIndentObject implements VimExtension {
// This is done as a separate step so that it works even when the caret is inside the indentation.
int offset = caretLineStartOffset;
int indentSize = 0;
while (offset < charSequence.length()) {
while (++offset < charSequence.length()) {
final char ch = charSequence.charAt(offset);
if (ch == ' ' || ch == '\t') {
++indentSize;
++offset;
} else {
break;
}
@@ -264,18 +262,17 @@ public class VimIndentObject implements VimExtension {
@Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
IjVimEditor vimEditor = (IjVimEditor)editor;
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
if (!vimStateMachine.isOperatorPending()) {
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) {
if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
EngineVisualGroupKt.vimSetSelection(new IjVimCaret(caret), range.getStartOffset(), range.getEndOffset() - 1, true);
} else {
InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
@@ -285,7 +282,7 @@ public class VimIndentObject implements VimExtension {
});
} else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION,
EnumSet.noneOf(CommandFlags.class))));
}

View File

@@ -57,9 +57,8 @@ import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.NumberType
import com.maddyhome.idea.vim.helper.SearchHelper
import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.findNumberUnderCursor
import com.maddyhome.idea.vim.helper.findNumbersInRange
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
@@ -69,22 +68,22 @@ import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly
import java.math.BigInteger
import java.util.*
import java.util.function.Consumer
import kotlin.math.max
import kotlin.math.min
/**
* Provides all the insert/replace related functionality
*/
class ChangeGroup : VimChangeGroupBase() {
public class ChangeGroup : VimChangeGroupBase() {
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
private val listener: EditorMouseListener = object : EditorMouseListener {
override fun mouseClicked(event: EditorMouseEvent) {
@@ -95,7 +94,7 @@ class ChangeGroup : VimChangeGroupBase() {
}
}
fun editorCreated(editor: Editor?, disposable: Disposable) {
public fun editorCreated(editor: Editor?, disposable: Disposable) {
EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
}
@@ -103,9 +102,6 @@ class ChangeGroup : VimChangeGroupBase() {
val editor = (vimEditor as IjVimEditor).editor
val ijContext = context.ij
val doc = vimEditor.editor.document
val undo = injector.undo
val nanoTime = System.nanoTime()
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
CommandProcessor.getInstance().executeCommand(
editor.project, {
ApplicationManager.getApplication()
@@ -201,7 +197,7 @@ class ChangeGroup : VimChangeGroupBase() {
val allowWrap = injector.options(editor).whichwrap.contains("~")
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
if (motion is Motion.Error) return false
changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
changeCase(editor, caret, caret.offset.point, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
motion = injector.motion.getHorizontalMotion(
editor,
caret,
@@ -239,7 +235,8 @@ class ChangeGroup : VimChangeGroupBase() {
}
val lineLength = editor.lineLength(line)
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column)
val pad =
EditorHelper.pad((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context, line, column)
val offset = editor.getLineEndOffset(line)
insertText(editor, caret, offset, pad)
}
@@ -398,7 +395,6 @@ class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext,
range: TextRange,
) {
val startPos = editor.offsetToBufferPosition(caret.offset)
val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor
@@ -423,7 +419,11 @@ class ChangeGroup : VimChangeGroupBase() {
}
}
val afterAction = {
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
val firstLine = editor.offsetToBufferPosition(
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
).line
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
}
if (project != null) {
@@ -454,7 +454,7 @@ class ChangeGroup : VimChangeGroupBase() {
dir: Int,
operatorArguments: OperatorArguments,
) {
val start = caret.offset
val start = caret.offset.point
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
}
@@ -488,7 +488,7 @@ class ChangeGroup : VimChangeGroupBase() {
// Remember the current caret column
val intendedColumn = caret.vimLastColumn
val indentConfig = create((editor as IjVimEditor).editor)
val indentConfig = create((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context)
val sline = editor.offsetToBufferPosition(range.startOffset).line
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
@@ -535,7 +535,7 @@ class ChangeGroup : VimChangeGroupBase() {
val soff = editor.getLineStartOffset(l)
val eoff = editor.getLineEndOffset(l, true)
val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l)
val col = editor.offsetToBufferPosition(woff).column
val col = editor.offsetToVisualPosition(woff).column
val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble())
.toInt()
if (col > 0 || soff != eoff) {
@@ -571,68 +571,54 @@ class ChangeGroup : VimChangeGroupBase() {
): Boolean {
val startLine = range.startLine
val endLine = range.endLine
val count = range.size
val count = endLine - startLine + 1
if (count < 2) {
return false
}
val startOffset = editor.getLineStartOffset(startLine)
val endOffset = editor.getLineEndOffset(endLine)
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(startOffset, endOffset))
val lines = selectedText.split("\n")
val modifiedLines = sortOptions.pattern?.let {
if (sortOptions.sortOnPattern) {
extractPatternFromLines(editor, lines, startLine, it)
} else {
deletePatternFromLines(editor, lines, startLine, it)
return sortTextRange(editor, caret, startOffset, endOffset, lineComparator, sortOptions)
}
} ?: lines
val sortedLines = lines.zip(modifiedLines)
.sortedWith { l1, l2 -> lineComparator.compare(l1.second, l2.second) }
.map {it.first}
.toMutableList()
if (sortOptions.unique) {
val iterator = sortedLines.iterator()
/**
* Sorts a text range with a comparator. Returns true if a replace was performed, false otherwise.
*
* @param editor The editor to replace text in
* @param start The starting position for the sort
* @param end The ending position for the sort
* @param lineComparator The comparator to use to sort
* @param sortOption The option to sort the range
* @return true if able to sort the text, false if not
*/
private fun sortTextRange(
editor: VimEditor,
caret: VimCaret,
start: Int,
end: Int,
lineComparator: Comparator<String>,
sortOption: SortOption,
): Boolean {
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(start, end))
val lines: MutableList<String> = selectedText.split("\n").sortedWith(lineComparator).toMutableList()
if (sortOption.unique) {
val iterator = lines.iterator()
var previous: String? = null
while (iterator.hasNext()) {
val current = iterator.next()
if (current == previous || sortOptions.ignoreCase && current.equals(previous, ignoreCase = true)) {
if (current == previous || sortOption.ignoreCase && current.equals(previous, ignoreCase = true)) {
iterator.remove()
} else {
previous = current
}
}
}
if (sortedLines.isEmpty()) {
if (lines.size < 1) {
return false
}
replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n"))
replaceText(editor, caret, start, end, StringUtil.join(lines, "\n"))
return true
}
private fun extractPatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> result.value
is VimMatchResult.Failure -> line
}
}
}
private fun deletePatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> line.substring(result.value.length, line.length)
is VimMatchResult.Failure -> line
}
}
}
/**
* Perform increment and decrement for numbers in visual mode
*
@@ -654,7 +640,7 @@ class ChangeGroup : VimChangeGroupBase() {
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val numberRanges = findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
val numberRanges = SearchHelper.findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
val newNumbers: MutableList<String?> = ArrayList()
for (i in numberRanges.indices) {
val numberRange = numberRanges[i]
@@ -677,7 +663,8 @@ class ChangeGroup : VimChangeGroupBase() {
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val range = findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
val range =
SearchHelper.findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
if (range == null) {
logger.debug("no number on line")
return false
@@ -786,11 +773,11 @@ class ChangeGroup : VimChangeGroupBase() {
return number
}
fun addInsertListener(listener: VimInsertListener) {
public fun addInsertListener(listener: VimInsertListener) {
insertListeners.add(listener)
}
fun removeInsertListener(listener: VimInsertListener) {
public fun removeInsertListener(listener: VimInsertListener) {
insertListeners.remove(listener)
}

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