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
430 changed files with 59325 additions and 89505 deletions

1
.gitattributes vendored
View File

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

View File

@@ -11,7 +11,7 @@ on:
jobs: jobs:
build: build:
if: false if: github.event.pull_request.merged == true && github.repository == 'JetBrains/ideavim'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

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

@@ -7,12 +7,15 @@ on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: '0 10 * * *' - cron: '0 10 * * *'
# Workflow run on push is disabled to avoid conflicts when merging PR
# push:
# branches: [ master ]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: false if: github.repository == 'JetBrains/ideavim'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

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

View File

@@ -25,7 +25,6 @@ object Project : Project({
// Active tests // Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2023.3", "<default>", version = "2023.3")) buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
buildType(TestingBuildType("2024.1", "<default>"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased) buildType(PropertyBased)

View File

@@ -34,6 +34,7 @@ object Compatibility : IdeaVimBuildType({
java --version 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 '${'$'}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 '${'$'}eu.theblob42.idea.whichkey' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city
# Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city # Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city

View File

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

View File

@@ -39,7 +39,7 @@ object Nvim : IdeaVimBuildType({
""".trimIndent() """.trimIndent()
} }
gradle { gradle {
tasks = "clean test -Dnvim" tasks = "clean testWithNeovim"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
} }

View File

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

View File

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

View File

@@ -97,14 +97,14 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Set TeamCity build number" name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber" tasks = "scripts:setTeamCityBuildNumber"
} }
// gradle { gradle {
// name = "Update change log" name = "Update change log"
// tasks = "scripts:changelogUpdateUnreleased" tasks = "scripts:changelogUpdateUnreleased"
// } }
// gradle { gradle {
// name = "Commit preparation changes" name = "Commit preparation changes"
// tasks = "scripts:commitChanges" tasks = "scripts:commitChanges"
// } }
gradle { gradle {
name = "Add release tag" name = "Add release tag"
tasks = "scripts:addReleaseTag" tasks = "scripts:addReleaseTag"
@@ -117,24 +117,33 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Publish release" name = "Publish release"
tasks = "publishPlugin" tasks = "publishPlugin"
} }
// script { script {
// name = "Checkout master branch" name = "Checkout master branch"
// scriptContent = """ scriptContent = """
// echo Checkout master echo Checkout master
// git checkout master git checkout master
// """.trimIndent() """.trimIndent()
// } }
// gradle { gradle {
// name = "Update change log in master" name = "Update change log in master"
// tasks = "scripts:changelogUpdateUnreleased" tasks = "scripts:changelogUpdateUnreleased"
// } }
// gradle { gradle {
// name = "Commit preparation changes in master" name = "Commit preparation changes in master"
// tasks = "scripts:commitChanges" tasks = "scripts:commitChanges"
// } }
script { script {
name = "Push changes to the repo" name = "Push changes to the repo"
scriptContent = """ scriptContent = """
branch=$(git branch --show-current)
echo Current branch is ${'$'}branch
if [ "master" != "${'$'}branch" ];
then
git checkout master
fi
git push origin
git checkout release git checkout release
echo checkout release branch echo checkout release branch
git branch --set-upstream-to=origin/release release git branch --set-upstream-to=origin/release release

View File

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

View File

@@ -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 = 'PublishVimEngine'
accordingly, and delete the patch script.
*/
changeBuildType(RelativeId("PublishVimEngine")) {
vcs {
check(branchFilter == "+:<default>") {
"Unexpected option value: branchFilter = $branchFilter"
}
branchFilter = "+:fleet"
}
}

View File

@@ -495,14 +495,6 @@ Contributors:
[![icon][github]](https://github.com/emanuelgestosa) [![icon][github]](https://github.com/emanuelgestosa)
&nbsp; &nbsp;
Emanuel Gestosa Emanuel Gestosa
* [![icon][mail]](mailto:81118900+lippfi@users.noreply.github.com)
[![icon][github]](https://github.com/lippfi)
&nbsp;
lippfi,
* [![icon][mail]](mailto:fillipser143@gmail.com)
[![icon][github]](https://github.com/Parker7123)
&nbsp;
FilipParker
Previous contributors: 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 Please note that the quality of EAP versions may at times be way below even
usual beta standards. usual beta standards.
## End of changelog file maintenance ## To Be Released
Since version 2.9.0, the changelog can be found on YouTrack
To Be Released: https://youtrack.jetbrains.com/issues/VIM?q=%23%7BReady%20To%20Release%7D%20
Latest Fixes: https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20sort%20by:%20updated%20
## 2.9.0, 2024-02-20
### Fixes: ### Fixes:
* [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot * [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot
### Merged PRs: ### 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 ## 2.8.0, 2024-01-30

View File

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

View File

@@ -32,6 +32,7 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.exceptions.MissingVersionException
import org.kohsuke.github.GHUser import org.kohsuke.github.GHUser
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@@ -48,14 +49,14 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r") classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.10") classpath("io.ktor:ktor-client-core:2.3.7")
classpath("io.ktor:ktor-client-cio:2.3.10") classpath("io.ktor:ktor-client-cio:2.3.7")
classpath("io.ktor:ktor-client-auth:2.3.10") classpath("io.ktor:ktor-client-auth:2.3.7")
classpath("io.ktor:ktor-client-content-negotiation:2.3.10") classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.10") classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
// This comes from the changelog plugin // This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1") // classpath("org.jetbrains:markdown:0.3.1")
@@ -69,11 +70,14 @@ plugins {
application application
id("java-test-fixtures") 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" 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("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "3.0.0" id("com.dorongold.task-tree") version "2.1.1"
id("com.google.devtools.ksp") version "1.9.22-1.0.17" id("com.google.devtools.ksp") version "1.9.22-1.0.17"
} }
@@ -100,8 +104,8 @@ val ideaVersion: String by project
val ideaType: String by project val ideaType: String by project
val downloadIdeaSources: String by project val downloadIdeaSources: String by project
val instrumentPluginCode: String by project val instrumentPluginCode: String by project
val antlrVersion: String by project
val remoteRobotVersion: String by project val remoteRobotVersion: String by project
val antlrVersion: String by project
val publishChannels: String by project val publishChannels: String by project
val publishToken: String by project val publishToken: String by project
@@ -141,14 +145,19 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1") testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2") testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2") testImplementation("com.intellij.remoterobot:ide-launcher:$remoteRobotVersion")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2") 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 { configurations {
@@ -157,17 +166,74 @@ configurations {
} }
} }
// --- 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 { tasks {
test { // 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. // Set teamcity env variable locally to run additional tests for leaks.
// By default, this test runs on TC only, but this test doesn't take a lot of time, // By default, this test runs on TC only, but this test doesn't take a lot of time,
// so we can turn it on for local development // so we can turn it on for local development
if (environment["TEAMCITY_VERSION"] == null) { 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") 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 { compileJava {
@@ -187,7 +253,6 @@ tasks {
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
compileTestKotlin { compileTestKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = javaVersion jvmTarget = javaVersion
@@ -195,23 +260,6 @@ tasks {
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
downloadRobotServerPlugin {
version.set(remoteRobotVersion)
}
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)
}
runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
}
} }
java { java {
@@ -238,7 +286,6 @@ gradle.projectsEvaluated {
intellij { intellij {
version.set(ideaVersion) version.set(ideaVersion)
type.set(ideaType)
pluginName.set("IdeaVim") pluginName.set("IdeaVim")
updateSinceUntilBuild.set(false) updateSinceUntilBuild.set(false)
@@ -250,6 +297,10 @@ intellij {
} }
tasks { tasks {
downloadRobotServerPlugin {
version.set(remoteRobotVersion)
}
publishPlugin { publishPlugin {
channels.set(publishChannels.split(",")) channels.set(publishChannels.split(","))
token.set(publishToken) token.set(publishToken)
@@ -261,9 +312,18 @@ tasks {
password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD")) 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")
}
runPluginVerifier { runPluginVerifier {
downloadDir.set("${project.buildDir}/pluginVerifier/ides") downloadDir.set("${project.buildDir}/pluginVerifier/ides")
teamCityOutputFormat.set(true) teamCityOutputFormat.set(true)
// ideVersions.set(listOf("IC-2021.3.4"))
} }
generateGrammarSource { generateGrammarSource {
@@ -308,24 +368,53 @@ tasks {
from(createOpenApiSourceJar) { into("lib/src") } from(createOpenApiSourceJar) { into("lib/src") }
} }
patchPluginXml { val pluginVersion = version
// Don't forget to update plugin.xml // Don't forget to update plugin.xml
sinceBuild.set("233.11799.67") patchPluginXml {
sinceBuild.set("233.11799.30")
// Get the latest available change notes from the changelog file
changeNotes.set( 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,
)
}
},
) )
} }
} }
// --- Linting
//ktlint {
// version.set("0.48.2")
//}
// --- Tests // --- Tests
tasks { tasks {
test { test {
useJUnitPlatform() useJUnitPlatform()
exclude("**/propertybased/**")
exclude("**/longrunning/**")
exclude("/ui/**")
} }
} }
tasks.register<Test>("testUi") {
group = "verification"
useJUnitPlatform()
include("/ui/**")
}
// --- Changelog // --- Changelog
changelog { changelog {
@@ -344,6 +433,16 @@ koverMerged {
enable() 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 // --- Slack notification
tasks.register("slackNotification") { tasks.register("slackNotification") {
@@ -420,14 +519,12 @@ val prId: String by project
tasks.register("updateMergedPr") { tasks.register("updateMergedPr") {
doLast { doLast {
val x = changelog.getUnreleased() if (project.hasProperty("prId")) {
println("x") println("Got pr id: $prId")
// if (project.hasProperty("prId")) { updateMergedPr(prId.toInt())
// println("Got pr id: $prId") } else {
// updateMergedPr(prId.toInt()) error("Cannot get prId")
// } else { }
// error("Cannot get prId")
// }
} }
} }
@@ -451,7 +548,7 @@ val fixVersionsElementType = "VersionBundleElement"
tasks.register("releaseActions") { tasks.register("releaseActions") {
group = "other" group = "other"
doLast { 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()) { if (tickets.isNotEmpty()) {
println("Updating statuses for tickets: $tickets") println("Updating statuses for tickets: $tickets")
setYoutrackStatus(tickets, "Fixed") setYoutrackStatus(tickets, "Fixed")
@@ -539,8 +636,7 @@ fun addReleaseToYoutrack(name: String): String {
println("Creating new release version in YouTrack: $name") println("Creating new release version in YouTrack: $name")
return runBlocking { return runBlocking {
val response = val response = client.post("https://youtrack.jetbrains.com/api/admin/projects/$vimProjectId/customFields/$fixVersionsFieldId/bundle/values?fields=id,name") {
client.post("https://youtrack.jetbrains.com/api/admin/projects/$vimProjectId/customFields/$fixVersionsFieldId/bundle/values?fields=id,name") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
accept(ContentType.Application.Json) accept(ContentType.Application.Json)
val request = buildJsonObject { val request = buildJsonObject {
@@ -557,8 +653,7 @@ fun getVersionIdByName(name: String): String? {
val client = httpClient() val client = httpClient()
return runBlocking { return runBlocking {
val response = val response = client.get("https://youtrack.jetbrains.com/api/admin/projects/$vimProjectId/customFields/$fixVersionsFieldId/bundle/values?fields=id,name&query=$name")
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 response.body<JsonArray>().singleOrNull()?.jsonObject?.get("id")?.jsonPrimitive?.content
} }
} }
@@ -596,8 +691,7 @@ fun setYoutrackStatus(tickets: Collection<String>, status: String) {
runBlocking { runBlocking {
for (ticket in tickets) { for (ticket in tickets) {
println("Try to set $ticket to $status") println("Try to set $ticket to $status")
val response = val response = client.post("https://youtrack.jetbrains.com/api/issues/$ticket?fields=customFields(id,name,value(id,name))") {
client.post("https://youtrack.jetbrains.com/api/issues/$ticket?fields=customFields(id,name,value(id,name))") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
accept(ContentType.Application.Json) accept(ContentType.Application.Json)
val request = buildJsonObject { val request = buildJsonObject {
@@ -636,8 +730,7 @@ fun setYoutrackFixVersion(tickets: Collection<String>, version: String) {
runBlocking { runBlocking {
for (ticket in tickets) { for (ticket in tickets) {
println("Try to set fix version $version for $ticket") println("Try to set fix version $version for $ticket")
val response = val response = client.post("https://youtrack.jetbrains.com/api/issues/$ticket?fields=customFields(id,name,value(id,name))") {
client.post("https://youtrack.jetbrains.com/api/issues/$ticket?fields=customFields(id,name,value(id,name))") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
accept(ContentType.Application.Json) accept(ContentType.Application.Json)
val request = buildJsonObject { val request = buildJsonObject {
@@ -675,8 +768,7 @@ fun getYoutrackStatus(ticket: String): String {
val client = httpClient() val client = httpClient()
return runBlocking { return runBlocking {
val response = val response = client.get("https://youtrack.jetbrains.com/api/issues/$ticket/customFields/123-129?fields=value(name)")
client.get("https://youtrack.jetbrains.com/api/issues/$ticket/customFields/123-129?fields=value(name)")
response.body<JsonObject>()["value"]!!.jsonObject.getValue("name").jsonPrimitive.content response.body<JsonObject>()["value"]!!.jsonObject.getValue("name").jsonPrimitive.content
} }
} }

View File

@@ -8,18 +8,16 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
#ideaVersion=LATEST-EAP-SNAPSHOT ideaVersion=2023.3.2
ideaVersion=2024.1
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC ideaType=IC
downloadIdeaSources=true downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-33 version=SNAPSHOT
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.22 remoteRobotVersion=0.11.21
antlrVersion=4.10.1 antlrVersion=4.10.1
kotlin.incremental.useClasspathSnapshot=false
# Please don't forget to update kotlin version in buildscript section # Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version # Also update kotlinxSerializationVersion version
@@ -29,7 +27,7 @@ publishChannels=eap
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However, # Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
# we exclude this version from the dependency and use our own version of kotlin that is specified above # we exclude this version from the dependency and use our own version of kotlin that is specified above
kotlinxSerializationVersion=1.6.2 kotlinxSerializationVersion=1.5.1
slackUrl= slackUrl=
youtrackToken= youtrackToken=

Binary file not shown.

View File

@@ -1,7 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

5
gradlew vendored
View File

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

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/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/vimscript/parser/generated
- src/main/java/com/maddyhome/idea/vim/package-info.java - src/main/java/com/maddyhome/idea/vim/package-info.java
- vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
- src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java
- tests/ui-fixtures
dependencyIgnores: dependencyIgnores:
- name: "acejump" - name: "acejump"
- name: "icu4j" - name: "icu4j"

View File

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

View File

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

View File

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

View File

@@ -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()
}

View File

@@ -13,8 +13,4 @@ include 'vim-engine'
include 'scripts' include 'scripts'
include 'annotation-processors' include 'annotation-processors'
include 'tests:java-tests' 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

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

View File

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

View File

@@ -21,7 +21,7 @@ public object RegisterActions {
@JvmStatic @JvmStatic
public fun registerActions() { public fun registerActions() {
registerVimCommandActions() registerVimCommandActions()
registerShortcutsWithoutActions() registerEmptyShortcuts() // todo most likely it is not needed
} }
public fun findAction(id: String): EditorActionHandlerBase? { public fun findAction(id: String): EditorActionHandlerBase? {
@@ -46,11 +46,12 @@ public object RegisterActions {
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) } IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
} }
private fun registerShortcutsWithoutActions() { private fun registerEmptyShortcuts() {
val parser = VimPlugin.getKey() val parser = VimPlugin.getKey()
// The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we // The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we
// still need to register the shortcut, to make sure the editor doesn't swallow it. // still need to register the shortcut, to make sure the editor doesn't swallow it.
parser.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System) parser
.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System)
} }
} }

View File

@@ -211,22 +211,22 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
public static void setEnabled(final boolean enabled) { public static void setEnabled(final boolean enabled) {
if (isEnabled() == enabled) return; if (isEnabled() == enabled) return;
if (!enabled) {
getInstance().turnOffPlugin(true);
}
getInstance().enabled = enabled; getInstance().enabled = enabled;
if (enabled) {
getInstance().turnOnPlugin();
}
if (enabled) { if (enabled) {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn(); VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
} else { } else {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff(); VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
} }
if (!enabled) {
getInstance().turnOffPlugin(true);
}
if (enabled) {
getInstance().turnOnPlugin();
}
StatusBarIconFactory.Util.INSTANCE.updateIcon(); StatusBarIconFactory.Util.INSTANCE.updateIcon();
} }
@@ -353,7 +353,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (onOffDisposable != null) { if (onOffDisposable != null) {
Disposer.dispose(onOffDisposable); Disposer.dispose(onOffDisposable);
onOffDisposable = null;
} }
} }

View File

@@ -14,7 +14,7 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.maddyhome.idea.vim.group.EditorHolderService import com.maddyhome.idea.vim.group.EditorHolderService
@Service(Service.Level.PROJECT) @Service
internal class VimProjectService(val project: Project) : Disposable { internal class VimProjectService(val project: Project) : Disposable {
override fun dispose() { override fun dispose() {
// Not sure if this is a best solution // Not sure if this is a best solution

View File

@@ -77,7 +77,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0 val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers) val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
val startTime = if (traceTime) System.currentTimeMillis() else null val startTime = if (traceTime) System.currentTimeMillis() else null
handler.handleKey(editor.vim, keyStroke, context.vim, handler.keyHandlerState) handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim))
if (startTime != null) { if (startTime != null) {
val duration = System.currentTimeMillis() - startTime val duration = System.currentTimeMillis() - startTime
LOG.info("VimTypedAction '$charTyped': $duration ms") LOG.info("VimTypedAction '$charTyped': $duration ms")

View File

@@ -44,6 +44,7 @@ import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.InputEvent import java.awt.event.InputEvent
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
@@ -78,8 +79,11 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
// Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler? // Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler?
try { try {
val start = if (traceTime) System.currentTimeMillis() else null val start = if (traceTime) System.currentTimeMillis() else null
val keyHandler = KeyHandler.getInstance() KeyHandler.getInstance().handleKey(
keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState) editor.vim,
keyStroke,
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
)
if (start != null) { if (start != null) {
val duration = System.currentTimeMillis() - start val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut update '$keyStroke': $duration ms") LOG.info("VimShortcut update '$keyStroke': $duration ms")
@@ -376,10 +380,6 @@ private class ActionEnableStatus(
} }
} }
override fun toString(): String {
return "ActionEnableStatus(isEnabled=$isEnabled, message='$message', logLevel=$logLevel)"
}
companion object { companion object {
private val LOG = logger<ActionEnableStatus>() private val LOG = logger<ActionEnableStatus>()

View File

@@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
@@ -22,7 +21,6 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.argumentCaptured import com.maddyhome.idea.vim.common.argumentCaptured
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
@@ -31,67 +29,21 @@ import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
// todo make it multicaret // todo make it multicaret
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean { private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
val func = injector.globalOptions().operatorfunc val operatorFunction = injector.keyGroup.operatorFunction
if (func.isEmpty()) { if (operatorFunction == null) {
VimPlugin.showMessage(MessageHelper.message("E774")) VimPlugin.showMessage(MessageHelper.message("E774"))
return false return false
} }
val scriptContext = CommandLineVimLContext
// The option value is either a function name, which should have a handler, or it might be a lambda expression, or a
// `function` or `funcref` call expression, all of which will return a funcref (with a handler)
var handler = injector.functionService.getFunctionHandlerOrNull(null, func, scriptContext)
if (handler == null) {
val expression = injector.vimscriptParser.parseExpression(func)
if (expression != null) {
try {
val value = expression.evaluate(editor, context, scriptContext)
if (value is VimFuncref) {
handler = value.handler
}
} catch (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 val saveRepeatHandler = VimRepeater.repeatHandler
injector.markService.setChangeMarks(editor.primaryCaret(), textRange) injector.markService.setChangeMarks(editor.primaryCaret(), textRange)
KeyHandler.getInstance().reset(editor) KeyHandler.getInstance().reset(editor)
val result = operatorFunction.apply(editor, context, selectionType)
val arguments = listOf(SimpleExpression(arg))
handler.executeFunction(arguments, editor, context, scriptContext)
VimRepeater.repeatHandler = saveRepeatHandler VimRepeater.repeatHandler = saveRepeatHandler
return true return result
} }
@CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL])

View File

@@ -7,9 +7,9 @@
*/ */
package com.maddyhome.idea.vim.action.change package com.maddyhome.idea.vim.action.change
import com.intellij.openapi.command.CommandProcessor
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
import com.intellij.openapi.command.CommandProcessor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor

View File

@@ -34,7 +34,7 @@ public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecu
} }
injector.editorGroup.notifyIdeaJoin(editor) injector.editorGroup.notifyIdeaJoin(editor)
var res = true var res = true
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret -> editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) { if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) {
res = false res = false
} }

View File

@@ -39,7 +39,7 @@ public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExe
return true return true
} }
var res = true var res = true
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret -> editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!caret.isValid) return@forEach if (!caret.isValid) return@forEach
val range = caretsAndSelections[caret] ?: return@forEach val range = caretsAndSelections[caret] ?: return@forEach
if (!injector.changeGroup.deleteJoinRange( if (!injector.changeGroup.deleteJoinRange(

View File

@@ -39,7 +39,7 @@ public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.Sin
return true return true
} }
var res = true var res = true
editor.carets().sortedByDescending { it.offset }.forEach { caret -> editor.carets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!caret.isValid) return@forEach if (!caret.isValid) return@forEach
val range = caretsAndSelections[caret] ?: return@forEach val range = caretsAndSelections[caret] ?: return@forEach
if (!injector.changeGroup.deleteJoinRange( if (!injector.changeGroup.deleteJoinRange(

View File

@@ -21,19 +21,19 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
public class CommandState(private val machine: VimStateMachine) { public class CommandState(private val machine: VimStateMachine) {
public val isOperatorPending: Boolean public val isOperatorPending: Boolean
get() = machine.isOperatorPending(machine.mode) get() = machine.isOperatorPending
public val mode: Mode public val mode: CommandState.Mode
get() { get() {
val myMode = machine.mode val myMode = machine.mode
return when (myMode) { return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> CommandState.Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> CommandState.Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> CommandState.Mode.VISUAL
} }
} }

View File

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

View File

@@ -7,7 +7,6 @@
*/ */
package com.maddyhome.idea.vim.extension package com.maddyhome.idea.vim.extension
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
@@ -15,34 +14,21 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.action.change.Extension import com.maddyhome.idea.vim.action.change.Extension
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.common.CommandAlias import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.CommandLineHelper import com.maddyhome.idea.vim.helper.CommandLineHelper
import com.maddyhome.idea.vim.helper.TestInputModel 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.helper.vimStateMachine
import com.maddyhome.idea.vim.key.MappingOwner import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimLContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionDeclaration
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionFlag
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import java.util.*
import javax.swing.KeyStroke import javax.swing.KeyStroke
/** /**
@@ -134,6 +120,12 @@ public object VimExtensionFacade {
.setAlias(name, CommandAlias.Call(minimumNumberOfArguments, maximumNumberOfArguments, name, handler)) .setAlias(name, CommandAlias.Call(minimumNumberOfArguments, maximumNumberOfArguments, name, handler))
} }
/** Sets the value of 'operatorfunc' to be used as the operator function in 'g@'. */
@JvmStatic
public fun setOperatorFunction(function: OperatorFunction) {
VimPlugin.getKey().operatorFunction = function
}
/** /**
* Runs normal mode commands similar to ':normal! {commands}'. * Runs normal mode commands similar to ':normal! {commands}'.
* Mappings doesn't work with this function * Mappings doesn't work with this function
@@ -143,9 +135,8 @@ public object VimExtensionFacade {
*/ */
@JvmStatic @JvmStatic
public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) { public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim) val context = injector.executionContextManager.onEditor(editor.vim)
val keyHandler = KeyHandler.getInstance() keys.forEach { KeyHandler.getInstance().handleKey(editor.vim, it, context, false, false) }
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
} }
/** Returns a single key stroke from the user input similar to 'getchar()'. */ /** Returns a single key stroke from the user input similar to 'getchar()'. */
@@ -161,7 +152,7 @@ public object VimExtensionFacade {
LOG.trace("Unit test mode is active") LOG.trace("Unit test mode is active")
val mappingStack = KeyHandler.getInstance().keyStack val mappingStack = KeyHandler.getInstance().keyStack
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also { mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
if (injector.registerGroup.isRecording) { if (editor.vim.vimStateMachine.isRecording) {
KeyHandler.getInstance().modalEntryKeys += it KeyHandler.getInstance().modalEntryKeys += it
} }
} }
@@ -182,8 +173,8 @@ public object VimExtensionFacade {
/** Returns a string typed in the input box similar to 'input()'. */ /** Returns a string typed in the input box similar to 'input()'. */
@JvmStatic @JvmStatic
public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String { public fun inputString(editor: Editor, prompt: String, finishOn: Char?): String {
return service<CommandLineHelper>().inputString(editor.vim, context.vim, prompt, finishOn) ?: "" return service<CommandLineHelper>().inputString(editor.vim, prompt, finishOn) ?: ""
} }
/** Get the current contents of the given register similar to 'getreg()'. */ /** Get the current contents of the given register similar to 'getreg()'. */
@@ -216,65 +207,4 @@ public object VimExtensionFacade {
public fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) { public fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type) VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type)
} }
@JvmStatic
public 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)
}
}
public 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
}
}
}
public fun interface ScriptFunction {
public fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
} }

View File

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

View File

@@ -251,7 +251,7 @@ public class VimArgTextObjExtension implements VimExtension {
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner); final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
//noinspection DuplicatedCode //noinspection DuplicatedCode
if (!vimStateMachine.isOperatorPending(editor.getMode())) { if (!vimStateMachine.isOperatorPending()) {
editor.nativeCarets().forEach((VimCaret caret) -> { editor.nativeCarets().forEach((VimCaret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0); final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
if (range != null) { if (range != null) {

View File

@@ -22,26 +22,26 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.command.TextObjectVisualType import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.CommandAliasHandler import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.Ranges import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.addCommand import com.maddyhome.idea.vim.extension.VimExtensionFacade.addCommand
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.handler.TextObjectActionHandler import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.PsiHelper import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.vimStateMachine
@@ -49,19 +49,17 @@ import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.* import java.util.*
internal class CommentaryExtension : VimExtension { internal class CommentaryExtension : VimExtension {
object Util { companion object {
fun doCommentary( fun doCommentary(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
range: TextRange, range: TextRange,
selectionType: SelectionType, selectionType: SelectionType,
resetCaret: Boolean = true, resetCaret: Boolean,
): Boolean { ): Boolean {
val mode = editor.vimStateMachine.mode val mode = editor.vimStateMachine.mode
if (mode !is Mode.VISUAL) { if (mode !is Mode.VISUAL) {
@@ -69,7 +67,8 @@ internal class CommentaryExtension : VimExtension {
} }
return runWriteAction { return runWriteAction {
// Treat block- and character-wise selections as block comments. Fall back if the first action isn't available // Treat block- and character-wise selections as block comments. Be ready to fall back to if the first action
// isn't available
val actions = if (selectionType === SelectionType.LINE_WISE) { val actions = if (selectionType === SelectionType.LINE_WISE) {
listOf(IdeActions.ACTION_COMMENT_LINE, IdeActions.ACTION_COMMENT_BLOCK) listOf(IdeActions.ACTION_COMMENT_LINE, IdeActions.ACTION_COMMENT_BLOCK)
} else { } else {
@@ -114,17 +113,12 @@ internal class CommentaryExtension : VimExtension {
// first non-whitespace character, then the caret is in the right place. If it's inserted at the first column, // first non-whitespace character, then the caret is in the right place. If it's inserted at the first column,
// then the caret is now in a bit of a weird place. We can't detect this scenario, so we just have to accept // then the caret is now in a bit of a weird place. We can't detect this scenario, so we just have to accept
// the difference // the difference
// TODO: If we don't move the caret to the start offset, we should maintain the current logical position
if (resetCaret) { if (resetCaret) {
editor.primaryCaret().moveToOffset(range.startOffset) editor.primaryCaret().moveToOffset(range.startOffset)
} }
} }
} }
companion object {
private const val OPERATOR_FUNC = "CommentaryOperatorFunc"
}
override fun getName() = "commentary" override fun getName() = "commentary"
override fun init() { override fun init() {
@@ -151,16 +145,6 @@ internal class CommentaryExtension : VimExtension {
putKeyMapping(MappingMode.N, injector.parser.parseKeys("<Plug>(CommentLine)"), owner, plugCommentaryLineKeys, true) putKeyMapping(MappingMode.N, injector.parser.parseKeys("<Plug>(CommentLine)"), owner, plugCommentaryLineKeys, true)
addCommand("Commentary", CommentaryCommandAliasHandler()) addCommand("Commentary", CommentaryCommandAliasHandler())
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, CommentaryOperatorFunction())
}
private class CommentaryOperatorFunction : OperatorFunction {
// todo make it multicaret
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
return Util.doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
}
} }
/** /**
@@ -169,13 +153,19 @@ internal class CommentaryExtension : VimExtension {
* E.g. handles the `gc` in `gc_`, by setting the operator function, then invoking `g@` to receive the `_` motion to * E.g. handles the `gc` in `gc_`, by setting the operator function, then invoking `g@` to receive the `_` motion to
* invoke the operator. This object is both the mapping handler and the operator function. * invoke the operator. This object is both the mapping handler and the operator function.
*/ */
private class CommentaryOperatorHandler : ExtensionHandler { private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC setOperatorFunction(this)
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
} }
// todo make it multicaret
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
return doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
}
} }
private class CommentaryMappingHandler : ExtensionHandler { private class CommentaryMappingHandler : ExtensionHandler {
@@ -249,7 +239,7 @@ internal class CommentaryExtension : VimExtension {
*/ */
private class CommentaryCommandAliasHandler : CommandAliasHandler { private class CommentaryCommandAliasHandler : CommandAliasHandler {
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) { override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
Util.doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false) 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.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getOffset import com.maddyhome.idea.vim.api.getOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.helper.fileSize import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
@@ -43,8 +45,6 @@ import com.maddyhome.idea.vim.mark.VimMarkConstants
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
/** /**
@@ -72,13 +72,30 @@ internal class VimExchangeExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("X"), owner, injector.parser.parseKeys(EXCHANGE_CMD), true) putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("X"), owner, injector.parser.parseKeys(EXCHANGE_CMD), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxc"), owner, injector.parser.parseKeys(EXCHANGE_CLEAR_CMD), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxc"), owner, injector.parser.parseKeys(EXCHANGE_CLEAR_CMD), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxx"), owner, injector.parser.parseKeys(EXCHANGE_LINE_CMD), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxx"), owner, injector.parser.parseKeys(EXCHANGE_LINE_CMD), true)
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
} }
object Util { companion object {
@NonNls
const val EXCHANGE_CMD = "<Plug>(Exchange)"
@NonNls
const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
@NonNls
const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
val EXCHANGE_KEY = Key<Exchange>("exchange") val EXCHANGE_KEY = Key<Exchange>("exchange")
// End mark has always greater of eq offset than start mark
class Exchange(val type: SelectionType, val start: Mark, val end: Mark, val text: String) {
private var myHighlighter: RangeHighlighter? = null
fun setHighlighter(highlighter: RangeHighlighter) {
myHighlighter = highlighter
}
fun getHighlighter(): RangeHighlighter? = myHighlighter
}
fun clearExchange(editor: Editor) { fun clearExchange(editor: Editor) {
editor.getUserData(EXCHANGE_KEY)?.getHighlighter()?.let { editor.getUserData(EXCHANGE_KEY)?.getHighlighter()?.let {
editor.markupModel.removeHighlighter(it) editor.markupModel.removeHighlighter(it)
@@ -87,25 +104,18 @@ internal class VimExchangeExtension : VimExtension {
} }
} }
companion object {
@NonNls private const val EXCHANGE_CMD = "<Plug>(Exchange)"
@NonNls private const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
@NonNls private const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
@NonNls private const val OPERATOR_FUNC = "ExchangeOperatorFunc"
}
private class ExchangeHandler(private val isLine: Boolean) : ExtensionHandler { private class ExchangeHandler(private val isLine: Boolean) : ExtensionHandler {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC setOperatorFunction(Operator(false))
executeNormalWithoutMapping(injector.parser.parseKeys(if (isLine) "g@_" else "g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys(if (isLine) "g@_" else "g@"), editor.ij)
} }
} }
private class ExchangeClearHandler : ExtensionHandler { private class ExchangeClearHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
Util.clearExchange(editor.ij) clearExchange(editor.ij)
} }
} }
@@ -115,12 +125,12 @@ internal class VimExchangeExtension : VimExtension {
val mode = editor.mode val mode = editor.mode
// Leave visual mode to create selection marks // Leave visual mode to create selection marks
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
Operator(true).apply(editor, context, mode.selectionType ?: SelectionType.CHARACTER_WISE) Operator(true).apply(editor, context, mode.selectionType ?: CHARACTER_WISE)
} }
} }
} }
private class Operator(private val isVisual: Boolean = false) : OperatorFunction { private class Operator(private val isVisual: Boolean) : OperatorFunction {
fun Editor.getMarkOffset(mark: Mark) = IjVimEditor(this).getOffset(mark.line, mark.col) fun Editor.getMarkOffset(mark: Mark) = IjVimEditor(this).getOffset(mark.line, mark.col)
fun SelectionType.getString() = when (this) { fun SelectionType.getString() = when (this) {
SelectionType.CHARACTER_WISE -> "v" SelectionType.CHARACTER_WISE -> "v"
@@ -138,7 +148,7 @@ internal class VimExchangeExtension : VimExtension {
else -> HighlighterTargetArea.EXACT_RANGE else -> HighlighterTargetArea.EXACT_RANGE
} }
val isVisualLine = ex.type == SelectionType.LINE_WISE val isVisualLine = ex.type == SelectionType.LINE_WISE
val endAdj = if (!(isVisualLine) && (hlArea == HighlighterTargetArea.EXACT_RANGE || isVisual)) 1 else 0 val endAdj = if (!(isVisualLine) && (hlArea == HighlighterTargetArea.EXACT_RANGE || (isVisual))) 1 else 0
return ijEditor.markupModel.addRangeHighlighter( return ijEditor.markupModel.addRangeHighlighter(
ijEditor.getMarkOffset(ex.start), ijEditor.getMarkOffset(ex.start),
(ijEditor.getMarkOffset(ex.end) + endAdj).coerceAtMost(ijEditor.fileSize), (ijEditor.getMarkOffset(ex.end) + endAdj).coerceAtMost(ijEditor.fileSize),
@@ -148,12 +158,12 @@ internal class VimExchangeExtension : VimExtension {
) )
} }
val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: SelectionType.CHARACTER_WISE) val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: CHARACTER_WISE)
val exchange1 = ijEditor.getUserData(Util.EXCHANGE_KEY) val exchange1 = ijEditor.getUserData(EXCHANGE_KEY)
if (exchange1 == null) { if (exchange1 == null) {
val highlighter = highlightExchange(currentExchange) val highlighter = highlightExchange(currentExchange)
currentExchange.setHighlighter(highlighter) currentExchange.setHighlighter(highlighter)
ijEditor.putUserData(Util.EXCHANGE_KEY, currentExchange) ijEditor.putUserData(EXCHANGE_KEY, currentExchange)
return true return true
} else { } else {
val cmp = compareExchanges(exchange1, currentExchange) val cmp = compareExchanges(exchange1, currentExchange)
@@ -179,7 +189,7 @@ internal class VimExchangeExtension : VimExtension {
} }
} }
exchange(ijEditor, ex1, ex2, reverse, expand) exchange(ijEditor, ex1, ex2, reverse, expand)
Util.clearExchange(ijEditor) clearExchange(ijEditor)
return true return true
} }
} }
@@ -344,14 +354,4 @@ internal class VimExchangeExtension : VimExtension {
} }
} }
} }
// End mark has always greater of eq offset than start mark
class Exchange(val type: SelectionType, val start: Mark, val end: Mark, val text: String) {
private var myHighlighter: RangeHighlighter? = null
fun setHighlighter(highlighter: RangeHighlighter) {
myHighlighter = highlighter
}
fun getHighlighter(): RangeHighlighter? = myHighlighter
}
} }

View File

@@ -99,7 +99,7 @@ internal class Matchit : VimExtension {
// Normally we want to jump to the start of the matching pair. But when moving forward in operator // Normally we want to jump to the start of the matching pair. But when moving forward in operator
// pending mode, we want to include the entire match. isInOpPending makes that distinction. // pending mode, we want to include the entire match. isInOpPending makes that distinction.
val isInOpPending = commandState.isOperatorPending(editor.mode) val isInOpPending = commandState.isOperatorPending
if (isInOpPending) { if (isInOpPending) {
val matchitAction = MatchitAction() val matchitAction = MatchitAction()
@@ -217,8 +217,6 @@ private object FileTypePatterns {
return if (fileTypeName in htmlLikeFileTypes) { return if (fileTypeName in htmlLikeFileTypes) {
this.htmlPatterns this.htmlPatterns
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
this.javaPatterns
} else if (fileTypeName == "Ruby" || fileExtension == "rb") { } else if (fileTypeName == "Ruby" || fileExtension == "rb") {
this.rubyPatterns this.rubyPatterns
} else if (fileTypeName == "RHTML" || fileExtension == "erb") { } else if (fileTypeName == "RHTML" || fileExtension == "erb") {
@@ -233,7 +231,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") { } else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns this.cMakePatterns
} else { } else {
this.htmlPatterns return null
} }
} }
@@ -244,7 +242,6 @@ private object FileTypePatterns {
) )
private val htmlPatterns = createHtmlPatterns() private val htmlPatterns = createHtmlPatterns()
private val javaPatterns = createJavaPatterns()
private val rubyPatterns = createRubyPatterns() private val rubyPatterns = createRubyPatterns()
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
private val phpPatterns = createPhpPatterns() private val phpPatterns = createPhpPatterns()
@@ -274,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 { private fun createRubyPatterns(): LanguagePatterns {
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim // 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. // We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries.

View File

@@ -8,21 +8,25 @@
package com.maddyhome.idea.vim.extension.nerdtree package com.maddyhome.idea.vim.extension.nerdtree
import com.intellij.ide.projectView.ProjectView
import com.intellij.ide.projectView.impl.AbstractProjectViewPane
import com.intellij.ide.projectView.impl.ProjectViewImpl
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.ui.getUserData import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.putUserData import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Key import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowId import com.intellij.openapi.wm.ToolWindowId
import com.intellij.openapi.wm.ex.ToolWindowManagerEx import com.intellij.openapi.wm.ex.ToolWindowManagerEx
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
import com.intellij.ui.KeyStrokeAdapter import com.intellij.ui.KeyStrokeAdapter
import com.intellij.ui.TreeExpandCollapse import com.intellij.ui.TreeExpandCollapse
import com.intellij.ui.speedSearch.SpeedSearchSupply import com.intellij.ui.speedSearch.SpeedSearchSupply
@@ -49,8 +53,6 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.JComponent
import javax.swing.JTree
import javax.swing.KeyStroke import javax.swing.KeyStroke
import javax.swing.SwingConstants import javax.swing.SwingConstants
@@ -128,14 +130,15 @@ internal class NerdTree : VimExtension {
addCommand("NERDTreeFind", IjCommandHandler("SelectInProjectView")) addCommand("NERDTreeFind", IjCommandHandler("SelectInProjectView"))
addCommand("NERDTreeRefreshRoot", IjCommandHandler("Synchronize")) addCommand("NERDTreeRefreshRoot", IjCommandHandler("Synchronize"))
synchronized(Util.monitor) { synchronized(monitor) {
Util.commandsRegistered = true commandsRegistered = true
ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
} }
} }
class IjCommandHandler(private val actionId: String) : CommandAliasHandler { class IjCommandHandler(private val actionId: String) : CommandAliasHandler {
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) { override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
Util.callAction(editor, actionId, context) callAction(editor, actionId, context)
} }
} }
@@ -146,7 +149,7 @@ internal class NerdTree : VimExtension {
if (toolWindow.isVisible) { if (toolWindow.isVisible) {
toolWindow.hide() toolWindow.hide()
} else { } else {
Util.callAction(editor, "ActivateProjectToolWindow", context) callAction(editor, "ActivateProjectToolWindow", context)
} }
} }
} }
@@ -161,8 +164,39 @@ internal class NerdTree : VimExtension {
} }
} }
class ProjectViewListener(private val project: Project) : ToolWindowManagerListener {
override fun toolWindowShown(toolWindow: ToolWindow) {
if (ToolWindowId.PROJECT_VIEW != toolWindow.id) return
val dispatcher = NerdDispatcher.getInstance(project)
if (dispatcher.speedSearchListenerInstalled) return
// I specify nullability explicitly as we've got a lot of exceptions saying this property is null
val currentProjectViewPane: AbstractProjectViewPane? = ProjectView.getInstance(project).currentProjectViewPane
val tree = currentProjectViewPane?.tree ?: return
val supply = SpeedSearchSupply.getSupply(tree, true) ?: return
// NB: Here might be some issues with concurrency, but it's not really bad, I think
dispatcher.speedSearchListenerInstalled = true
supply.addChangeListener {
dispatcher.waitForSearch = false
}
}
}
// 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(monitor) {
if (!commandsRegistered) return
installDispatcher(project)
}
}
}
class NerdDispatcher : DumbAwareAction() { class NerdDispatcher : DumbAwareAction() {
internal var waitForSearch = false internal var waitForSearch = false
internal var speedSearchListenerInstalled = false
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
var keyStroke = getKeyStroke(e) ?: return var keyStroke = getKeyStroke(e) ?: return
@@ -180,7 +214,7 @@ internal class NerdTree : VimExtension {
val action = nextNode.actionHolder val action = nextNode.actionHolder
when (action) { when (action) {
is NerdAction.ToIj -> Util.callAction(null, action.name, e.dataContext.vim) is NerdAction.ToIj -> callAction(null, action.name, e.dataContext.vim)
is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) } is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) }
} }
} }
@@ -210,6 +244,10 @@ internal class NerdTree : VimExtension {
} }
companion object { companion object {
fun getInstance(project: Project): NerdDispatcher {
return project.getService(NerdDispatcher::class.java)
}
private const val ESCAPE_KEY_CODE = 27 private const val ESCAPE_KEY_CODE = 27
} }
@@ -245,15 +283,20 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapActivateNode", "NERDTreeMapActivateNode",
"o", "o",
NerdAction.Code { _, dataContext, e -> NerdAction.Code { project, dataContext, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val array = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext)?.filter { it.canNavigateToSource() }
if (array.isNullOrEmpty()) {
val row = tree.selectionRows?.getOrNull(0) ?: return@Code val row = tree.selectionRows?.getOrNull(0) ?: return@Code
if (tree.isExpanded(row)) { if (tree.isExpanded(row)) {
tree.collapseRow(row) tree.collapseRow(row)
} else { } else {
tree.expandRow(row) tree.expandRow(row)
} }
} else {
array.forEach { it.navigate(true) }
}
}, },
) )
registerCommand( registerCommand(
@@ -313,7 +356,7 @@ internal class NerdTree : VimExtension {
currentWindow?.split(SwingConstants.VERTICAL, true, file, true) currentWindow?.split(SwingConstants.VERTICAL, true, file, true)
// FIXME: 22.01.2021 This solution bouncing a bit // FIXME: 22.01.2021 This solution bouncing a bit
Util.callAction(null, "ActivateProjectToolWindow", context.vim) callAction(null, "ActivateProjectToolWindow", context.vim)
}, },
) )
registerCommand( registerCommand(
@@ -325,14 +368,14 @@ internal class NerdTree : VimExtension {
val currentWindow = splitters.currentWindow val currentWindow = splitters.currentWindow
currentWindow?.split(SwingConstants.HORIZONTAL, true, file, true) currentWindow?.split(SwingConstants.HORIZONTAL, true, file, true)
Util.callAction(null, "ActivateProjectToolWindow", context.vim) callAction(null, "ActivateProjectToolWindow", context.vim)
}, },
) )
registerCommand( registerCommand(
"NERDTreeMapOpenRecursively", "NERDTreeMapOpenRecursively",
"O", "O",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
TreeExpandCollapse.expandAll(tree) TreeExpandCollapse.expandAll(tree)
tree.selectionPath?.let { tree.selectionPath?.let {
TreeUtil.scrollToVisible(tree, it, false) TreeUtil.scrollToVisible(tree, it, false)
@@ -342,8 +385,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapCloseChildren", "NERDTreeMapCloseChildren",
"X", "X",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
TreeExpandCollapse.collapse(tree) TreeExpandCollapse.collapse(tree)
tree.selectionPath?.let { tree.selectionPath?.let {
TreeUtil.scrollToVisible(tree, it, false) TreeUtil.scrollToVisible(tree, it, false)
@@ -353,8 +396,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapCloseDir", "NERDTreeMapCloseDir",
"x", "x",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
if (tree.isExpanded(currentPath)) { if (tree.isExpanded(currentPath)) {
tree.collapsePath(currentPath) tree.collapsePath(currentPath)
@@ -372,8 +415,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpParent", "NERDTreeMapJumpParent",
"p", "p",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val parentPath = currentPath.parentPath ?: return@Code val parentPath = currentPath.parentPath ?: return@Code
if (parentPath.parentPath != null) { if (parentPath.parentPath != null) {
@@ -386,8 +429,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpFirstChild", "NERDTreeMapJumpFirstChild",
"K", "K",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val parent = currentPath.parentPath ?: return@Code val parent = currentPath.parentPath ?: return@Code
val row = tree.getRowForPath(parent) val row = tree.getRowForPath(parent)
@@ -399,8 +442,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpLastChild", "NERDTreeMapJumpLastChild",
"J", "J",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val currentPathCount = currentPath.pathCount val currentPathCount = currentPath.pathCount
@@ -421,12 +464,12 @@ internal class NerdTree : VimExtension {
) )
registerCommand( registerCommand(
"NERDTreeMapJumpNextSibling", "NERDTreeMapJumpNextSibling",
"<A-J>", "<C-J>",
NerdAction.ToIj("Tree-selectNextSibling"), NerdAction.ToIj("Tree-selectNextSibling"),
) )
registerCommand( registerCommand(
"NERDTreeMapJumpPrevSibling", "NERDTreeMapJumpPrevSibling",
"<A-K>", "<C-K>",
NerdAction.ToIj("Tree-selectPreviousSibling"), NerdAction.ToIj("Tree-selectPreviousSibling"),
) )
registerCommand( registerCommand(
@@ -445,31 +488,30 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"/", "/",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code NerdDispatcher.getInstance(project).waitForSearch = true
tree.getUserData(KEY)?.waitForSearch = true
}, },
) )
registerCommand( registerCommand(
"<ESC>", "<ESC>",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val instance = NerdDispatcher.getInstance(project)
tree.getUserData(KEY)?.waitForSearch = false if (instance.waitForSearch) {
instance.waitForSearch = false
}
}, },
) )
for (c in ('a'..'z') + ('A'..'Z')) {
val ks = KeyStroke.getKeyStroke(c)
if (ks !in actionsRoot) {
registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
}
}
} }
object Util { companion object {
const val pluginName = "NERDTree"
internal val monitor = Any() internal val monitor = Any()
internal var commandsRegistered = false internal var commandsRegistered = false
private val LOG = logger<NerdTree>()
fun callAction(editor: VimEditor?, name: String, context: ExecutionContext) { fun callAction(editor: VimEditor?, name: String, context: ExecutionContext) {
val action = ActionManager.getInstance().getAction(name) ?: run { val action = ActionManager.getInstance().getAction(name) ?: run {
VimPlugin.showMessage(MessageHelper.message("action.not.found.0", name)) VimPlugin.showMessage(MessageHelper.message("action.not.found.0", name))
@@ -484,34 +526,12 @@ internal class NerdTree : VimExtension {
} }
} }
} }
}
companion object { private fun addCommand(alias: String, handler: CommandAliasHandler) {
const val pluginName = "NERDTree"
private val LOG = logger<NerdTree>()
private val KEY = Key.create<NerdDispatcher>("IdeaVim-NerdTree-Dispatcher")
fun installDispatcher(component: JComponent) {
if (component.getUserData(KEY) != null) return
val dispatcher = NerdDispatcher()
component.putUserData(KEY, dispatcher)
val shortcuts = collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(pluginName)) }
dispatcher.registerCustomShortcutSet(KeyGroup.toShortcutSet(shortcuts), component)
SpeedSearchSupply.getSupply(component, true)?.addChangeListener {
dispatcher.waitForSearch = false
}
}
}
}
private fun addCommand(alias: String, handler: CommandAliasHandler) {
VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler)) VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
} }
private fun registerCommand(variable: String, default: String, action: NerdAction) { private fun registerCommand(variable: String, default: String, action: NerdAction) {
val variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable) val variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable)
val mappings = if (variableValue is VimString) { val mappings = if (variableValue is VimString) {
variableValue.value variableValue.value
@@ -519,16 +539,16 @@ private fun registerCommand(variable: String, default: String, action: NerdActio
default default
} }
actionsRoot.addLeafs(mappings, action) actionsRoot.addLeafs(mappings, action)
} }
private fun registerCommand(default: String, action: NerdAction) { private fun registerCommand(default: String, action: NerdAction) {
actionsRoot.addLeafs(default, action) actionsRoot.addLeafs(default, action)
} }
private val actionsRoot: RootNode<NerdAction> = RootNode()
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private val actionsRoot: RootNode<NerdAction> = RootNode() private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
return if (node is CommandPartNode<NerdAction>) { return if (node is CommandPartNode<NerdAction>) {
val res = node.keys.toMutableSet() val res = node.keys.toMutableSet()
res += node.values.map { collectShortcuts(it) }.flatten() res += node.values.map { collectShortcuts(it) }.flatten()
@@ -536,8 +556,15 @@ private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
} else { } else {
emptySet() emptySet()
} }
} }
private fun getTree(e: AnActionEvent): JTree? { private fun installDispatcher(project: Project) {
return e.dataContext.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) as? JTree 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

@@ -1,25 +0,0 @@
package com.maddyhome.idea.vim.extension.nerdtree
import com.intellij.ide.ApplicationInitializedListener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.util.ui.StartupUiUtil
import kotlinx.coroutines.CoroutineScope
import java.awt.AWTEvent
import java.awt.event.FocusEvent
import javax.swing.JTree
@Suppress("UnstableApiUsage")
internal class NerdTreeApplicationListener : ApplicationInitializedListener {
override suspend fun execute(asyncScope: CoroutineScope) {
StartupUiUtil.addAwtListener(::handleEvent, AWTEvent.FOCUS_EVENT_MASK, ApplicationManager.getApplication().getService(NerdTreeDisposableService::class.java))
}
private fun handleEvent(event: AWTEvent) {
if (event is FocusEvent && event.id == FocusEvent.FOCUS_GAINED) {
val source = event.source
if (source is JTree) {
NerdTree.installDispatcher(source)
}
}
}
}

View File

@@ -1,9 +0,0 @@
package com.maddyhome.idea.vim.extension.nerdtree
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
@Service
internal class NerdTreeDisposableService : Disposable {
override fun dispose() {}
}

View File

@@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.extension.paragraphmotion package com.maddyhome.idea.vim.extension.paragraphmotion
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector 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
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.helper.vimForEachCaret import com.maddyhome.idea.vim.helper.vimForEachCaret
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import javax.swing.KeyStroke
internal class ParagraphMotion : VimExtension { internal class ParagraphMotion : VimExtension {
override fun getName(): String = "vim-paragraph-motion" override fun getName(): String = "vim-paragraph-motion"
@@ -33,8 +30,8 @@ internal class ParagraphMotion : VimExtension {
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), owner, ParagraphMotionHandler(1), false) VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), owner, ParagraphMotionHandler(1), false)
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), owner, ParagraphMotionHandler(-1), false) VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), owner, ParagraphMotionHandler(-1), false)
putKeyMappingIfMissingFromAndToKeys(MappingMode.NXO, injector.parser.parseKeys("}"), owner, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), true) putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("}"), owner, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), true)
putKeyMappingIfMissingFromAndToKeys(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true) putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true)
} }
private class ParagraphMotionHandler(private val count: Int) : ExtensionHandler { private class ParagraphMotionHandler(private val count: Int) : ExtensionHandler {
@@ -52,17 +49,4 @@ internal class ParagraphMotion : VimExtension {
?.let { editor.normalizeOffset(it, true) } ?.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,26 +8,30 @@
package com.maddyhome.idea.vim.extension.replacewithregister package com.maddyhome.idea.vim.extension.replacewithregister
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
import com.maddyhome.idea.vim.state.mode.isLine
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
@@ -35,10 +39,6 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isLine
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
internal class ReplaceWithRegister : VimExtension { internal class ReplaceWithRegister : VimExtension {
@@ -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("gr"), owner, injector.parser.parseKeys(RWR_OPERATOR), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("grr"), owner, injector.parser.parseKeys(RWR_LINE), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("grr"), owner, injector.parser.parseKeys(RWR_LINE), true)
putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_VISUAL), true) putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_VISUAL), true)
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
} }
private class RwrVisual : ExtensionHandler { private class RwrVisual : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val typeInEditor = editor.mode.selectionType ?: SelectionType.CHARACTER_WISE val typeInEditor = editor.mode.selectionType ?: CHARACTER_WISE
editor.sortedCarets().forEach { caret -> editor.sortedCarets().forEach { caret ->
val selectionStart = caret.selectionStart val selectionStart = caret.selectionStart
val selectionEnd = caret.selectionEnd val selectionEnd = caret.selectionEnd
val visualSelection = caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor) val visualSelection = caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor)
doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor)) doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor))
} }
editor.exitVisualMode() editor.exitVisualMode()
} }
@@ -75,7 +73,7 @@ internal class ReplaceWithRegister : VimExtension {
override val isRepeatable: Boolean = true override val isRepeatable: Boolean = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC setOperatorFunction(Operator())
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
} }
} }
@@ -93,7 +91,7 @@ internal class ReplaceWithRegister : VimExtension {
val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor) val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor)
caretsAndSelections += visualSelection caretsAndSelections += visualSelection
doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE)) doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE))
} }
editor.sortedCarets().forEach { caret -> editor.sortedCarets().forEach { caret ->
@@ -114,14 +112,14 @@ internal class ReplaceWithRegister : VimExtension {
editor.primaryCaret() to VimSelection.create( editor.primaryCaret() to VimSelection.create(
range.startOffset, range.startOffset,
range.endOffset - 1, range.endOffset - 1,
selectionType ?: SelectionType.CHARACTER_WISE, selectionType ?: CHARACTER_WISE,
editor, editor,
), ),
), ),
selectionType ?: SelectionType.CHARACTER_WISE, selectionType ?: CHARACTER_WISE,
) )
// todo multicaret // todo multicaret
doReplace(ijEditor, context.ij, editor.primaryCaret(), visualSelection) doReplace(ijEditor, editor.primaryCaret(), visualSelection)
return true return true
} }
@@ -134,14 +132,16 @@ internal class ReplaceWithRegister : VimExtension {
} }
companion object { companion object {
@NonNls private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator" @NonNls
@NonNls private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine" private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
@NonNls private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
@NonNls private const val OPERATOR_FUNC = "ReplaceWithRegisterOperatorFunc"
}
}
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 registerGroup = injector.registerGroup
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret() val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
@@ -165,18 +165,19 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
caretAfterInsertedText = false, caretAfterInsertedText = false,
putToLine = -1, putToLine = -1,
) )
val vimEditor = editor.vim
ClipboardOptionHelper.IdeaputDisabler().use { ClipboardOptionHelper.IdeaputDisabler().use {
VimPlugin.getPut().putText( VimPlugin.getPut().putText(
vimEditor, IjVimEditor(editor),
context.vim, injector.executionContextManager.onEditor(editor.vim),
putData, putData,
operatorArguments = OperatorArguments( operatorArguments = OperatorArguments(
editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false, editor.vimStateMachine?.isOperatorPending ?: false,
0, 0,
editor.vim.mode, editor.vim.mode,
), ),
saveToRegister = false saveToRegister = false
) )
} }
}
}
} }

View File

@@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.extension.sneak package com.maddyhome.idea.vim.extension.sneak
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.editor.colors.EditorColors 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.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimProjectService import com.maddyhome.idea.vim.VimProjectService
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@@ -30,17 +30,16 @@ import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionHandler import com.maddyhome.idea.vim.extension.VimExtensionHandler
import com.maddyhome.idea.vim.helper.StrictMode import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import java.awt.Font import java.awt.Font
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import java.util.* import java.util.concurrent.Executors
import javax.swing.Timer 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) // By [Mikhail Levchenko](https://github.com/Mishkun)
// Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak // Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
@@ -49,20 +48,17 @@ internal class IdeaVimSneakExtension : VimExtension {
override fun init() { override fun init() {
val highlightHandler = HighlightHandler() val highlightHandler = HighlightHandler()
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD), MappingMode.NXO) mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD))
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD))
// vim-sneak uses `Z` for visual mode because `S` conflict with vim-sneak plugin VIM-3330
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.NO)
mapToFunctionAndProvideKeys("Z", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.X)
// workaround to support ; and , commands // workaround to support ; and , commands
mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"), MappingMode.NXO) mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"))
mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"), MappingMode.NXO) mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"))
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"), MappingMode.NXO) mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"))
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"), MappingMode.NXO) mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"))
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO) mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL))
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO) mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE))
} }
private class SneakHandler( private class SneakHandler(
@@ -118,7 +114,7 @@ internal class IdeaVimSneakExtension : VimExtension {
var lastSymbols: String = "" var lastSymbols: String = ""
fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? { fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? {
val caret = editor.primaryCaret() val caret = editor.primaryCaret()
val position = caret.offset val position = caret.offset.point
val chars = editor.text() val chars = editor.text()
val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo) val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo)
if (foundPosition != null) { if (foundPosition != null) {
@@ -254,13 +250,11 @@ internal class IdeaVimSneakExtension : VimExtension {
} }
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) { private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
val timer = Timer(DEFAULT_HIGHLIGHT_DURATION_SNEAK) { Executors.newSingleThreadScheduledExecutor().schedule({
if (editor?.isDisposed != true) { ApplicationManager.getApplication().invokeLater {
editor?.markupModel?.removeHighlighter(highlighter) editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
} }
} }, DEFAULT_HIGHLIGHT_DURATION_SNEAK, TimeUnit.MILLISECONDS)
timer.isRepeats = false
timer.start()
} }
private fun getHighlightTextAttributes() = TextAttributes( private fun getHighlightTextAttributes() = TextAttributes(
@@ -277,51 +271,21 @@ internal class IdeaVimSneakExtension : VimExtension {
* Map some <Plug>(keys) command to given handler * Map some <Plug>(keys) command to given handler
* and create mapping to <Plug>(prefix)[keys] * and create mapping to <Plug>(prefix)[keys]
*/ */
private fun VimExtension.mapToFunctionAndProvideKeys( private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: ExtensionHandler) {
keys: String, handler: ExtensionHandler, mappingModes: EnumSet<MappingMode>
) {
VimExtensionFacade.putExtensionHandlerMapping( VimExtensionFacade.putExtensionHandlerMapping(
mappingModes, MappingMode.NXO,
injector.parser.parseKeys(command(keys)), injector.parser.parseKeys(command(keys)),
owner, owner,
handler, handler,
false false
) )
VimExtensionFacade.putExtensionHandlerMapping( VimExtensionFacade.putKeyMapping(
mappingModes, MappingMode.NXO,
injector.parser.parseKeys(commandFromOriginalPlugin(keys)), injector.parser.parseKeys(keys),
owner, owner,
handler, injector.parser.parseKeys(command(keys)),
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)),
true true
) )
} }
private fun command(keys: String) = "<Plug>(sneak-$keys)" 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 package com.maddyhome.idea.vim.extension.surround
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
@@ -26,26 +23,22 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegisterForCaret
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
@@ -81,15 +74,13 @@ internal class VimSurroundExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("ds"), owner, injector.parser.parseKeys("<Plug>DSurround"), true) putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("ds"), owner, injector.parser.parseKeys("<Plug>DSurround"), true)
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true) putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
} }
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
} }
private class YSurroundHandler : ExtensionHandler { private class YSurroundHandler : ExtensionHandler {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC setOperatorFunction(Operator())
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
} }
} }
@@ -101,7 +92,7 @@ internal class VimSurroundExtension : VimExtension {
val ijEditor = editor.ij val ijEditor = editor.ij
val c = getChar(ijEditor) val c = getChar(ijEditor)
if (c.code == 0) return if (c.code == 0) return
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return val pair = getOrInputPair(c, ijEditor) ?: return
editor.forEachCaret { editor.forEachCaret {
val line = it.getBufferPosition().line val line = it.getBufferPosition().line
@@ -110,7 +101,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset) val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) { if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1) val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it, count = operatorArguments.count1) performSurround(pair, range, it)
} }
// it.moveToOffset(lineStartOffset) // it.moveToOffset(lineStartOffset)
} }
@@ -130,13 +121,15 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler { private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway // 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 return
} }
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) 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) val charTo = getChar(editor.ij)
if (charTo.code == 0) return if (charTo.code == 0) return
val newSurround = getOrInputPair(charTo, editor.ij, context.ij) ?: return val newSurround = getOrInputPair(charTo, editor.ij) ?: return
runWriteAction { change(editor, context, charFrom, newSurround) } runWriteAction { change(editor, context, charFrom, newSurround) }
} }
companion object { companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) { 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 // Save old register values for carets
val surroundings = editor.sortedCarets() val surroundings = editor.sortedCarets()
.map { .map {
@@ -228,12 +217,12 @@ internal class VimSurroundExtension : VimExtension {
val searchHelper = injector.searchHelper val searchHelper = injector.searchHelper
return when (char) { return when (char) {
't' -> searchHelper.findBlockTagRange(editor, caret, 1, true) 't' -> searchHelper.findBlockTagRange(editor, caret, 1, true)
'(', ')', 'b' -> findBlockRange(editor, caret, '(', 1, true) '(', ')', 'b' -> searchHelper.findBlockRange(editor, caret, '(', 1, true)
'[', ']' -> findBlockRange(editor, caret, '[', 1, true) '[', ']' -> searchHelper.findBlockRange(editor, caret, '[', 1, true)
'{', '}', 'B' -> findBlockRange(editor, caret, '{', 1, true) '{', '}', 'B' -> searchHelper.findBlockRange(editor, caret, '{', 1, true)
'<', '>' -> findBlockRange(editor, caret, '<', 1, true) '<', '>' -> searchHelper.findBlockRange(editor, caret, '<', 1, true)
'`', '\'', '"' -> { '`', '\'', '"' -> {
val caretOffset = caret.offset val caretOffset = caret.offset.point
val text = editor.text() val text = editor.text()
if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) { if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) {
TextRange(caretOffset - 1, caretOffset + 1) TextRange(caretOffset - 1, caretOffset + 1)
@@ -268,42 +257,21 @@ internal class VimSurroundExtension : VimExtension {
} }
} }
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction { private class Operator : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = vimEditor.ij val ijEditor = editor.ij
val c = getChar(ijEditor) val c = getChar(ijEditor)
if (c.code == 0) return true if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false val pair = getOrInputPair(c, ijEditor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
runWriteAction { val range = getSurroundRange(editor.currentCaret()) ?: return false
val change = VimPlugin.getChange() performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
if (supportsMultipleCursors) {
ijEditor.runWithEveryCaretAndRestore {
applyOnce(ijEditor, change, pair, count)
}
}
else {
applyOnce(ijEditor, change, pair, count)
// Jump back to start // Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
}
}
return true 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? { private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor val editor = caret.editor
val ijEditor = editor.ij val ijEditor = editor.ij
@@ -320,9 +288,7 @@ private val LOG = logger<VimSurroundExtension>()
private const val REGISTER = '"' private const val REGISTER = '"'
private const val OPERATOR_FUNC = "SurroundOperatorFunc" private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val SURROUND_PAIRS = mapOf( private val SURROUND_PAIRS = mapOf(
'b' to ("(" to ")"), 'b' to ("(" to ")"),
@@ -348,8 +314,8 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
null null
} }
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? { private fun inputTagPair(editor: Editor): Pair<String, String>? {
val tagInput = inputString(editor, context, "<", '>') val tagInput = inputString(editor, "<", '>')
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput) val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) { return if (matcher.find()) {
val tagName = matcher.group(1) val tagName = matcher.group(1)
@@ -362,18 +328,17 @@ private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, Str
private fun inputFunctionName( private fun inputFunctionName(
editor: Editor, editor: Editor,
context: DataContext,
withInternalSpaces: Boolean, withInternalSpaces: Boolean,
): Pair<String, String>? { ): Pair<String, String>? {
val functionNameInput = inputString(editor, context, "function: ", null) val functionNameInput = inputString(editor, "function: ", null)
if (functionNameInput.isEmpty()) return null if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")" return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
} }
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) { private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor, context) '<', 't' -> inputTagPair(editor)
'f' -> inputFunctionName(editor, context, false) 'f' -> inputFunctionName(editor, false)
'F' -> inputFunctionName(editor, context, true) 'F' -> inputFunctionName(editor, true)
else -> getSurroundPair(c) else -> getSurroundPair(c)
} }
@@ -389,15 +354,15 @@ private fun getChar(editor: Editor): Char {
return res 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 { runWriteAction {
val editor = caret.editor val editor = caret.editor
val change = VimPlugin.getChange() 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 isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine() val hasNewLine = editor.endsWithNewLine()
val rightSurround = (if (tagsOnNewLines) { val rightSurround = if (tagsOnNewLines) {
if (isEOF && !hasNewLine) { if (isEOF && !hasNewLine) {
"\n" + pair.second "\n" + pair.second
} else { } else {
@@ -405,7 +370,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
} }
} else { } else {
pair.second pair.second
}).let { RepeatedCharSequence.of(it, count) } }
change.insertText(editor, caret, range.startOffset, leftSurround) change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround) change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)

View File

@@ -138,7 +138,7 @@ public class VimTextObjEntireExtension implements VimExtension {
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing); final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
//noinspection DuplicatedCode //noinspection DuplicatedCode
if (!vimStateMachine.isOperatorPending(editor.getMode())) { if (!vimStateMachine.isOperatorPending()) {
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> { ((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0); final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
if (range != null) { if (range != null) {

View File

@@ -267,7 +267,7 @@ public class VimIndentObject implements VimExtension {
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow); final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
if (!vimStateMachine.isOperatorPending(editor.getMode())) { if (!vimStateMachine.isOperatorPending()) {
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> { ((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0); final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
if (range != null) { if (range != null) {

View File

@@ -68,17 +68,17 @@ import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import java.math.BigInteger import java.math.BigInteger
import java.util.* import java.util.*
import java.util.function.Consumer import java.util.function.Consumer
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
/** /**
* Provides all the insert/replace related functionality * Provides all the insert/replace related functionality
@@ -197,7 +197,7 @@ public class ChangeGroup : VimChangeGroupBase() {
val allowWrap = injector.options(editor).whichwrap.contains("~") val allowWrap = injector.options(editor).whichwrap.contains("~")
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap) var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
if (motion is Motion.Error) return false 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( motion = injector.motion.getHorizontalMotion(
editor, editor,
caret, caret,
@@ -235,7 +235,8 @@ public class ChangeGroup : VimChangeGroupBase() {
} }
val lineLength = editor.lineLength(line) val lineLength = editor.lineLength(line)
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) { 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) val offset = editor.getLineEndOffset(line)
insertText(editor, caret, offset, pad) insertText(editor, caret, offset, pad)
} }
@@ -394,7 +395,6 @@ public class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext, context: ExecutionContext,
range: TextRange, range: TextRange,
) { ) {
val startPos = editor.offsetToBufferPosition(caret.offset)
val startOffset = editor.getLineStartForOffset(range.startOffset) val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset) val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor val ijEditor = (editor as IjVimEditor).editor
@@ -419,7 +419,11 @@ public class ChangeGroup : VimChangeGroupBase() {
} }
} }
val afterAction = { 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) restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
} }
if (project != null) { if (project != null) {
@@ -450,7 +454,7 @@ public class ChangeGroup : VimChangeGroupBase() {
dir: Int, dir: Int,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
) { ) {
val start = caret.offset val start = caret.offset.point
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true) val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments) indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
} }
@@ -484,7 +488,7 @@ public class ChangeGroup : VimChangeGroupBase() {
// Remember the current caret column // Remember the current caret column
val intendedColumn = caret.vimLastColumn 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 sline = editor.offsetToBufferPosition(range.startOffset).line
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset) val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0) val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
@@ -531,7 +535,7 @@ public class ChangeGroup : VimChangeGroupBase() {
val soff = editor.getLineStartOffset(l) val soff = editor.getLineStartOffset(l)
val eoff = editor.getLineEndOffset(l, true) val eoff = editor.getLineEndOffset(l, true)
val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l) 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()) val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble())
.toInt() .toInt()
if (col > 0 || soff != eoff) { if (col > 0 || soff != eoff) {
@@ -573,62 +577,48 @@ public class ChangeGroup : VimChangeGroupBase() {
} }
val startOffset = editor.getLineStartOffset(startLine) val startOffset = editor.getLineStartOffset(startLine)
val endOffset = editor.getLineEndOffset(endLine) val endOffset = editor.getLineEndOffset(endLine)
return sortTextRange(editor, caret, startOffset, endOffset, lineComparator, sortOptions)
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(startOffset, endOffset))
val lines = selectedText.split("\n")
val modifiedLines = sortOptions.pattern?.let {
if (sortOptions.sortOnPattern) {
extractPatternFromLines(editor, lines, startLine, it)
} else {
deletePatternFromLines(editor, lines, startLine, it)
} }
} ?: lines
val sortedLines = lines.zip(modifiedLines)
.sortedWith { l1, l2 -> lineComparator.compare(l1.second, l2.second) }
.map {it.first}
.toMutableList()
if (sortOptions.unique) { /**
val iterator = sortedLines.iterator() * 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 var previous: String? = null
while (iterator.hasNext()) { while (iterator.hasNext()) {
val current = iterator.next() 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() iterator.remove()
} else { } else {
previous = current previous = current
} }
} }
} }
if (sortedLines.isEmpty()) { if (lines.size < 1) {
return false return false
} }
replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n")) replaceText(editor, caret, start, end, StringUtil.join(lines, "\n"))
return true 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 * Perform increment and decrement for numbers in visual mode
* *

View File

@@ -8,11 +8,9 @@
package com.maddyhome.idea.vim.group package com.maddyhome.idea.vim.group
import com.intellij.openapi.components.Service
import com.maddyhome.idea.vim.api.VimCommandGroupBase import com.maddyhome.idea.vim.api.VimCommandGroupBase
/** /**
* @author Elliot Courant * @author Elliot Courant
*/ */
@Service
internal class CommandGroup : VimCommandGroupBase() internal class CommandGroup : VimCommandGroupBase()

View File

@@ -11,9 +11,6 @@ package com.maddyhome.idea.vim.group;
import com.intellij.execution.impl.ConsoleViewImpl; import com.intellij.execution.impl.ConsoleViewImpl;
import com.intellij.find.EditorSearchSession; import com.intellij.find.EditorSearchSession;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.client.ClientAppSession;
import com.intellij.openapi.client.ClientKind;
import com.intellij.openapi.client.ClientSessionsManager;
import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State; import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.Storage;
@@ -25,10 +22,7 @@ import com.intellij.openapi.project.Project;
import com.maddyhome.idea.vim.KeyHandler; import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt; import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.helper.CommandStateHelper;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.UserDataManager;
import com.maddyhome.idea.vim.newapi.IjVimDocument; import com.maddyhome.idea.vim.newapi.IjVimDocument;
import com.maddyhome.idea.vim.newapi.IjVimEditor; import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener; import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener;
@@ -40,10 +34,10 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static com.maddyhome.idea.vim.api.VimInjectorKt.options; import static com.maddyhome.idea.vim.api.VimInjectorKt.options;
import static com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt.updateCaretsVisualAttributes;
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions; import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
/** /**
@@ -210,8 +204,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
} }
public void editorCreated(@NotNull Editor editor) { public void editorCreated(@NotNull Editor editor) {
UserDataManager.setVimInitialised(editor, true); DocumentManager.INSTANCE.addListeners(editor.getDocument());
VimPlugin.getKey().registerRequiredShortcutKeys(new IjVimEditor(editor)); VimPlugin.getKey().registerRequiredShortcutKeys(new IjVimEditor(editor));
initLineNumbers(editor); initLineNumbers(editor);
@@ -235,7 +228,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need // Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable. // to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
Runnable switchToInsertMode = () -> { Runnable switchToInsertMode = () -> {
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor)); ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(editor), null);
VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context); VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
KeyHandler.getInstance().reset(new IjVimEditor(editor)); KeyHandler.getInstance().reset(new IjVimEditor(editor));
}; };
@@ -253,13 +246,14 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
switchToInsertMode.run(); switchToInsertMode.run();
} }
}); });
updateCaretsVisualAttributes(new IjVimEditor(editor)); updateCaretsVisualAttributes(editor);
} }
public void editorDeinit(@NotNull Editor editor, boolean isReleased) { public void editorDeinit(@NotNull Editor editor, boolean isReleased) {
deinitLineNumbers(editor, isReleased); deinitLineNumbers(editor, isReleased);
UserDataManager.unInitializeEditor(editor); UserDataManager.unInitializeEditor(editor);
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor)); VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
DocumentManager.INSTANCE.removeListeners(editor.getDocument());
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor); CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
} }
@@ -290,18 +284,6 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
notifyIdeaJoin(((IjVimEditor) editor).getEditor().getProject(), editor); notifyIdeaJoin(((IjVimEditor) editor).getEditor().getProject(), editor);
} }
@Override
public void updateCaretsVisualAttributes(@NotNull VimEditor editor) {
Editor ijEditor = ((IjVimEditor) editor).getEditor();
CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
}
@Override
public void updateCaretsVisualPosition(@NotNull VimEditor editor) {
Editor ijEditor = ((IjVimEditor) editor).getEditor();
CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
}
public static class NumberChangeListener implements EffectiveOptionValueChangeListener { public static class NumberChangeListener implements EffectiveOptionValueChangeListener {
public static NumberChangeListener INSTANCE = new NumberChangeListener(); public static NumberChangeListener INSTANCE = new NumberChangeListener();
@@ -342,45 +324,20 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
} }
} }
@NotNull
@Override @Override
public @NotNull Collection<VimEditor> getEditorsRaw() { public Collection<VimEditor> localEditors() {
return getLocalEditors() return HelperKt.localEditors().stream()
.map(IjVimEditor::new) .map(IjVimEditor::new)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@NotNull @NotNull
@Override @Override
public Collection<VimEditor> getEditors() { public Collection<VimEditor> localEditors(@NotNull VimDocument buffer) {
return getLocalEditors()
.filter(UserDataManager::getVimInitialised)
.map(IjVimEditor::new)
.collect(Collectors.toList());
}
@NotNull
@Override
public Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument(); final Document document = ((IjVimDocument)buffer).getDocument();
return getLocalEditors() return HelperKt.localEditors(document).stream()
.filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
.map(IjVimEditor::new) .map(IjVimEditor::new)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private Stream<Editor> getLocalEditors() {
// Always fetch local editors. If we're hosting a Code With Me session, any connected guests will create hidden
// editors to handle syntax highlighting, completion requests, etc. We need to make sure that IdeaVim only makes
// changes (e.g. adding search highlights) to local editors, so things don't incorrectly flow through to any Clients.
// In non-CWM scenarios, or if IdeaVim is installed on the Client, there are only ever local editors, so this will
// also work there. In Gateway remote development scenarios, IdeaVim should not be installed on the host, only the
// Client, so all should work there too.
// Note that most IdeaVim operations are in response to interactive keystrokes, which would mean that
// ClientEditorManager.getCurrentInstance would return local editors. However, some operations are in response to
// events such as document change (to update search highlights) and these can come from CWM guests, and we'd get the
// remote editors.
// This invocation will always get local editors, regardless of current context.
final ClientAppSession localSession = ClientSessionsManager.getAppSessions(ClientKind.LOCAL).get(0);
return localSession.getService(ClientEditorManager.class).editors();
}
} }

View File

@@ -22,17 +22,16 @@ import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.VirtualFileSystem;
import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope; import com.intellij.psi.search.ProjectScope;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.helper.EditorHelper; import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.EditorHelperRt; import com.maddyhome.idea.vim.helper.EditorHelperRt;
@@ -41,13 +40,10 @@ import com.maddyhome.idea.vim.helper.SearchHelper;
import com.maddyhome.idea.vim.newapi.ExecuteExtensionKt; import com.maddyhome.idea.vim.newapi.ExecuteExtensionKt;
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext; import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
import com.maddyhome.idea.vim.newapi.IjVimEditor; import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
@@ -442,35 +438,14 @@ public class FileGroup extends VimFileBase {
private static final @NotNull Logger logger = Logger.getInstance(FileGroup.class.getName()); private static final @NotNull Logger logger = Logger.getInstance(FileGroup.class.getName());
/** /**
* Respond to editor tab selection and remember the last used tab * This method listens for editor tab changes so any insert/replace modes that need to be reset can be.
*/ */
public static void fileEditorManagerSelectionChangedCallback(@NotNull FileEditorManagerEvent event) { public static void fileEditorManagerSelectionChangedCallback(@NotNull FileEditorManagerEvent event) {
// The user has changed the editor they are working with - exit insert/replace mode, and complete any
// appropriate repeat
if (event.getOldFile() != null) { if (event.getOldFile() != null) {
LastTabService.getInstance(event.getManager().getProject()).setLastTab(event.getOldFile()); LastTabService.getInstance(event.getManager().getProject()).setLastTab(event.getOldFile());
} }
} }
@Nullable
@Override
public VimEditor selectEditor(@NotNull String projectId, @NotNull String documentPath, @Nullable String protocol) {
VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol);
if (fileSystem == null) return null;
VirtualFile virtualFile = fileSystem.findFileByPath(documentPath);
if (virtualFile == null) return null;
Project project = Arrays.stream(ProjectManager.getInstance().getOpenProjects())
.filter(p -> injector.getFile().getProjectId(p).equals(projectId))
.findFirst().orElseThrow();
Editor editor = selectEditor(project, virtualFile);
if (editor == null) return null;
return new IjVimEditor(editor);
}
@NotNull
@Override
public String getProjectId(@NotNull Object project) {
if (!(project instanceof Project)) throw new IllegalArgumentException();
return ((Project) project).getName();
}
} }

View File

@@ -20,6 +20,7 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
*/ */
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) { public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
public var ide: String by optionProperty(IjOptions.ide) public var ide: String by optionProperty(IjOptions.ide)
public var ideamarks: Boolean by optionProperty(IjOptions.ideamarks) public var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
public var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon) public var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
@@ -28,15 +29,15 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys) public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids) public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
public var visualdelay: Int by optionProperty(IjOptions.visualdelay) public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
// Temporary options to control work-in-progress behaviour // Temporary options to control work-in-progress behaviour
public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
public var oldundo: Boolean by optionProperty(IjOptions.oldundo) public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps) public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex) public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation) public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
} }
/** /**

View File

@@ -33,6 +33,8 @@ public object IjOptions {
Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux")) Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux"))
} }
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true))
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true))
public val ide: StringOption = addOption( public val ide: StringOption = addOption(
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition) StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
) )
@@ -79,16 +81,13 @@ public object IjOptions {
"<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>") "<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
) )
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false)) public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100)) public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true))
// Temporary feature flags during development, not really intended for external use public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true)) public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true)) public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true)) public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isTemporary = true))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which // This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
// derives from Option<VimInt> // derives from Option<VimInt>

View File

@@ -15,38 +15,38 @@ import com.maddyhome.idea.vim.statistic.VimscriptState
internal class IjStatisticsService : VimStatistics { internal class IjStatisticsService : VimStatistics {
override fun logTrackedAction(actionId: String) { override fun logTrackedAction(actionId: String) {
ActionTracker.Util.logTrackedAction(actionId) ActionTracker.logTrackedAction(actionId)
} }
override fun logCopiedAction(actionId: String) { override fun logCopiedAction(actionId: String) {
ActionTracker.Util.logCopiedAction(actionId) ActionTracker.logCopiedAction(actionId)
} }
override fun setIfLoopUsed(value: Boolean) { override fun setIfLoopUsed(value: Boolean) {
VimscriptState.Util.isLoopUsed = value VimscriptState.isLoopUsed = value
} }
override fun setIfMapExprUsed(value: Boolean) { override fun setIfMapExprUsed(value: Boolean) {
VimscriptState.Util.isMapExprUsed = value VimscriptState.isMapExprUsed = value
} }
override fun setIfFunctionCallUsed(value: Boolean) { override fun setIfFunctionCallUsed(value: Boolean) {
VimscriptState.Util.isFunctionCallUsed = value VimscriptState.isFunctionCallUsed = value
} }
override fun setIfFunctionDeclarationUsed(value: Boolean) { override fun setIfFunctionDeclarationUsed(value: Boolean) {
VimscriptState.Util.isFunctionDeclarationUsed = value VimscriptState.isFunctionDeclarationUsed = value
} }
override fun setIfIfUsed(value: Boolean) { override fun setIfIfUsed(value: Boolean) {
VimscriptState.Util.isIfUsed = value VimscriptState.isIfUsed = value
} }
override fun addExtensionEnabledWithPlug(extension: String) { override fun addExtensionEnabledWithPlug(extension: String) {
VimscriptState.Util.extensionsEnabledWithPlug.add(extension) VimscriptState.extensionsEnabledWithPlug.add(extension)
} }
override fun addSourcedFile(path: String) { override fun addSourcedFile(path: String) {
VimscriptState.Util.sourcedFiles.add(path) VimscriptState.sourcedFiles.add(path)
} }
} }

View File

@@ -1,61 +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.group
import com.intellij.lang.CodeDocumentationAwareCommenter
import com.intellij.lang.LanguageCommenters
import com.intellij.openapi.components.Service
import com.intellij.psi.PsiComment
import com.intellij.psi.util.PsiTreeUtil
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimPsiService
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
@Service
public class IjVimPsiService: VimPsiService {
override fun getCommentAtPos(editor: VimEditor, pos: Int): Pair<TextRange, Pair<String, String>?>? {
val psiFile = PsiHelper.getFile(editor.ij) ?: return null
val psiElement = psiFile.findElementAt(pos) ?: return null
val language = psiElement.language
val commenter = LanguageCommenters.INSTANCE.forLanguage(language)
val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null
val commentText = psiComment.text
val blockCommentPrefix = commenter.blockCommentPrefix
val blockCommentSuffix = commenter.blockCommentSuffix
val docCommentPrefix = (commenter as? CodeDocumentationAwareCommenter)?.documentationCommentPrefix
val docCommentSuffix = (commenter as? CodeDocumentationAwareCommenter)?.documentationCommentSuffix
val prefixToSuffix: Pair<String, String>? =
if (docCommentPrefix != null && docCommentSuffix != null && commentText.startsWith(docCommentPrefix) && commentText.endsWith(docCommentSuffix)) {
docCommentPrefix to docCommentSuffix
}
else if (blockCommentPrefix != null && blockCommentSuffix != null && commentText.startsWith(blockCommentPrefix) && commentText.endsWith(blockCommentSuffix)) {
blockCommentPrefix to blockCommentSuffix
}
else {
null
}
return Pair(psiComment.textRange.vim, prefixToSuffix)
}
override fun getDoubleQuotedString(editor: VimEditor, pos: Int, isInner: Boolean): TextRange? {
// TODO[ideavim] It wasn't implemented before, but implementing it will significantly improve % motion
return getDoubleQuotesRangeNoPSI(editor.text(), pos, isInner)
}
override fun getSingleQuotedString(editor: VimEditor, pos: Int, isInner: Boolean): TextRange? {
// TODO[ideavim] It wasn't implemented before, but implementing it will significantly improve % motion
return getSingleQuotesRangeNoPSI(editor.text(), pos, isInner)
}
}

View File

@@ -26,12 +26,10 @@ import com.maddyhome.idea.vim.EventFacade;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction; import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.action.change.LazyVimCommand; import com.maddyhome.idea.vim.action.change.LazyVimCommand;
import com.maddyhome.idea.vim.api.NativeAction; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
import com.maddyhome.idea.vim.command.MappingMode; import com.maddyhome.idea.vim.command.MappingMode;
import com.maddyhome.idea.vim.ex.ExOutputModel; import com.maddyhome.idea.vim.ex.ExOutputModel;
import com.maddyhome.idea.vim.helper.HelperKt;
import com.maddyhome.idea.vim.key.*; import com.maddyhome.idea.vim.key.*;
import com.maddyhome.idea.vim.newapi.IjNativeAction; import com.maddyhome.idea.vim.newapi.IjNativeAction;
import com.maddyhome.idea.vim.newapi.IjVimEditor; import com.maddyhome.idea.vim.newapi.IjVimEditor;
@@ -101,9 +99,9 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
@Override @Override
public void updateShortcutKeysRegistration() { public void updateShortcutKeysRegistration() {
for (VimEditor editor : injector.getEditorGroup().getEditors()) { for (Editor editor : HelperKt.localEditors()) {
unregisterShortcutKeys(editor); unregisterShortcutKeys(new IjVimEditor(editor));
registerRequiredShortcutKeys(editor); registerRequiredShortcutKeys(new IjVimEditor(editor));
} }
} }
@@ -230,7 +228,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) { private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
for (KeyStroke key : keys) { for (KeyStroke key : keys) {
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
if (!injector.getApplication().isOctopusEnabled() || if (!injector.getOptionGroup().getGlobalOptions().getOctopushandler() ||
!(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) && !(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
!(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) { !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner)); getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));

View File

@@ -1,68 +0,0 @@
package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import java.util.function.BooleanSupplier
internal object MacroAutoImport {
fun run(editor: Editor, dataContext: DataContext) {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
return
}
val importers = ReferenceImporter.EP_NAME.extensionList
if (importers.isEmpty()) {
return
}
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
override fun run(indicator: ProgressIndicator) {
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
val fixes = mutableListOf<BooleanSupplier>()
file.accept(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
for (reference in element.references) {
if (reference.resolve() != null) {
continue
}
for (importer in importers) {
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
?.let(fixes::add)
}
}
super.visitElement(element)
}
})
return@nonBlocking fixes
}.executeSynchronously()
ApplicationManager.getApplication().invokeAndWait {
WriteCommandAction.writeCommandAction(project)
.withName("Auto Import")
.withGroupId("IdeaVimAutoImportAfterMacro")
.shouldRecordActionForActiveDocument(true)
.run<RuntimeException> {
fixes.forEach { it.asBoolean }
}
}
}
})
}
}

View File

@@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.completion.CompletionPhase import com.intellij.codeInsight.completion.CompletionPhase
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
@@ -22,12 +21,10 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.MessageHelper.message import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.macro.VimMacroBase import com.maddyhome.idea.vim.macro.VimMacroBase
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
/** /**
* Used to handle playback of macros * Used to handle playback of macros
*/ */
@Service
internal class MacroGroup : VimMacroBase() { internal class MacroGroup : VimMacroBase() {
// If it's null, this is the top macro (as in most cases). If it's not null, this macro is executed from top macro // If it's null, this is the top macro (as in most cases). If it's not null, this macro is executed from top macro
@@ -78,12 +75,11 @@ internal class MacroGroup : VimMacroBase() {
} catch (e: ProcessCanceledException) { } catch (e: ProcessCanceledException) {
return@runnable return@runnable
} }
val keyHandler = getInstance()
ProgressManager.getInstance().executeNonCancelableSection { ProgressManager.getInstance().executeNonCancelableSection {
// Prevent autocompletion during macros. // Prevent autocompletion during macros.
// See https://github.com/JetBrains/ideavim/pull/772 for details // See https://github.com/JetBrains/ideavim/pull/772 for details
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion) CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
keyHandler.handleKey(editor, key, context, keyHandler.keyHandlerState) getInstance().handleKey(editor, key, context)
} }
if (injector.messages.isError()) return@runnable if (injector.messages.isError()) return@runnable
} }
@@ -94,9 +90,6 @@ internal class MacroGroup : VimMacroBase() {
} finally { } finally {
keyStack.removeFirst() keyStack.removeFirst()
} }
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
} }
if (isInternalMacro) { if (isInternalMacro) {

View File

@@ -8,27 +8,43 @@
package com.maddyhome.idea.vim.group package com.maddyhome.idea.vim.group
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.fileEditor.FileEditorManagerEvent import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.fileEditor.impl.EditorWindow import com.intellij.openapi.fileEditor.impl.EditorWindow
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.util.MathUtil.clamp
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroupBase import com.maddyhome.idea.vim.api.VimChangeGroupBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimMotionGroupBase import com.maddyhome.idea.vim.api.VimMotionGroupBase
import com.maddyhome.idea.vim.api.addJump
import com.maddyhome.idea.vim.api.anyNonWhitespace import com.maddyhome.idea.vim.api.anyNonWhitespace
import com.maddyhome.idea.vim.api.getJump
import com.maddyhome.idea.vim.api.getJumpSpot
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
import com.maddyhome.idea.vim.api.getVisualLineCount import com.maddyhome.idea.vim.api.getVisualLineCount
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.lineLength import com.maddyhome.idea.vim.api.lineLength
import com.maddyhome.idea.vim.api.normalizeColumn
import com.maddyhome.idea.vim.api.normalizeLine
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.api.normalizeVisualColumn import com.maddyhome.idea.vim.api.normalizeVisualColumn
import com.maddyhome.idea.vim.api.normalizeVisualLine import com.maddyhome.idea.vim.api.normalizeVisualLine
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.api.visualLineToBufferLine import com.maddyhome.idea.vim.api.visualLineToBufferLine
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.command.MotionType
@@ -37,9 +53,12 @@ import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExOutputModel import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.handler.Motion.AdjustedOffset
import com.maddyhome.idea.vim.handler.MotionActionHandler import com.maddyhome.idea.vim.handler.MotionActionHandler
import com.maddyhome.idea.vim.handler.TextObjectActionHandler import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.handler.toMotionOrError
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.SearchHelper
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.fileSize import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
@@ -47,25 +66,46 @@ import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.listener.AppCodeTemplates import com.maddyhome.idea.vim.listener.AppCodeTemplates
import com.maddyhome.idea.vim.mark.Mark
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext 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.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import org.jetbrains.annotations.Range import org.jetbrains.annotations.Range
import java.io.File
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** /**
* This handles all motion related commands and marks * This handles all motion related commands and marks
*/ */
@Service
internal class MotionGroup : VimMotionGroupBase() { internal class MotionGroup : VimMotionGroupBase() {
override fun onAppCodeMovement(editor: VimEditor, caret: VimCaret, offset: Int, oldOffset: Int) { override fun onAppCodeMovement(editor: VimEditor, caret: VimCaret, offset: Int, oldOffset: Int) {
AppCodeTemplates.onMovement(editor.ij, caret.ij, oldOffset < offset) AppCodeTemplates.onMovement(editor.ij, caret.ij, oldOffset < offset)
} }
private fun selectEditor(project: Project, mark: Mark): Editor? {
val virtualFile = markToVirtualFile(mark) ?: return null
return selectEditor(project, virtualFile)
}
private fun markToVirtualFile(mark: Mark): VirtualFile? {
val protocol = mark.protocol
val fileSystem: VirtualFileSystem? = VirtualFileManager.getInstance().getFileSystem(protocol)
return fileSystem?.findFileByPath(mark.filepath)
}
private fun selectEditor(project: Project?, file: VirtualFile) =
VimPlugin.getFile().selectEditor(project, file)
override fun moveCaretToMatchingPair(editor: VimEditor, caret: ImmutableVimCaret): Motion {
return SearchHelper.findMatchingPairOnCurrentLine(editor.ij, caret.ij).toMotionOrError()
}
override fun moveCaretToFirstDisplayLine( override fun moveCaretToFirstDisplayLine(
editor: VimEditor, editor: VimEditor,
caret: ImmutableVimCaret, caret: ImmutableVimCaret,
@@ -88,12 +128,85 @@ internal class MotionGroup : VimMotionGroupBase() {
return moveCaretToScreenLocation(editor.ij, caret.ij, ScreenLocation.MIDDLE, 0, false) return moveCaretToScreenLocation(editor.ij, caret.ij, ScreenLocation.MIDDLE, 0, false)
} }
override fun moveCaretToMark(caret: ImmutableVimCaret, ch: Char, toLineStart: Boolean): Motion {
val markService = injector.markService
val mark = markService.getMark(caret, ch) ?: return Motion.Error
val caretEditor = caret.editor
val caretVirtualFile = EditorHelper.getVirtualFile((caretEditor as IjVimEditor).editor)
val line = mark.line
if (caretVirtualFile!!.path == mark.filepath) {
val offset = if (toLineStart) {
moveCaretToLineStartSkipLeading(caretEditor, line)
} else {
caretEditor.bufferPositionToOffset(BufferPosition(line, mark.col, false))
}
return offset.toMotionOrError()
}
val project = caretEditor.editor.project
val markEditor = selectEditor(project!!, mark)
if (markEditor != null) {
// todo should we move all the carets or only one?
for (carett in markEditor.caretModel.allCarets) {
val offset = if (toLineStart) {
moveCaretToLineStartSkipLeading(IjVimEditor(markEditor), line)
} else {
// todo should it be the same as getting offset above?
markEditor.logicalPositionToOffset(LogicalPosition(line, mark.col))
}
IjVimCaret(carett!!).moveToOffset(offset)
}
}
return Motion.Error
}
override fun moveCaretToJump(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Motion {
val jumpService = injector.jumpService
val spot = jumpService.getJumpSpot(editor)
val (line, col, fileName) = jumpService.getJump(editor, count) ?: return Motion.Error
val vf = EditorHelper.getVirtualFile(editor.ij) ?: return Motion.Error
val lp = BufferPosition(line, col, false)
val lpNative = LogicalPosition(line, col, false)
return if (vf.path != fileName) {
val newFile = LocalFileSystem.getInstance().findFileByPath(fileName.replace(File.separatorChar, '/'))
?: return Motion.Error
selectEditor(editor.ij.project, newFile)?.let { newEditor ->
if (spot == -1) {
jumpService.addJump(editor, false)
}
newEditor.vim.let {
it.currentCaret().moveToOffset(it.normalizeOffset(newEditor.logicalPositionToOffset(lpNative), false))
}
}
Motion.Error
} else {
if (spot == -1) {
jumpService.addJump(editor, false)
}
editor.bufferPositionToOffset(lp).toMotionOrError()
}
}
override fun moveCaretToCurrentDisplayLineMiddle(editor: VimEditor, caret: ImmutableVimCaret): Motion { override fun moveCaretToCurrentDisplayLineMiddle(editor: VimEditor, caret: ImmutableVimCaret): Motion {
val width = EditorHelper.getApproximateScreenWidth(editor.ij) / 2 val width = EditorHelper.getApproximateScreenWidth(editor.ij) / 2
val len = editor.lineLength(editor.currentCaret().getBufferPosition().line) val len = editor.lineLength(editor.currentCaret().getBufferPosition().line)
return moveCaretToColumn(editor, caret, max(0, min(len - 1, width)), false) return moveCaretToColumn(editor, caret, max(0, min(len - 1, width)), false)
} }
override fun moveCaretToColumn(editor: VimEditor, caret: ImmutableVimCaret, count: Int, allowEnd: Boolean): Motion {
val line = caret.getLine().line
val column = editor.normalizeColumn(line, count, allowEnd)
val offset = editor.bufferPositionToOffset(BufferPosition(line, column, false))
return if (column != count) {
AdjustedOffset(offset, count)
} else {
AbsoluteOffset(offset)
}
}
override fun moveCaretToCurrentDisplayLineStart(editor: VimEditor, caret: ImmutableVimCaret): Motion { override fun moveCaretToCurrentDisplayLineStart(editor: VimEditor, caret: ImmutableVimCaret): Motion {
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line) val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
return moveCaretToColumn(editor, caret, col, false) return moveCaretToColumn(editor, caret, col, false)
@@ -104,7 +217,7 @@ internal class MotionGroup : VimMotionGroupBase() {
caret: ImmutableVimCaret, caret: ImmutableVimCaret,
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int { ): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line) val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
val bufferLine = caret.getLine() val bufferLine = caret.getLine().line
return editor.getLeadingCharacterOffset(bufferLine, col) return editor.getLeadingCharacterOffset(bufferLine, col)
} }
@@ -117,6 +230,36 @@ internal class MotionGroup : VimMotionGroupBase() {
return moveCaretToColumn(editor, caret, col, allowEnd) return moveCaretToColumn(editor, caret, col, allowEnd)
} }
override fun moveCaretToLineWithSameColumn(
editor: VimEditor,
line: Int,
caret: ImmutableVimCaret,
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
var c = caret.vimLastColumn
var l = line
if (l < 0) {
l = 0
c = 0
} else if (l >= editor.lineCount()) {
l = editor.normalizeLine(editor.lineCount() - 1)
c = editor.lineLength(l)
}
val newPos = BufferPosition(l, editor.normalizeColumn(l, c, false))
return editor.bufferPositionToOffset(newPos)
}
override fun moveCaretToLineWithStartOfLineOption(
editor: VimEditor,
line: Int,
caret: ImmutableVimCaret,
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
return if (injector.options(editor).startofline) {
moveCaretToLineStartSkipLeading(editor, line)
} else {
moveCaretToLineWithSameColumn(editor, line, caret)
}
}
/** /**
* If 'absolute' is true, then set tab index to 'value', otherwise add 'value' to tab index with wraparound. * If 'absolute' is true, then set tab index to 'value', otherwise add 'value' to tab index with wraparound.
*/ */
@@ -134,18 +277,30 @@ internal class MotionGroup : VimMotionGroupBase() {
} }
override fun moveCaretGotoPreviousTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int { override fun moveCaretGotoPreviousTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int {
val project = editor.ij.project ?: return editor.currentCaret().offset val project = editor.ij.project ?: return editor.currentCaret().offset.point
val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow
switchEditorTab(currentWindow, if (rawCount >= 1) -rawCount else -1, false) switchEditorTab(currentWindow, if (rawCount >= 1) -rawCount else -1, false)
return editor.currentCaret().offset return editor.currentCaret().offset.point
} }
override fun moveCaretGotoNextTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int { override fun moveCaretGotoNextTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int {
val absolute = rawCount >= 1 val absolute = rawCount >= 1
val project = editor.ij.project ?: return editor.currentCaret().offset val project = editor.ij.project ?: return editor.currentCaret().offset.point
val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow
switchEditorTab(currentWindow, if (absolute) rawCount - 1 else 1, absolute) switchEditorTab(currentWindow, if (absolute) rawCount - 1 else 1, absolute)
return editor.currentCaret().offset return editor.currentCaret().offset.point
}
override fun moveCaretToLinePercent(
editor: VimEditor,
caret: ImmutableVimCaret,
count: Int,
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
return moveCaretToLineWithStartOfLineOption(
editor,
editor.normalizeLine((editor.lineCount() * clamp(count, 0, 100) + 99) / 100 - 1),
caret,
)
} }
private enum class ScreenLocation { private enum class ScreenLocation {

View File

@@ -21,7 +21,6 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.KeyboardShortcut import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.keymap.KeymapUtil import com.intellij.openapi.keymap.KeymapUtil
@@ -56,7 +55,6 @@ import javax.swing.KeyStroke
* This service is can be used as application level and as project level service. * This service is can be used as application level and as project level service.
* If project is null, this means that this is an application level service and notification will be shown for all projects * If project is null, this means that this is an application level service and notification will be shown for all projects
*/ */
@Service(Service.Level.PROJECT, Service.Level.APP)
internal class NotificationService(private val project: Project?) { internal class NotificationService(private val project: Project?) {
// This constructor is used to create an applicationService // This constructor is used to create an applicationService
@Suppress("unused") @Suppress("unused")
@@ -278,7 +276,7 @@ internal class NotificationService(private val project: Project?) {
} }
if (id != null) { if (id != null) {
ActionTracker.Util.logTrackedAction(id) ActionTracker.logTrackedAction(id)
} }
} }
@@ -286,7 +284,7 @@ internal class NotificationService(private val project: Project?) {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
CopyPasteManager.getInstance().setContents(StringSelection(id ?: "")) CopyPasteManager.getInstance().setContents(StringSelection(id ?: ""))
if (id != null) { if (id != null) {
ActionTracker.Util.logCopiedAction(id) ActionTracker.logCopiedAction(id)
} }
notification?.expire() notification?.expire()

View File

@@ -20,7 +20,6 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.util.execution.ParametersListUtil import com.intellij.util.execution.ParametersListUtil
import com.intellij.util.text.CharSequenceReader import com.intellij.util.text.CharSequenceReader
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@@ -38,6 +37,7 @@ import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.NORMAL import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import java.io.BufferedWriter import java.io.BufferedWriter
@@ -85,27 +85,24 @@ public class ProcessGroup : VimProcessGroupBase() {
modeBeforeCommandProcessing = currentMode modeBeforeCommandProcessing = currentMode
val initText = getRange(editor, cmd) val initText = getRange(editor, cmd)
injector.markService.setVisualSelectionMarks(editor) injector.markService.setVisualSelectionMarks(editor)
editor.mode = Mode.CMD_LINE(currentMode) editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance() val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1) panel.activate(editor.ij, context.ij, ":", initText, 1)
} }
public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean { public override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean {
// This will only get called if somehow the key focus ended up in the editor while the ex entry window // This will only get called if somehow the key focus ended up in the editor while the ex entry window
// is open. So I'll put focus back in the editor and process the key. // is open. So I'll put focus back in the editor and process the key.
val panel = ExEntryPanel.getInstance() val panel = ExEntryPanel.getInstance()
if (panel.isActive) { if (panel.isActive) {
processResultBuilder.addExecutionStep { _, _, _ ->
requestFocus(panel.entry) requestFocus(panel.entry)
panel.handleKey(stroke) panel.handleKey(stroke)
}
return true return true
} else { } else {
processResultBuilder.addExecutionStep { _, lambdaEditor, _ -> getInstance(editor).mode = NORMAL()
lambdaEditor.mode = NORMAL() getInstance().reset(editor)
getInstance().reset(lambdaEditor)
}
return false return false
} }
} }
@@ -115,7 +112,7 @@ public class ProcessGroup : VimProcessGroupBase() {
panel.deactivate(true) panel.deactivate(true)
var res = true var res = true
try { try {
editor.mode = NORMAL() getInstance(editor).mode = NORMAL()
logger.debug("processing command") logger.debug("processing command")
@@ -155,7 +152,7 @@ public class ProcessGroup : VimProcessGroupBase() {
} }
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) { public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
editor.mode = NORMAL() editor.vimStateMachine.mode = NORMAL()
getInstance().reset(editor) getInstance().reset(editor)
val panel = ExEntryPanel.getInstance() val panel = ExEntryPanel.getInstance()
panel.deactivate(true, resetCaret) panel.deactivate(true, resetCaret)
@@ -165,7 +162,7 @@ public class ProcessGroup : VimProcessGroupBase() {
val initText = getRange(editor, cmd) + "!" val initText = getRange(editor, cmd) + "!"
val currentMode = editor.mode val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" } check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
editor.mode = Mode.CMD_LINE(currentMode) editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance() val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1) panel.activate(editor.ij, context.ij, ":", initText, 1)
} }

View File

@@ -14,9 +14,9 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.state.mode.SelectionType;
import com.maddyhome.idea.vim.register.Register; import com.maddyhome.idea.vim.register.Register;
import com.maddyhome.idea.vim.register.VimRegisterGroupBase; import com.maddyhome.idea.vim.register.VimRegisterGroupBase;
import com.maddyhome.idea.vim.state.mode.SelectionType;
import org.jdom.Element; import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -37,10 +37,6 @@ public class RegisterGroup extends VimRegisterGroupBase implements PersistentSta
private static final Logger logger = Logger.getInstance(RegisterGroup.class); private static final Logger logger = Logger.getInstance(RegisterGroup.class);
public RegisterGroup() {
this.initClipboardOptionListener();
}
public void saveData(final @NotNull Element element) { public void saveData(final @NotNull Element element) {
logger.debug("Save registers data"); logger.debug("Save registers data");
final Element registersElement = new Element("registers"); final Element registersElement = new Element("registers");

View File

@@ -21,6 +21,8 @@ import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.fileEditor.FileEditorManagerEvent; import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.Ref;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
@@ -31,11 +33,12 @@ import com.maddyhome.idea.vim.ex.ExException;
import com.maddyhome.idea.vim.ex.ranges.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.*; import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.history.HistoryConstants; import com.maddyhome.idea.vim.history.HistoryConstants;
import com.maddyhome.idea.vim.newapi.*; 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.IjVimSearchGroup;
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener; import com.maddyhome.idea.vim.options.GlobalOptionChangeListener;
import com.maddyhome.idea.vim.regexp.CharPointer; import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.regexp.CharacterClasses;
import com.maddyhome.idea.vim.regexp.RegExp;
import com.maddyhome.idea.vim.ui.ModalEntry; import com.maddyhome.idea.vim.ui.ModalEntry;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel; import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.vimscript.model.VimLContext; import com.maddyhome.idea.vim.vimscript.model.VimLContext;
@@ -56,6 +59,7 @@ import java.text.ParsePosition;
import java.util.*; import java.util.*;
import static com.maddyhome.idea.vim.api.VimInjectorKt.*; import static com.maddyhome.idea.vim.api.VimInjectorKt.*;
import static com.maddyhome.idea.vim.helper.HelperKt.localEditors;
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase; import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions; import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER; import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
@@ -210,8 +214,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}` * @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
* @param direction The direction to search * @param direction The direction to search
*/ */
@Override @TestOnly
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern, public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
@NotNull String patternOffset, Direction direction) { @NotNull String patternOffset, Direction direction) {
if (globalIjOptions(injector).getUseNewRegex()) { if (globalIjOptions(injector).getUseNewRegex()) {
super.setLastSearchState(pattern, patternOffset, direction); super.setLastSearchState(pattern, patternOffset, direction);
@@ -538,7 +542,6 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* *
* @param editor The editor to search in * @param editor The editor to search in
* @param caret The caret to use for initial search offset, and to move for interactive substitution * @param caret The caret to use for initial search offset, and to move for interactive substitution
* @param context
* @param range Only search and substitute within the given line range. Must be valid * @param range Only search and substitute within the given line range. Must be valid
* @param excmd The command part of the ex command line, e.g. `s` or `substitute`, or `~` * @param excmd The command part of the ex command line, e.g. `s` or `substitute`, or `~`
* @param exarg The argument to the substitute command, such as `/{pattern}/{string}/[flags]` * @param exarg The argument to the substitute command, such as `/{pattern}/{string}/[flags]`
@@ -548,14 +551,11 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
@RWLockLabel.SelfSynchronized @RWLockLabel.SelfSynchronized
public boolean processSubstituteCommand(@NotNull VimEditor editor, public boolean processSubstituteCommand(@NotNull VimEditor editor,
@NotNull VimCaret caret, @NotNull VimCaret caret,
@NotNull ExecutionContext context,
@NotNull LineRange range, @NotNull LineRange range,
@NotNull @NonNls String excmd, @NotNull @NonNls String excmd,
@NotNull @NonNls String exarg, @NotNull @NonNls String exarg,
@NotNull VimLContext parent) { @NotNull VimLContext parent) {
if (globalIjOptions(injector).getUseNewRegex()) { if (globalIjOptions(injector).getUseNewRegex()) return super.processSubstituteCommand(editor, caret, range, excmd, exarg, parent);
return super.processSubstituteCommand(editor, caret, context, range, excmd, exarg, parent);
}
// Explicitly exit visual mode here, so that visual mode marks don't change when we move the cursor to a match. // Explicitly exit visual mode here, so that visual mode marks don't change when we move the cursor to a match.
List<ExException> exceptions = new ArrayList<>(); List<ExException> exceptions = new ArrayList<>();
@@ -812,7 +812,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
RangeHighlighter hl = RangeHighlighter hl =
SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor)editor).getEditor(), startoff, SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor)editor).getEditor(), startoff,
endoff); endoff);
final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), context, match, ((IjVimCaret)caret).getCaret(), startoff); final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), match, ((IjVimCaret)caret).getCaret(), startoff);
((IjVimEditor)editor).getEditor().getMarkupModel().removeHighlighter(hl); ((IjVimEditor)editor).getEditor().getMarkupModel().removeHighlighter(hl);
switch (choice) { switch (choice) {
case SUBSTITUTE_THIS: case SUBSTITUTE_THIS:
@@ -841,7 +841,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
caret.moveToOffset(startoff); caret.moveToOffset(startoff);
if (expression != null) { if (expression != null) {
try { try {
match = expression.evaluate(editor, context, parent).toInsertableString(); match =
expression.evaluate(editor, injector.getExecutionContextManager().onEditor(editor, null), parent).toInsertableString();
} }
catch (Exception e) { catch (Exception e) {
exceptions.add((ExException)e); exceptions.add((ExException)e);
@@ -992,9 +993,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
return new Pair<>(true, new Triple<>(regmatch, pattern, sp)); return new Pair<>(true, new Triple<>(regmatch, pattern, sp));
} }
private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
@NotNull ExecutionContext context,
@NotNull String match, @NotNull Caret caret, int startoff) {
final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT); final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT);
final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> { final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> {
final ReplaceConfirmationChoice choice; final ReplaceConfirmationChoice choice;
@@ -1028,6 +1027,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
else { else {
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method // XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
final ExEntryPanel exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts(); final ExEntryPanel exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts();
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(editor), null);
exEntryPanel.activate(editor, ((IjEditorExecutionContext)context).getContext(), MessageHelper.message("replace.with.0", match), "", 1); exEntryPanel.activate(editor, ((IjEditorExecutionContext)context).getContext(), MessageHelper.message("replace.with.0", match), "", 1);
new IjVimCaret(caret).moveToOffset(startoff); new IjVimCaret(caret).moveToOffset(startoff);
ModalEntry.INSTANCE.activate(new IjVimEditor(editor), keyStrokeProcessor); ModalEntry.INSTANCE.activate(new IjVimEditor(editor), keyStrokeProcessor);
@@ -1085,9 +1085,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
private @Nullable TextRange findNextSearchForGn(@NotNull VimEditor editor, int count, boolean forwards) { private @Nullable TextRange findNextSearchForGn(@NotNull VimEditor editor, int count, boolean forwards) {
if (forwards) { if (forwards) {
final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE); final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE);
return VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), editor.primaryCaret().getOffset(), count, searchOptions); return VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), editor.primaryCaret().getOffset().getPoint(), count, searchOptions);
} else { } else {
return searchBackward(editor, editor.primaryCaret().getOffset(), count); return searchBackward(editor, editor.primaryCaret().getOffset().getPoint(), count);
} }
} }
@@ -1200,18 +1200,15 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
public static DocumentSearchListener INSTANCE = new DocumentSearchListener(); public static DocumentSearchListener INSTANCE = new DocumentSearchListener();
@Contract(pure = true) @Contract(pure = true)
private DocumentSearchListener() { private DocumentSearchListener () {
} }
@Override @Override
public void documentChanged(@NotNull DocumentEvent event) { public void documentChanged(@NotNull DocumentEvent event) {
// Loop over all local editors for the changed document, across all projects, and update search highlights. for (Project project : ProjectManager.getInstance().getOpenProjects()) {
// Note that the change may have come from a remote guest in Code With Me scenarios (in which case
// ClientId.current will be a guest ID), but we don't care - we still need to add/remove highlights for the
// changed text. Make sure we only update local editors, though.
final Document document = event.getDocument(); final Document document = event.getDocument();
for (VimEditor vimEditor : injector.getEditorGroup().getEditors(new IjVimDocument(document))) {
final Editor editor = ((IjVimEditor)vimEditor).getEditor(); for (Editor editor : localEditors(document, project)) {
Collection<RangeHighlighter> hls = UserDataManager.getVimLastHighlighters(editor); Collection<RangeHighlighter> hls = UserDataManager.getVimLastHighlighters(editor);
if (hls == null) { if (hls == null) {
continue; continue;
@@ -1231,8 +1228,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
final Iterator<RangeHighlighter> iter = hls.iterator(); final Iterator<RangeHighlighter> iter = hls.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
final RangeHighlighter highlighter = iter.next(); final RangeHighlighter highlighter = iter.next();
if (!highlighter.isValid() || if (!highlighter.isValid() || (highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
(highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
iter.remove(); iter.remove();
editor.getMarkupModel().removeHighlighter(highlighter); editor.getMarkupModel().removeHighlighter(highlighter);
} }
@@ -1248,6 +1244,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
} }
} }
} }
}
//endregion //endregion

View File

@@ -111,7 +111,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
} }
private fun buildJump(place: PlaceInfo): Jump? { private fun buildJump(place: PlaceInfo): Jump? {
val editor = injector.editorGroup.getEditors().firstOrNull { it.ij.virtualFile == place.file } ?: return null val editor = injector.editorGroup.localEditors().firstOrNull { it.ij.virtualFile == place.file } ?: return null
val offset = place.caretPosition?.startOffset ?: return null val offset = place.caretPosition?.startOffset ?: return null
val bufferPosition = editor.offsetToBufferPosition(offset) val bufferPosition = editor.offsetToBufferPosition(offset)

View File

@@ -7,7 +7,6 @@
*/ */
package com.maddyhome.idea.vim.group package com.maddyhome.idea.vim.group
import com.intellij.codeWithMe.ClientId
import com.intellij.ide.bookmark.Bookmark import com.intellij.ide.bookmark.Bookmark
import com.intellij.ide.bookmark.BookmarkGroup import com.intellij.ide.bookmark.BookmarkGroup
import com.intellij.ide.bookmark.BookmarksListener import com.intellij.ide.bookmark.BookmarksListener
@@ -19,7 +18,7 @@ import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage import com.intellij.openapi.components.Storage
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.EditorFactory import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManager
@@ -29,11 +28,11 @@ import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.asSafely import com.intellij.util.asSafely
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimEditorGroup
import com.maddyhome.idea.vim.api.VimMarkService import com.maddyhome.idea.vim.api.VimMarkService
import com.maddyhome.idea.vim.api.VimMarkServiceBase import com.maddyhome.idea.vim.api.VimMarkServiceBase
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.SystemMarks.Companion.createOrGetSystemMark import com.maddyhome.idea.vim.group.SystemMarks.Companion.createOrGetSystemMark
import com.maddyhome.idea.vim.helper.localEditors
import com.maddyhome.idea.vim.mark.IntellijMark import com.maddyhome.idea.vim.mark.IntellijMark
import com.maddyhome.idea.vim.mark.Mark import com.maddyhome.idea.vim.mark.Mark
import com.maddyhome.idea.vim.mark.VimMark.Companion.create import com.maddyhome.idea.vim.mark.VimMark.Companion.create
@@ -194,10 +193,6 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
* This event indicates that a document is about to be changed. We use this event to update all the * This event indicates that a document is about to be changed. We use this event to update all the
* editor's marks if text is about to be deleted. * editor's marks if text is about to be deleted.
* *
* Note that the event is fired for both local changes and changes from remote guests in Code With Me scenarios (in
* which case [ClientId.current] will be the remote client). We don't care who caused it, we just need to update the
* stored marks.
*
* @param event The change event * @param event The change event
*/ */
override fun beforeDocumentChange(event: DocumentEvent) { override fun beforeDocumentChange(event: DocumentEvent) {
@@ -205,18 +200,15 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event") if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
if (event.oldLength == 0) return if (event.oldLength == 0) return
val doc = event.document val doc = event.document
val anEditor = getAnyEditorForDocument(doc) ?: return val anEditor = getAnEditor(doc) ?: return
injector.markService.updateMarksFromDelete(anEditor, event.offset, event.oldLength) injector.markService
.updateMarksFromDelete(IjVimEditor(anEditor), event.offset, event.oldLength)
} }
/** /**
* This event indicates that a document was just changed. We use this event to update all the editor's * This event indicates that a document was just changed. We use this event to update all the editor's
* marks if text was just added. * marks if text was just added.
* *
* Note that the event is fired for both local changes and changes from remote guests in Code With Me scenarios (in
* which case [ClientId.current] will be the remote client). We don't care who caused it, we just need to update the
* stored marks.
*
* @param event The change event * @param event The change event
*/ */
override fun documentChanged(event: DocumentEvent) { override fun documentChanged(event: DocumentEvent) {
@@ -224,19 +216,19 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event") if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
val doc = event.document val doc = event.document
val anEditor = getAnyEditorForDocument(doc) ?: return val anEditor = getAnEditor(doc) ?: return
injector.markService.updateMarksFromInsert(anEditor, event.offset, event.newLength) injector.markService
.updateMarksFromInsert(IjVimEditor(anEditor), event.offset, event.newLength)
} }
/** private fun getAnEditor(doc: Document): Editor? {
* Get any editor for the given document val editors = localEditors(doc)
* return if (editors.size > 0) {
* We need an editor to help calculate offsets for marks, and it doesn't matter which one we use, because they would editors[0]
* all return the same results. However, we cannot use [VimEditorGroup.getEditors] because the change might have } else {
* come from a remote guest and there might not be an open local editor. null
*/ }
private fun getAnyEditorForDocument(doc: Document) = }
EditorFactory.getInstance().getEditors(doc).firstOrNull()?.let { IjVimEditor(it) }
} }
class VimBookmarksListener(private val myProject: Project) : BookmarksListener { class VimBookmarksListener(private val myProject: Project) : BookmarksListener {

View File

@@ -8,11 +8,9 @@
package com.maddyhome.idea.vim.group package com.maddyhome.idea.vim.group
import com.intellij.openapi.components.Service
import org.apache.commons.codec.binary.Base64 import org.apache.commons.codec.binary.Base64
import org.jdom.Element import org.jdom.Element
@Service
internal class XMLGroup { internal class XMLGroup {
/** /**
* Set the text of an XML element, safely encode it if needed. * Set the text of an XML element, safely encode it if needed.

View File

@@ -14,7 +14,6 @@ import com.intellij.ide.DataManager
import com.intellij.ide.PasteProvider import com.intellij.ide.PasteProvider
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorEx
@@ -52,7 +51,6 @@ import com.maddyhome.idea.vim.state.mode.isChar
import com.maddyhome.idea.vim.state.mode.isLine import com.maddyhome.idea.vim.state.mode.isLine
import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.DataFlavor
@Service
internal class PutGroup : VimPutBase() { internal class PutGroup : VimPutBase() {
override fun getProviderForPasteViaIde( override fun getProviderForPasteViaIde(

View File

@@ -20,8 +20,9 @@ import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.hasVisualSelection import com.maddyhome.idea.vim.helper.hasVisualSelection
import com.maddyhome.idea.vim.helper.inInsertMode import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.inNormalMode import com.maddyhome.idea.vim.helper.inNormalMode
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.helper.isTemplateActive import com.maddyhome.idea.vim.helper.isTemplateActive
import com.maddyhome.idea.vim.helper.vimDisabled import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.listener.VimListenerManager import com.maddyhome.idea.vim.listener.VimListenerManager
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.OptionConstants import com.maddyhome.idea.vim.options.OptionConstants
@@ -29,6 +30,7 @@ import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inNormalMode import com.maddyhome.idea.vim.state.mode.inNormalMode
import com.maddyhome.idea.vim.state.mode.inSelectMode import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.inVisualMode import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect
@@ -53,7 +55,9 @@ internal object IdeaSelectionControl {
selectionSource: VimListenerManager.SelectionSource = VimListenerManager.SelectionSource.OTHER, selectionSource: VimListenerManager.SelectionSource = VimListenerManager.SelectionSource.OTHER,
) { ) {
VimVisualTimer.singleTask(editor.vim.mode) { initialMode -> VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
if (vimDisabled(editor)) return@singleTask
if (VimPlugin.isNotEnabled()) return@singleTask
if (editor.isIdeaVimDisabledHere) return@singleTask
logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode") logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
@@ -75,7 +79,7 @@ internal object IdeaSelectionControl {
logger.debug("Some carets have selection. State before adjustment: ${editor.vim.mode}") logger.debug("Some carets have selection. State before adjustment: ${editor.vim.mode}")
editor.vim.mode = Mode.NORMAL() editor.vim.vimStateMachine.mode = Mode.NORMAL()
activateMode(editor, chooseSelectionMode(editor, selectionSource, true)) activateMode(editor, chooseSelectionMode(editor, selectionSource, true))
} else { } else {
@@ -117,7 +121,7 @@ internal object IdeaSelectionControl {
is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType) is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType)
is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType) is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType)
is Mode.INSERT -> VimPlugin.getChange() is Mode.INSERT -> VimPlugin.getChange()
.insertBeforeCursor(editor.vim, injector.executionContextManager.getEditorExecutionContext(editor.vim)) .insertBeforeCursor(editor.vim, injector.executionContextManager.onEditor(editor.vim))
is Mode.NORMAL -> Unit is Mode.NORMAL -> Unit
else -> error("Unexpected mode: $mode") else -> error("Unexpected mode: $mode")

View File

@@ -12,13 +12,13 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.getLineEndForOffset import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.helper.vimSelectionStart import com.maddyhome.idea.vim.helper.vimSelectionStart
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inBlockSelection
internal fun moveCaretOneCharLeftFromSelectionEnd(editor: Editor, predictedMode: Mode) { internal fun moveCaretOneCharLeftFromSelectionEnd(editor: Editor, predictedMode: Mode) {
if (predictedMode !is Mode.VISUAL) { if (predictedMode !is Mode.VISUAL) {

View File

@@ -13,10 +13,10 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualMotionGroupBase import com.maddyhome.idea.vim.api.VimVisualMotionGroupBase
import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.command.engine import com.maddyhome.idea.vim.command.engine
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
/** /**
* @author Alex Plate * @author Alex Plate

View File

@@ -27,6 +27,7 @@ import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.removeUserData import com.intellij.openapi.util.removeUserData
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
@@ -38,6 +39,7 @@ import com.maddyhome.idea.vim.newapi.actionStartedFromVim
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.mode
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
@@ -337,9 +339,8 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) { override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
val enterKey = key(key) val enterKey = key(key)
val context = dataContext?.vim ?: injector.executionContextManager.getEditorExecutionContext(editor.vim) val context = injector.executionContextManager.onEditor(editor.vim, dataContext?.vim)
val keyHandler = KeyHandler.getInstance() KeyHandler.getInstance().handleKey(editor.vim, enterKey, context)
keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState)
} }
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean { override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
@@ -361,4 +362,4 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
} }
internal val enableOctopus: Boolean internal val enableOctopus: Boolean
get() = injector.application.isOctopusEnabled() get() = injector.globalOptions().octopushandler

View File

@@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.helper package com.maddyhome.idea.vim.helper
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretVisualAttributes import com.intellij.openapi.editor.CaretVisualAttributes
@@ -19,17 +18,14 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.IsReplaceCharListener
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
import com.maddyhome.idea.vim.options.helpers.GuiCursorMode import com.maddyhome.idea.vim.options.helpers.GuiCursorMode
import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper
import com.maddyhome.idea.vim.options.helpers.GuiCursorType import com.maddyhome.idea.vim.options.helpers.GuiCursorType
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inBlockSelection import com.maddyhome.idea.vim.state.mode.inBlockSelection
import com.maddyhome.idea.vim.state.mode.mode
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import java.awt.Color import java.awt.Color
@@ -89,12 +85,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this) caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
// NOTE: At the moment, this causes project leak in tests
// IJPL-928 - this will be fixed in 2024.2
// [VERSION UPDATE] 2024.2 - remove if wrapping
if (!ApplicationManager.getApplication().isUnitTestMode) {
(this as? EditorEx)?.setCaretVisible(true) (this as? EditorEx)?.setCaretVisible(true)
}
} }
private fun Editor.updateSecondaryCaretsVisualAttributes() { private fun Editor.updateSecondaryCaretsVisualAttributes() {
@@ -143,31 +134,3 @@ private object AttributesCache {
@TestOnly @TestOnly
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode() internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
override fun isReplaceCharChanged(editor: VimEditor) {
updateCaretsVisual(editor)
}
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
updateCaretsVisual(editor)
}
private fun updateCaretsVisual(editor: VimEditor) {
if (injector.globalOptions().ideaglobalmode) {
updateAllEditorsCaretsVisual()
} else {
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
}
}
public fun updateAllEditorsCaretsVisual() {
injector.editorGroup.getEditors().forEach { editor ->
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
}
}
}

View File

@@ -11,8 +11,8 @@ package com.maddyhome.idea.vim.helper
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.maddyhome.idea.vim.action.change.Extension import com.maddyhome.idea.vim.action.change.Extension
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
@@ -23,7 +23,7 @@ import javax.swing.KeyStroke
@Service @Service
internal class CommandLineHelper : VimCommandLineHelper { internal class CommandLineHelper : VimCommandLineHelper {
override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? { override fun inputString(vimEditor: VimEditor, prompt: String, finishOn: Char?): String? {
val editor = vimEditor.ij val editor = vimEditor.ij
if (vimEditor.vimStateMachine.isDotRepeatInProgress) { if (vimEditor.vimStateMachine.isDotRepeatInProgress) {
val input = Extension.consumeString() val input = Extension.consumeString()
@@ -53,7 +53,7 @@ internal class CommandLineHelper : VimCommandLineHelper {
var text: String? = null var text: String? = null
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input() // XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts() val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts()
exEntryPanel.activate(editor, context.ij, prompt.ifEmpty { " " }, "", 1) exEntryPanel.activate(editor, injector.executionContextManager.onEditor(editor.vim).ij, prompt.ifEmpty { " " }, "", 1)
ModalEntry.activate(editor.vim) { key: KeyStroke -> ModalEntry.activate(editor.vim) { key: KeyStroke ->
return@activate when { return@activate when {
key.isCloseKeyStroke() -> { key.isCloseKeyStroke() -> {

View File

@@ -20,6 +20,7 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
import com.maddyhome.idea.vim.options.OptionConstants import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inVisualMode import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.mode
internal val Mode.hasVisualSelection internal val Mode.hasVisualSelection
get() = when (this) { get() = when (this) {

View File

@@ -0,0 +1,45 @@
/*
* 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.helper
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.util.Key
import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.group.SearchGroup
import com.maddyhome.idea.vim.group.VimMarkServiceImpl
internal object DocumentManager {
private val docListeners = mutableSetOf<DocumentListener>()
private val LISTENER_MARKER = Key<String>("VimlistenerMarker")
init {
docListeners += VimMarkServiceImpl.MarkUpdater
docListeners += SearchGroup.DocumentSearchListener.INSTANCE
}
fun addListeners(doc: Document) {
val marker = doc.getUserData(LISTENER_MARKER)
if (marker != null) return
doc.putUserData(LISTENER_MARKER, "foo")
for (docListener in docListeners) {
EventFacade.getInstance().addDocumentListener(doc, docListener)
}
}
fun removeListeners(doc: Document) {
doc.getUserData(LISTENER_MARKER) ?: return
doc.putUserData(LISTENER_MARKER, null)
for (docListener in docListeners) {
EventFacade.getInstance().removeDocumentListener(doc, docListener)
}
}
}

View File

@@ -14,7 +14,6 @@ import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
@Deprecated("Do not use context wrappers, use existing provided contexts. If no context available, use `injector.getExecutionContextManager().getEditorExecutionContext(editor)`")
internal class EditorDataContext @Deprecated("Please use `init` method") constructor( internal class EditorDataContext @Deprecated("Please use `init` method") constructor(
private val editor: Editor, private val editor: Editor,
private val editorContext: DataContext, private val editorContext: DataContext,

View File

@@ -17,7 +17,6 @@ import com.intellij.testFramework.LightVirtualFile;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt; import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor; import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.common.IndentConfig; import com.maddyhome.idea.vim.common.IndentConfig;
import com.maddyhome.idea.vim.newapi.IjVimDocument;
import com.maddyhome.idea.vim.newapi.IjVimEditor; import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel; import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import kotlin.Pair; import kotlin.Pair;
@@ -30,7 +29,6 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static java.lang.Integer.max; import static java.lang.Integer.max;
import static java.lang.Integer.min; import static java.lang.Integer.min;
@@ -199,7 +197,7 @@ public class EditorHelper {
* @param file The virtual file get the editor for * @param file The virtual file get the editor for
* @return The matching editor or null if no match was found * @return The matching editor or null if no match was found
*/ */
public static @Nullable VimEditor getEditor(final @Nullable VirtualFile file) { public static @Nullable Editor getEditor(final @Nullable VirtualFile file) {
if (file == null) { if (file == null) {
return null; return null;
} }
@@ -208,15 +206,23 @@ public class EditorHelper {
if (doc == null) { if (doc == null) {
return null; return null;
} }
return injector.getEditorGroup().getEditors(new IjVimDocument(doc)).stream().findFirst().orElse(null); final List<Editor> editors = HelperKt.localEditors(doc);
if (editors.size() > 0) {
return editors.get(0);
} }
public static @NotNull String pad(final @NotNull Editor editor, int line, final int to) { return null;
}
public static @NotNull String pad(final @NotNull Editor editor,
@NotNull DataContext context,
int line,
final int to) {
final int len = EngineEditorHelperKt.lineLength(new IjVimEditor(editor), line); final int len = EngineEditorHelperKt.lineLength(new IjVimEditor(editor), line);
if (len >= to) return ""; if (len >= to) return "";
final int limit = to - len; final int limit = to - len;
return IndentConfig.create(editor).createIndentBySize(limit); return IndentConfig.create(editor, context).createIndentBySize(limit);
} }
/** /**
@@ -323,7 +329,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight); final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@NotNull final VimEditor editor1 = new IjVimEditor(editor); @NotNull final VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount(); final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine); final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen. // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.

View File

@@ -12,17 +12,13 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeWithMe.ClientId import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.util.ui.table.JBTableRowEditor import com.intellij.util.ui.table.JBTableRowEditor
import com.maddyhome.idea.vim.api.StringListOptionValue
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import java.awt.Component import java.awt.Component
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JTable import javax.swing.JTable
@@ -38,27 +34,25 @@ public val Editor.fileSize: Int
internal val Editor.isIdeaVimDisabledHere: Boolean internal val Editor.isIdeaVimDisabledHere: Boolean
get() { get() {
val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport
return (ideaVimDisabledInDialog(ideaVimSupportValue) && isInDialog()) || return disabledInDialog ||
!ClientId.isCurrentlyUnderLocalId || // CWM-927 (!ClientId.isCurrentlyUnderLocalId) || // CWM-927
(ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine()) (!ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline) && isDatabaseCell()) ||
(!ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline) && isOneLineMode)
} }
private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean { private fun Editor.isDatabaseCell(): Boolean {
return !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialog) return isTableCellEditor(this.component)
&& !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialoglegacy)
} }
private fun ideaVimDisabledForSingleLine(ideaVimSupportValue: StringListOptionValue): Boolean { private val Editor.disabledInDialog: Boolean
return !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline) get() {
} val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport
return (
private fun Editor.isInDialog(): Boolean { !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialog) &&
return !this.isPrimaryEditor() && !EditorHelper.isFileEditor(this) !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialoglegacy)
} ) &&
(!this.isPrimaryEditor() && !EditorHelper.isFileEditor(this))
private fun Editor.isSingleLine(): Boolean { }
return isTableCellEditor(this.component) || isOneLineMode
}
/** /**
* Checks if the editor is a primary editor in the main editing area. * Checks if the editor is a primary editor in the main editing area.
@@ -99,41 +93,3 @@ internal val Caret.vimLine: Int
*/ */
internal val Editor.vimLine: Int internal val Editor.vimLine: Int
get() = this.caretModel.currentCaret.vimLine get() = this.caretModel.currentCaret.vimLine
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
val caretModel = this.caretModel
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
if (carets == null || carets.size == 1) {
action()
}
else {
var initialDocumentSize = this.document.textLength
var documentSizeDifference = 0
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
val restoredCarets = mutableListOf<CaretState>()
caretModel.removeSecondaryCarets()
for ((selectionStart, selectionEnd) in caretOffsets) {
if (selectionStart == selectionEnd) {
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
}
else {
caretModel.primaryCaret.setSelection(
selectionStart + documentSizeDifference,
selectionEnd + documentSizeDifference
)
}
action()
restoredCarets.add(caretModel.caretsAndSelections.single())
val documentLength = this.document.textLength
documentSizeDifference += documentLength - initialDocumentSize
initialDocumentSize = documentLength
}
caretModel.caretsAndSelections = restoredCarets
}
}

View File

@@ -9,12 +9,19 @@
package com.maddyhome.idea.vim.helper package com.maddyhome.idea.vim.helper
import com.intellij.codeInsight.template.TemplateManager import com.intellij.codeInsight.template.TemplateManager
import com.intellij.codeWithMe.ClientId
import com.intellij.injected.editor.EditorWindow import com.intellij.injected.editor.EditorWindow
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.ClientEditorManager
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection import com.maddyhome.idea.vim.state.mode.inBlockSelection
import java.util.stream.Collectors
internal fun <T : Comparable<T>> sort(a: T, b: T) = if (a > b) b to a else a to b internal fun <T : Comparable<T>> sort(a: T, b: T) = if (a > b) b to a else a to b
@@ -29,6 +36,34 @@ internal inline fun Editor.vimForEachCaret(action: (caret: Caret) -> Unit) {
internal fun Editor.getTopLevelEditor() = if (this is EditorWindow) this.delegate else this internal fun Editor.getTopLevelEditor() = if (this is EditorWindow) this.delegate else this
/**
* Return list of editors for local host (for code with me plugin)
*/
public fun localEditors(): List<Editor> {
return ClientEditorManager.getCurrentInstance().editors().collect(Collectors.toList())
}
public fun localEditors(doc: Document): List<Editor> {
return EditorFactory.getInstance().getEditors(doc)
.filter { editor -> editor.editorClientId.let { it == null || it == ClientId.currentOrNull } }
}
public fun localEditors(doc: Document, project: Project): List<Editor> {
return EditorFactory.getInstance().getEditors(doc, project)
.filter { editor -> editor.editorClientId.let { it == null || it == ClientId.currentOrNull } }
}
private val Editor.editorClientId: ClientId?
get() {
if (editorClientKey == null) {
@Suppress("DEPRECATION")
editorClientKey = Key.findKeyByName("editorClientIdby userData()") ?: return null
}
return editorClientKey?.let { this.getUserData(it) as? ClientId }
}
private var editorClientKey: Key<*>? = null
@Suppress("IncorrectParentDisposable") @Suppress("IncorrectParentDisposable")
internal fun Editor.isTemplateActive(): Boolean { internal fun Editor.isTemplateActive(): Boolean {
val project = this.project ?: return false val project = this.project ?: return false

View File

@@ -21,7 +21,6 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionManagerEx import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
@@ -35,6 +34,7 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction import com.maddyhome.idea.vim.api.NativeAction
import com.maddyhome.idea.vim.api.VimActionExecutor import com.maddyhome.idea.vim.api.VimActionExecutor
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
import com.maddyhome.idea.vim.newapi.IjNativeAction import com.maddyhome.idea.vim.newapi.IjNativeAction
@@ -78,7 +78,6 @@ internal class IjActionExecutor : VimActionExecutor {
val dataContext = DataContextWrapper(context.ij) val dataContext = DataContextWrapper(context.ij)
dataContext.putUserData(runFromVimKey, true) dataContext.putUserData(runFromVimKey, true)
val actionId = ActionManager.getInstance().getId(ijAction)
val event = AnActionEvent( val event = AnActionEvent(
null, null,
dataContext, dataContext,
@@ -87,20 +86,11 @@ internal class IjActionExecutor : VimActionExecutor {
ActionManager.getInstance(), ActionManager.getInstance(),
0, 0,
) )
Utils.initUpdateSession(event)
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems. // beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
// because rider use async update method. See VIM-1819. // because rider use async update method. See VIM-1819.
// This method executes inside of lastUpdateAndCheckDumb // This method executes inside of lastUpdateAndCheckDumb
// Another related issue: VIM-2604 // Another related issue: VIM-2604
// This is a hack to fix the tests and fix VIM-3332
// We should get rid of it in VIM-3376
if (actionId == "RunClass" || actionId == IdeActions.ACTION_COMMENT_LINE || actionId == IdeActions.ACTION_COMMENT_BLOCK) {
ijAction.beforeActionPerformedUpdate(event)
if (!event.presentation.isEnabled) return false
} else {
if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false
}
if (ijAction is ActionGroup && !event.presentation.isPerformGroup) { if (ijAction is ActionGroup && !event.presentation.isPerformGroup) {
// Some ActionGroups should not be performed, but shown as a popup // Some ActionGroups should not be performed, but shown as a popup
val popup = JBPopupFactory.getInstance() val popup = JBPopupFactory.getInstance()
@@ -224,7 +214,7 @@ internal class IjActionExecutor : VimActionExecutor {
CommandProcessor.getInstance() CommandProcessor.getInstance()
.executeCommand( .executeCommand(
editor.ij.project, editor.ij.project,
{ cmd.execute(editor, context, operatorArguments) }, { cmd.execute(editor, injector.executionContextManager.onEditor(editor, context), operatorArguments) },
cmd.id, cmd.id,
DocCommandGroupId.noneGroupId(editor.ij.document), DocCommandGroupId.noneGroupId(editor.ij.document),
UndoConfirmationPolicy.DEFAULT, UndoConfirmationPolicy.DEFAULT,

View File

@@ -14,6 +14,7 @@ import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.actionSystem.EditorActionManager import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.maddyhome.idea.vim.api.EngineEditorHelper import com.maddyhome.idea.vim.api.EngineEditorHelper
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
@@ -50,8 +51,8 @@ internal class IjEditorHelper : EngineEditorHelper {
return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij) return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij)
} }
override fun pad(editor: VimEditor, line: Int, to: Int): String { override fun pad(editor: VimEditor, context: ExecutionContext, line: Int, to: Int): String {
return EditorHelper.pad(editor.ij, line, to) return EditorHelper.pad(editor.ij, context.ij, line, to)
} }
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition { override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {

View File

@@ -34,15 +34,15 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
val returnTo = this.vim.vimStateMachine.mode.returnTo val returnTo = this.vim.vimStateMachine.mode.returnTo
when (returnTo) { when (returnTo) {
ReturnTo.INSERT -> { ReturnTo.INSERT -> {
this.vim.mode = Mode.INSERT this.vim.vimStateMachine.mode = Mode.INSERT
} }
ReturnTo.REPLACE -> { ReturnTo.REPLACE -> {
this.vim.mode = Mode.REPLACE this.vim.vimStateMachine.mode = Mode.REPLACE
} }
null -> { null -> {
this.vim.mode = Mode.NORMAL() this.vim.vimStateMachine.mode = Mode.NORMAL()
} }
} }
SelectionVimListenerSuppressor.lock().use { SelectionVimListenerSuppressor.lock().use {
@@ -67,15 +67,15 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
val returnTo = this.vimStateMachine.mode.returnTo val returnTo = this.vimStateMachine.mode.returnTo
when (returnTo) { when (returnTo) {
ReturnTo.INSERT -> { ReturnTo.INSERT -> {
this.mode = Mode.INSERT this.vimStateMachine.mode = Mode.INSERT
} }
ReturnTo.REPLACE -> { ReturnTo.REPLACE -> {
this.mode = Mode.REPLACE this.vimStateMachine.mode = Mode.REPLACE
} }
null -> { null -> {
this.mode = Mode.NORMAL() this.vimStateMachine.mode = Mode.NORMAL()
} }
} }
SelectionVimListenerSuppressor.lock().use { SelectionVimListenerSuppressor.lock().use {

View File

@@ -14,6 +14,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeVisualColumn import com.maddyhome.idea.vim.api.normalizeVisualColumn
import com.maddyhome.idea.vim.api.options import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
@@ -28,7 +29,6 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -56,7 +56,7 @@ internal object ScrollViewHelper {
// that this needs to be replaced as a more or less dumb line for line rewrite. // that this needs to be replaced as a more or less dumb line for line rewrite.
val topLine = getVisualLineAtTopOfScreen(editor) val topLine = getVisualLineAtTopOfScreen(editor)
val bottomLine = getVisualLineAtBottomOfScreen(editor) val bottomLine = getVisualLineAtBottomOfScreen(editor)
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount val lastLine = vimEditor.getVisualLineCount() - 1
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
val scrollOffset = injector.options(vimEditor).scrolloff val scrollOffset = injector.options(vimEditor).scrolloff

View File

@@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.helper; package com.maddyhome.idea.vim.helper;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.lang.CodeDocumentationAwareCommenter; import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.Commenter; import com.intellij.lang.Commenter;
import com.intellij.lang.Language; import com.intellij.lang.Language;
@@ -17,28 +16,22 @@ import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiComment; import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt; import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor; import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.common.CharacterPosition; import com.maddyhome.idea.vim.common.CharacterPosition;
import com.maddyhome.idea.vim.common.Direction; import com.maddyhome.idea.vim.common.Direction;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.newapi.IjVimCaret; import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor; import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import it.unimi.dsi.fastutil.ints.IntComparator;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import kotlin.Pair; import kotlin.Pair;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -632,6 +625,113 @@ public class SearchHelper {
return new TextRange(bstart, bend + 1); return new TextRange(bstart, bend + 1);
} }
private static int findMatchingBlockCommentPair(@NotNull PsiComment comment,
int pos,
@Nullable String prefix,
@Nullable String suffix) {
if (prefix != null && suffix != null) {
// TODO: Try to get rid of `getText()` because it takes a lot of time to calculate the string
final String commentText = comment.getText();
if (commentText.startsWith(prefix) && commentText.endsWith(suffix)) {
final int endOffset = comment.getTextOffset() + comment.getTextLength();
if (pos < comment.getTextOffset() + prefix.length()) {
return endOffset;
}
else if (pos >= endOffset - suffix.length()) {
return comment.getTextOffset();
}
}
}
return -1;
}
private static int findMatchingBlockCommentPair(@NotNull PsiElement element, int pos) {
final Language language = element.getLanguage();
final Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(language);
final PsiComment comment = PsiTreeUtil.getParentOfType(element, PsiComment.class, false);
if (comment != null) {
final int ret = findMatchingBlockCommentPair(comment, pos, commenter.getBlockCommentPrefix(),
commenter.getBlockCommentSuffix());
if (ret >= 0) {
return ret;
}
if (commenter instanceof CodeDocumentationAwareCommenter docCommenter) {
return findMatchingBlockCommentPair(comment, pos, docCommenter.getDocumentationCommentPrefix(),
docCommenter.getDocumentationCommentSuffix());
}
}
return -1;
}
/**
* This looks on the current line, starting at the cursor position for one of {, }, (, ), [, or ]. It then searches
* forward or backward, as appropriate for the associated match pair. String in double quotes are skipped over.
* Single characters in single quotes are skipped too.
*
* @param editor The editor to search in
* @return The offset within the editor of the found character or -1 if no match was found or none of the characters
* were found on the remainder of the current line.
*/
public static int findMatchingPairOnCurrentLine(@NotNull Editor editor, @NotNull Caret caret) {
int pos = caret.getOffset();
final int commentPos = findMatchingComment(editor, pos);
if (commentPos >= 0) {
return commentPos;
}
int line = caret.getLogicalPosition().line;
final IjVimEditor vimEditor = new IjVimEditor(editor);
int end = EngineEditorHelperKt.getLineEndOffset(vimEditor, line, true);
// To handle the case where visual mode allows the user to go past the end of the line,
// which will prevent loc from finding a pairable character below
if (pos > 0 && pos == end) {
pos = end - 1;
}
final String pairChars = parseMatchPairsOption(vimEditor);
CharSequence chars = editor.getDocument().getCharsSequence();
int loc = -1;
// Search the remainder of the current line for one of the candidate characters
while (pos < end) {
loc = pairChars.indexOf(chars.charAt(pos));
if (loc >= 0) {
break;
}
pos++;
}
int res = -1;
// If we found one ...
if (loc >= 0) {
// What direction should we go now (-1 is backward, 1 is forward)
Direction dir = loc % 2 == 0 ? Direction.FORWARDS : Direction.BACKWARDS;
// Which character did we find and which should we now search for
char found = pairChars.charAt(loc);
char match = pairChars.charAt(loc + dir.toInt());
res = findBlockLocation(chars, found, match, dir, pos, 1, true);
}
return res;
}
/**
* If on the start/end of a block comment, jump to the matching of that comment, or vice versa.
*/
private static int findMatchingComment(@NotNull Editor editor, int pos) {
final PsiFile psiFile = PsiHelper.getFile(editor);
if (psiFile != null) {
final PsiElement element = psiFile.findElementAt(pos);
if (element != null) {
return findMatchingBlockCommentPair(element, pos);
}
}
return -1;
}
private static int findBlockLocation(@NotNull CharSequence chars, private static int findBlockLocation(@NotNull CharSequence chars,
char found, char found,
char match, char match,
@@ -1473,48 +1573,6 @@ public class SearchHelper {
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count); return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
} }
public static int findMisspelledWords(@NotNull Editor editor,
int startOffset,
int endOffset,
int skipCount,
IntComparator offsetOrdering) {
Project project = editor.getProject();
if (project == null) {
return -1;
}
IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
startOffset, endOffset, highlight -> {
if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
int offset = highlight.getStartOffset();
if (offset >= startOffset && offset <= endOffset) {
offsets.add(offset);
}
}
return true;
});
if (offsets.isEmpty()) {
return -1;
}
if (skipCount >= offsets.size()) {
return offsets.lastInt();
}
else {
IntIterator offsetIterator = offsets.iterator();
skip(offsetIterator, skipCount);
return offsetIterator.nextInt();
}
}
private static void skip(IntIterator iterator, final int n) {
if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
int i = n;
while (i-- != 0 && iterator.hasNext()) iterator.nextInt();
}
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) { private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
List<String> pairs = options(injector, vimEditor).getMatchpairs(); List<String> pairs = options(injector, vimEditor).getMatchpairs();
StringBuilder res = new StringBuilder(); StringBuilder res = new StringBuilder();

View File

@@ -26,7 +26,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.options import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.LineRange import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.newapi.IjVimDocument
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.annotations.Contract import org.jetbrains.annotations.Contract
@@ -87,24 +86,11 @@ private fun updateSearchHighlights(
): Int { ): Int {
var currentMatchOffset = -1 var currentMatchOffset = -1
val projectManager = ProjectManager.getInstanceIfCreated() ?: return currentMatchOffset val projectManager = ProjectManager.getInstanceIfCreated() ?: return currentMatchOffset
// TODO: This implementation needs rethinking
// It's a bit weird that we update search highlights across all open projects. It would make more sense to treat top
// level project frame windows as separate applications, but we can't do this because IdeaVim does not maintain state
// per-project.
// So, to be clear, this will loop over each project, and therefore, for each project top-level frame, will update
// search highlights in all editors for the document of the currently selected editor. It does not update highlights
// for editors for the document that are in other projects.
for (project in projectManager.openProjects) { for (project in projectManager.openProjects) {
val current = FileEditorManager.getInstance(project).selectedTextEditor ?: continue val current = FileEditorManager.getInstance(project).selectedTextEditor ?: continue
val editors = injector.editorGroup.getEditors(IjVimDocument(current.document)) // [VERSION UPDATE] 202+ Use editors
for (vimEditor in editors) { val editors = localEditors(current.document, project)
val editor = (vimEditor as IjVimEditor).editor for (editor in editors) {
if (editor.project != project) {
continue
}
// Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed. // Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
// Force update for the situations where the text is the same, but the ignore case values have changed. // Force update for the situations where the text is the same, but the ignore case values have changed.
// E.g. Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern, // E.g. Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,

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