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

Compare commits

..

21 Commits

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

View File

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

View File

@@ -9,13 +9,20 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Apply Patch
run: |
git apply tests/ui-ij-tests/src/test/kotlin/ui/octopus.patch
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
- name: Setup FFmpeg
run: brew install ffmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
with:
# Not strictly necessary, but it may prevent rate limit
# errors especially on GitHub-hosted macos machines.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin
@@ -23,7 +30,7 @@ jobs:
- name: Run Idea
run: |
mkdir -p build/reports
gradle runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
gradle runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@@ -45,7 +52,6 @@ jobs:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-ij-tests/build/reports
sandbox-idea-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest

View File

@@ -18,7 +18,11 @@ jobs:
with:
python-version: '3.10'
- name: Setup FFmpeg
run: brew install ffmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
with:
# Not strictly necessary, but it may prevent rate limit
# errors especially on GitHub-hosted macos machines.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin
@@ -48,5 +52,4 @@ jobs:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-py-tests/build/reports
sandbox-idea-log

View File

@@ -15,7 +15,11 @@ jobs:
distribution: zulu
java-version: 17
- name: Setup FFmpeg
run: brew install ffmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
with:
# Not strictly necessary, but it may prevent rate limit
# errors especially on GitHub-hosted macos machines.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin
@@ -45,7 +49,6 @@ jobs:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-ij-tests/build/reports
sandbox-idea-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest

View File

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

View File

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

2
.idea/vcs.xml generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -495,22 +495,6 @@ Contributors:
[![icon][github]](https://github.com/emanuelgestosa)
&nbsp;
Emanuel Gestosa
* [![icon][mail]](mailto:81118900+lippfi@users.noreply.github.com)
[![icon][github]](https://github.com/lippfi)
&nbsp;
lippfi,
* [![icon][mail]](mailto:fillipser143@gmail.com)
[![icon][github]](https://github.com/Parker7123)
&nbsp;
FilipParker
* [![icon][mail]](mailto:7138209+duhaesbaert@users.noreply.github.com)
[![icon][github]](https://github.com/duhaesbaert)
&nbsp;
Eduardo Haesbaert
* [![icon][mail]](mailto:nikolaevsky.egor@gmail.com)
[![icon][github]](https://github.com/Aisper)
&nbsp;
Egor Nikolaevsky
Previous contributors:

View File

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

View File

@@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.24-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") {
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

@@ -32,6 +32,7 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.exceptions.MissingVersionException
import org.kohsuke.github.GHUser
import java.net.HttpURLConnection
import java.net.URL
@@ -48,14 +49,14 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.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("io.ktor:ktor-client-core:2.3.11")
classpath("io.ktor:ktor-client-cio:2.3.10")
classpath("io.ktor:ktor-client-auth:2.3.11")
classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
classpath("io.ktor:ktor-client-core:2.3.7")
classpath("io.ktor:ktor-client-cio:2.3.7")
classpath("io.ktor:ktor-client-auth:2.3.7")
classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@@ -69,11 +70,11 @@ plugins {
application
id("java-test-fixtures")
id("org.jetbrains.intellij") version "1.17.3"
id("org.jetbrains.intellij") version "1.17.1"
id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("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"
}
@@ -141,14 +142,14 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
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 {
@@ -206,11 +207,6 @@ tasks {
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)
}
}
@@ -264,6 +260,7 @@ tasks {
runPluginVerifier {
downloadDir.set("${project.buildDir}/pluginVerifier/ides")
teamCityOutputFormat.set(true)
// ideVersions.set(listOf("IC-2021.3.4"))
}
generateGrammarSource {
@@ -308,14 +305,26 @@ tasks {
from(createOpenApiSourceJar) { into("lib/src") }
}
patchPluginXml {
val pluginVersion = version
// Don't forget to update plugin.xml
sinceBuild.set("241.15989.150")
changeNotes.set(
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
)
}
patchPluginXml {
// Get the latest available change notes from the changelog file
changeNotes.set(
provider {
with(changelog) {
val log = try {
getUnreleased()
} catch (e: MissingVersionException) {
getOrNull(pluginVersion.toString()) ?: getLatest()
}
renderItem(
log,
org.jetbrains.changelog.Changelog.OutputType.HTML,
)
}
},
)
}
}
// --- Tests
@@ -420,14 +429,12 @@ val prId: String by project
tasks.register("updateMergedPr") {
doLast {
val x = changelog.getUnreleased()
println("x")
// if (project.hasProperty("prId")) {
// println("Got pr id: $prId")
// updateMergedPr(prId.toInt())
// } else {
// error("Cannot get prId")
// }
if (project.hasProperty("prId")) {
println("Got pr id: $prId")
updateMergedPr(prId.toInt())
} else {
error("Cannot get prId")
}
}
}

View File

@@ -27,7 +27,7 @@ Plug 'nerdtree'
### Preview
<details>
<summary>Click for the preview</summary>
<summary>Click to the the preview</summary>
<img src="images/nerdtree.gif" alt="NERDTree example"/>
</details>

View File

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

View File

@@ -8,13 +8,12 @@
# suppress inspection "UnusedProperty" for whole file
#ideaVersion=LATEST-EAP-SNAPSHOT
ideaVersion=2024.1.1
ideaVersion=2023.3.3
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true
version=chylex-35
version=chylex-28
javaVersion=17
remoteRobotVersion=0.11.22
antlrVersion=4.10.1
@@ -29,7 +28,7 @@ publishChannels=eap
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
# we exclude this version from the dependency and use our own version of kotlin that is specified above
kotlinxSerializationVersion=1.6.2
kotlinxSerializationVersion=1.5.1
slackUrl=
youtrackToken=

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package scripts
import io.ktor.client.call.*
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
val areaWeights = setOf(
Triple("118-53212", "Plugins", 50),
Triple("118-53220", "Vim Script", 30),
Triple("118-54084", "Esc", 100),
)
suspend fun updateRates() {
println("Updating rates of the issues")
areaWeights.forEach { (id, name, weight) ->
val unmappedIssues = unmappedIssues(name)
println("Got ${unmappedIssues.size} for $name area")
unmappedIssues.forEach { issueId ->
print("Trying to update issue $issueId: ")
val response = updateCustomField(issueId) {
put("name", "Affected Rate")
put("\$type", "SimpleIssueCustomField")
put("value", weight)
}
println(response)
}
}
}
private suspend fun unmappedIssues(area: String): List<String> {
val areaProcessed = if (" " in area) "{$area}" else area
val res = issuesQuery(
query = "project: VIM Affected Rate: {No affected rate} Area: $areaProcessed #Unresolved",
fields = "id,idReadable"
)
return res.body<JsonArray>().map { it.jsonObject }.map { it["idReadable"]!!.jsonPrimitive.content }
}
suspend fun getAreasWithoutWeight(): Set<Pair<String, String>> {
val allAreas = getAreaValues()
return allAreas
.filterNot { it.key in areaWeights.map { it.first }.toSet() }
.entries
.map { it.key to it.value }
.toSet()
}
suspend fun main() {
updateRates()
}

View File

@@ -101,14 +101,10 @@ command:
(WS | COLON)* range? (WS | COLON)* EXECUTE WS* (expr WS*)* (NEW_LINE | BAR)+
#ExecuteCommand|
(WS | COLON)* range? (WS | COLON)* lShift
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
(NEW_LINE | BAR)*
(WS | COLON)* range? (WS | COLON)* lShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
#ShiftLeftCommand|
(WS | COLON)* range? (WS | COLON)* rShift
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
(NEW_LINE | BAR)*
(WS | COLON)* range? (WS | COLON)* rShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
#ShiftRightCommand|
(WS | COLON)* range? (WS | COLON)*
@@ -828,4 +824,4 @@ AUGROUP_SKIP: NEW_LINE (WS|COLON)* AUGROUP .*? AUGROUP WS+ END -> skip
// All the other symbols
UNICODE_CHAR: '\u0000'..'\uFFFE';
EMOJI: [\p{Emoji}];
EMOJI: [\p{Emoji}];

View File

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

View File

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

View File

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

View File

@@ -211,22 +211,22 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
public static void setEnabled(final boolean enabled) {
if (isEnabled() == enabled) return;
if (!enabled) {
getInstance().turnOffPlugin(true);
}
getInstance().enabled = enabled;
if (enabled) {
getInstance().turnOnPlugin();
}
if (enabled) {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
} else {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
}
if (!enabled) {
getInstance().turnOffPlugin(true);
}
if (enabled) {
getInstance().turnOnPlugin();
}
StatusBarIconFactory.Util.INSTANCE.updateIcon();
}
@@ -282,13 +282,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
ideavimrcRegistered = true;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
try {
VimInjectorKt.injector.getOptionGroup().startInitVimRc();
executeIdeaVimRc(editor);
}
finally {
VimInjectorKt.injector.getOptionGroup().endInitVimRc();
}
executeIdeaVimRc(editor);
}
}
@@ -359,7 +353,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (onOffDisposable != null) {
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.maddyhome.idea.vim.group.EditorHolderService
@Service(Service.Level.PROJECT)
@Service
internal class VimProjectService(val project: Project) : Disposable {
override fun dispose() {
// Not sure if this is a best solution

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 keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
val startTime = if (traceTime) System.currentTimeMillis() else null
handler.handleKey(editor.vim, keyStroke, context.vim, handler.keyHandlerState)
handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim))
if (startTime != null) {
val duration = System.currentTimeMillis() - startTime
LOG.info("VimTypedAction '$charTyped': $duration ms")

View File

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

View File

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

View File

@@ -26,7 +26,6 @@ import com.maddyhome.idea.vim.KeyHandler
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.group.EditorHolderService
import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.handler.enableOctopus
@@ -45,7 +44,7 @@ import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ex.ExTextField
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
@@ -80,8 +79,11 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
// Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler?
try {
val start = if (traceTime) System.currentTimeMillis() else null
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState)
KeyHandler.getInstance().handleKey(
editor.vim,
keyStroke,
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
)
if (start != null) {
val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut update '$keyStroke': $duration ms")
@@ -254,14 +256,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
return null
}
private fun getEditor(e: AnActionEvent): Editor? {
return e.getData(PlatformDataKeys.EDITOR)
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
EditorHolderService.getInstance().editor
} else {
null
}
}
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
/**
* Every time the key pressed with an active lookup, there is a decision:
@@ -385,10 +380,6 @@ private class ActionEnableStatus(
}
}
override fun toString(): String {
return "ActionEnableStatus(isEnabled=$isEnabled, message='$message', logLevel=$logLevel)"
}
companion object {
private val LOG = logger<ActionEnableStatus>()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,46 +13,24 @@ import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
/**
* @author vlan
*/
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
private var isActiveInTestMode = false
override val isActive: Boolean
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.isPanelActive(myEditor)
} else {
isActiveInTestMode
}
override var text: String? = null
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).text
} else {
field
}
set(value) {
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(value ?: "")
} else {
field = value
isActiveInTestMode = !value.isNullOrEmpty()
}
}
private set
override fun output(text: String) {
this.text = text
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(text)
}
}
override fun clear() {
text = null
}
override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).close()
}
else {
isActiveInTestMode = false
ExOutputPanel.getInstance(myEditor).deactivate(false)
}
}

View File

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

View File

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

View File

@@ -22,27 +22,26 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.toTextRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.addCommand
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
@@ -50,19 +49,17 @@ import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.*
internal class CommentaryExtension : VimExtension {
object Util {
companion object {
fun doCommentary(
editor: VimEditor,
context: ExecutionContext,
range: TextRange,
selectionType: SelectionType,
resetCaret: Boolean = true,
resetCaret: Boolean,
): Boolean {
val mode = editor.vimStateMachine.mode
if (mode !is Mode.VISUAL) {
@@ -70,7 +67,8 @@ internal class CommentaryExtension : VimExtension {
}
return runWriteAction {
// Treat block- and character-wise selections as block comments. Fall back if the first action isn't available
// Treat block- and character-wise selections as block comments. Be ready to fall back to if the first action
// isn't available
val actions = if (selectionType === SelectionType.LINE_WISE) {
listOf(IdeActions.ACTION_COMMENT_LINE, IdeActions.ACTION_COMMENT_BLOCK)
} else {
@@ -115,17 +113,12 @@ internal class CommentaryExtension : VimExtension {
// first non-whitespace character, then the caret is in the right place. If it's inserted at the first column,
// then the caret is now in a bit of a weird place. We can't detect this scenario, so we just have to accept
// the difference
// TODO: If we don't move the caret to the start offset, we should maintain the current logical position
if (resetCaret) {
editor.primaryCaret().moveToOffset(range.startOffset)
}
}
}
companion object {
private const val OPERATOR_FUNC = "CommentaryOperatorFunc"
}
override fun getName() = "commentary"
override fun init() {
@@ -152,16 +145,6 @@ internal class CommentaryExtension : VimExtension {
putKeyMapping(MappingMode.N, injector.parser.parseKeys("<Plug>(CommentLine)"), owner, plugCommentaryLineKeys, true)
addCommand("Commentary", CommentaryCommandAliasHandler())
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, CommentaryOperatorFunction())
}
private class CommentaryOperatorFunction : OperatorFunction {
// todo make it multicaret
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
return Util.doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
}
}
/**
@@ -170,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
* invoke the operator. This object is both the mapping handler and the operator function.
*/
private class CommentaryOperatorHandler : ExtensionHandler {
private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC
setOperatorFunction(this)
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
// todo make it multicaret
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
return doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
}
}
private class CommentaryMappingHandler : ExtensionHandler {
@@ -249,14 +238,8 @@ internal class CommentaryExtension : VimExtension {
* Used like `:1,3Commentary` or `g/fun/Commentary`
*/
private class CommentaryCommandAliasHandler : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
Util.doCommentary(
editor,
context,
range.getLineRange(editor, editor.primaryCaret()).toTextRange(editor),
SelectionType.LINE_WISE,
false
)
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false)
}
}
}

View File

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

View File

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

View File

@@ -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
// 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) {
val matchitAction = MatchitAction()

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,6 @@
*/
package com.maddyhome.idea.vim.extension.surround
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
@@ -18,7 +17,6 @@ import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode
@@ -26,16 +24,14 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegisterForCaret
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
@@ -46,6 +42,7 @@ import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent
@@ -81,15 +78,13 @@ internal class VimSurroundExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("ds"), owner, injector.parser.parseKeys("<Plug>DSurround"), true)
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
}
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
}
private class YSurroundHandler : ExtensionHandler {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
injector.globalOptions().operatorfunc = OPERATOR_FUNC
setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
}
@@ -101,7 +96,7 @@ internal class VimSurroundExtension : VimExtension {
val ijEditor = editor.ij
val c = getChar(ijEditor)
if (c.code == 0) return
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return
val pair = getOrInputPair(c, ijEditor) ?: return
editor.forEachCaret {
val line = it.getBufferPosition().line
@@ -151,7 +146,7 @@ internal class VimSurroundExtension : VimExtension {
val charTo = getChar(editor.ij)
if (charTo.code == 0) return
val newSurround = getOrInputPair(charTo, editor.ij, context.ij) ?: return
val newSurround = getOrInputPair(charTo, editor.ij) ?: return
runWriteAction { change(editor, context, charFrom, newSurround) }
}
@@ -228,12 +223,12 @@ internal class VimSurroundExtension : VimExtension {
val searchHelper = injector.searchHelper
return when (char) {
't' -> searchHelper.findBlockTagRange(editor, caret, 1, true)
'(', ')', 'b' -> findBlockRange(editor, caret, '(', 1, true)
'[', ']' -> findBlockRange(editor, caret, '[', 1, true)
'{', '}', 'B' -> findBlockRange(editor, caret, '{', 1, true)
'<', '>' -> findBlockRange(editor, caret, '<', 1, true)
'(', ')', 'b' -> searchHelper.findBlockRange(editor, caret, '(', 1, true)
'[', ']' -> searchHelper.findBlockRange(editor, caret, '[', 1, true)
'{', '}', 'B' -> searchHelper.findBlockRange(editor, caret, '{', 1, true)
'<', '>' -> searchHelper.findBlockRange(editor, caret, '<', 1, true)
'`', '\'', '"' -> {
val caretOffset = caret.offset
val caretOffset = caret.offset.point
val text = editor.text()
if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) {
TextRange(caretOffset - 1, caretOffset + 1)
@@ -270,23 +265,23 @@ internal class VimSurroundExtension : VimExtension {
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = vimEditor.ij
val c = getChar(ijEditor)
val editor = vimEditor.ij
val c = getChar(editor)
if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
val pair = getOrInputPair(c, editor) ?: return false
runWriteAction {
val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
ijEditor.runWithEveryCaretAndRestore {
applyOnce(ijEditor, change, pair, count)
editor.runWithEveryCaretAndRestore {
applyOnce(editor, change, pair, count)
}
}
else {
applyOnce(ijEditor, change, pair, count)
applyOnce(editor, change, pair, count)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
}
}
return true
@@ -320,9 +315,7 @@ private val LOG = logger<VimSurroundExtension>()
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(
'b' to ("(" to ")"),
@@ -348,8 +341,8 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
null
}
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
val tagInput = inputString(editor, context, "<", '>')
private fun inputTagPair(editor: Editor): Pair<String, String>? {
val tagInput = inputString(editor, "<", '>')
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) {
val tagName = matcher.group(1)
@@ -362,18 +355,17 @@ private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, Str
private fun inputFunctionName(
editor: Editor,
context: DataContext,
withInternalSpaces: Boolean,
): Pair<String, String>? {
val functionNameInput = inputString(editor, context, "function: ", null)
val functionNameInput = inputString(editor, "function: ", null)
if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
}
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor, context)
'f' -> inputFunctionName(editor, context, false)
'F' -> inputFunctionName(editor, context, true)
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor)
'f' -> inputFunctionName(editor, false)
'F' -> inputFunctionName(editor, true)
else -> getSurroundPair(c)
}

View File

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

View File

@@ -131,11 +131,10 @@ public class VimIndentObject implements VimExtension {
// This is done as a separate step so that it works even when the caret is inside the indentation.
int offset = caretLineStartOffset;
int indentSize = 0;
while (offset < charSequence.length()) {
while (++offset < charSequence.length()) {
final char ch = charSequence.charAt(offset);
if (ch == ' ' || ch == '\t') {
++indentSize;
++offset;
} else {
break;
}
@@ -268,7 +267,7 @@ public class VimIndentObject implements VimExtension {
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
if (!vimStateMachine.isOperatorPending()) {
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
if (range != null) {

View File

@@ -68,11 +68,10 @@ import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly
import java.math.BigInteger
@@ -197,7 +196,7 @@ public class ChangeGroup : VimChangeGroupBase() {
val allowWrap = injector.options(editor).whichwrap.contains("~")
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
if (motion is Motion.Error) return false
changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
changeCase(editor, caret, caret.offset.point, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
motion = injector.motion.getHorizontalMotion(
editor,
caret,
@@ -235,7 +234,8 @@ public class ChangeGroup : VimChangeGroupBase() {
}
val lineLength = editor.lineLength(line)
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column)
val pad =
EditorHelper.pad((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context, line, column)
val offset = editor.getLineEndOffset(line)
insertText(editor, caret, offset, pad)
}
@@ -394,7 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext,
range: TextRange,
) {
val startPos = editor.offsetToBufferPosition(caret.offset)
val startPos = editor.offsetToBufferPosition(caret.offset.point)
val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor
@@ -450,7 +450,7 @@ public class ChangeGroup : VimChangeGroupBase() {
dir: Int,
operatorArguments: OperatorArguments,
) {
val start = caret.offset
val start = caret.offset.point
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
}
@@ -484,7 +484,7 @@ public class ChangeGroup : VimChangeGroupBase() {
// Remember the current caret column
val intendedColumn = caret.vimLastColumn
val indentConfig = create((editor as IjVimEditor).editor)
val indentConfig = create((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context)
val sline = editor.offsetToBufferPosition(range.startOffset).line
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
@@ -567,68 +567,54 @@ public class ChangeGroup : VimChangeGroupBase() {
): Boolean {
val startLine = range.startLine
val endLine = range.endLine
val count = range.size
val count = endLine - startLine + 1
if (count < 2) {
return false
}
val startOffset = editor.getLineStartOffset(startLine)
val endOffset = editor.getLineEndOffset(endLine)
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
while (iterator.hasNext()) {
val current = iterator.next()
if (current == previous || sortOptions.ignoreCase && current.equals(previous, ignoreCase = true)) {
if (current == previous || sortOption.ignoreCase && current.equals(previous, ignoreCase = true)) {
iterator.remove()
} else {
previous = current
}
}
}
if (sortedLines.isEmpty()) {
if (lines.size < 1) {
return false
}
replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n"))
replaceText(editor, caret, start, end, StringUtil.join(lines, "\n"))
return true
}
private fun extractPatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> result.value
is VimMatchResult.Failure -> line
}
}
}
private fun deletePatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> line.substring(result.value.length, line.length)
is VimMatchResult.Failure -> line
}
}
}
/**
* Perform increment and decrement for numbers in visual mode
*

View File

@@ -8,11 +8,9 @@
package com.maddyhome.idea.vim.group
import com.intellij.openapi.components.Service
import com.maddyhome.idea.vim.api.VimCommandGroupBase
/**
* @author Elliot Courant
*/
@Service
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.find.EditorSearchSession;
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.State;
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.VimPlugin;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt;
import com.maddyhome.idea.vim.helper.CommandStateHelper;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.UserDataManager;
import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.newapi.IjVimDocument;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener;
@@ -39,12 +33,11 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.intellij.openapi.editor.EditorSettings.*;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
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;
/**
@@ -56,31 +49,12 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
private Boolean isKeyRepeat = null;
// TODO: Get rid of this custom line converter once we support soft wraps properly
// The builtin relative line converter looks like it's using Vim's logical lines for counting, where a Vim logical
// line is a buffer line, or a single line representing a fold of several buffer lines. This converter is counting
// screen lines (but badly - if you're on the second line of a wrapped line, it still counts like you're on the first.
// We really want to use Vim logical lines, but we don't currently support them for movement - we move by screen line.
private final CaretListener myLineNumbersCaretListener = new CaretListener() {
@Override
public void caretPositionChanged(@NotNull CaretEvent e) {
// We don't get notified when the IDE's settings change, so make sure we're up-to-date when the caret moves
final Editor editor = e.getEditor();
boolean relativenumber = ijOptions(injector, new IjVimEditor(editor)).getRelativenumber();
if (relativenumber) {
if (!hasRelativeLineNumbersInstalled(editor)) {
installRelativeLineNumbers(editor);
}
else {
// We must repaint on each caret move, so we update when caret's visual line doesn't match logical line
repaintRelativeLineNumbers(editor);
}
}
else {
if (hasRelativeLineNumbersInstalled(editor)) {
removeRelativeLineNumbers(editor);
}
final boolean requiresRepaint = e.getNewPosition().line != e.getOldPosition().line;
if (requiresRepaint && options(injector, new IjVimEditor(e.getEditor())).getRelativenumber()) {
repaintRelativeLineNumbers(e.getEditor());
}
}
};
@@ -93,10 +67,11 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
editor.getCaretModel().addCaretListener(myLineNumbersCaretListener);
UserDataManager.setVimEditorGroup(editor, true);
UserDataManager.setVimLineNumbersInitialState(editor, editor.getSettings().isLineNumbersShown());
updateLineNumbers(editor);
}
private void deinitLineNumbers(@NotNull Editor editor) {
private void deinitLineNumbers(@NotNull Editor editor, boolean isReleasing) {
if (isProjectDisposed(editor) || !supportsVimLineNumbers(editor) || !UserDataManager.getVimEditorGroup(editor)) {
return;
}
@@ -105,6 +80,14 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
UserDataManager.setVimEditorGroup(editor, false);
removeRelativeLineNumbers(editor);
// Don't reset the built in line numbers if we're releasing the editor. If we do, EditorSettings.setLineNumbersShown
// can cause the editor to refresh settings and can call into FileManagerImpl.getCachedPsiFile AFTER FileManagerImpl
// has been disposed (Closing the project with a Find Usages result showing a preview panel is a good repro case).
// See IDEA-184351 and VIM-1671
if (!isReleasing) {
setBuiltinLineNumbers(editor, UserDataManager.getVimLineNumbersInitialState(editor));
}
}
private static boolean supportsVimLineNumbers(final @NotNull Editor editor) {
@@ -118,22 +101,41 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
}
private static void updateLineNumbers(final @NotNull Editor editor) {
final boolean isLineNumbersShown = editor.getSettings().isLineNumbersShown();
if (!isLineNumbersShown) {
return;
final EffectiveOptions options = options(injector, new IjVimEditor(editor));
final boolean relativeNumber = options.getRelativenumber();
final boolean number = options.getNumber();
final boolean showBuiltinEditorLineNumbers = shouldShowBuiltinLineNumbers(editor, number, relativeNumber);
final EditorSettings settings = editor.getSettings();
if (settings.isLineNumbersShown() ^ showBuiltinEditorLineNumbers) {
// Update line numbers later since it may be called from a caret listener
// on the caret move and it may move the caret internally
ApplicationManager.getApplication().invokeLater(() -> {
if (editor.isDisposed()) return;
setBuiltinLineNumbers(editor, showBuiltinEditorLineNumbers);
});
}
final LineNumerationType lineNumerationType = editor.getSettings().getLineNumerationType();
if (lineNumerationType == LineNumerationType.RELATIVE || lineNumerationType == LineNumerationType.HYBRID) {
if (relativeNumber) {
if (!hasRelativeLineNumbersInstalled(editor)) {
installRelativeLineNumbers(editor);
}
}
else {
else if (hasRelativeLineNumbersInstalled(editor)) {
removeRelativeLineNumbers(editor);
}
}
private static boolean shouldShowBuiltinLineNumbers(final @NotNull Editor editor, boolean number, boolean relativeNumber) {
final boolean initialState = UserDataManager.getVimLineNumbersInitialState(editor);
return initialState || number || relativeNumber;
}
private static void setBuiltinLineNumbers(final @NotNull Editor editor, boolean show) {
editor.getSettings().setLineNumbersShown(show);
}
private static boolean hasRelativeLineNumbersInstalled(final @NotNull Editor editor) {
return UserDataManager.getVimHasRelativeLineNumbersInstalled(editor);
}
@@ -202,8 +204,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
}
public void editorCreated(@NotNull Editor editor) {
UserDataManager.setVimInitialised(editor, true);
DocumentManager.INSTANCE.addListeners(editor.getDocument());
VimPlugin.getKey().registerRequiredShortcutKeys(new IjVimEditor(editor));
initLineNumbers(editor);
@@ -227,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
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
Runnable switchToInsertMode = () -> {
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor));
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(editor), null);
VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
KeyHandler.getInstance().reset(new IjVimEditor(editor));
};
@@ -245,13 +246,14 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
switchToInsertMode.run();
}
});
updateCaretsVisualAttributes(new IjVimEditor(editor));
updateCaretsVisualAttributes(editor);
}
public void editorDeinit(@NotNull Editor editor) {
deinitLineNumbers(editor);
public void editorDeinit(@NotNull Editor editor, boolean isReleased) {
deinitLineNumbers(editor, isReleased);
UserDataManager.unInitializeEditor(editor);
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
DocumentManager.INSTANCE.removeListeners(editor.getDocument());
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
}
@@ -282,18 +284,6 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
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 NumberChangeListener INSTANCE = new NumberChangeListener();
@@ -314,18 +304,17 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
private static class RelativeLineNumberConverter implements LineNumberConverter {
@Override
public Integer convert(@NotNull Editor editor, int lineNumber) {
final IjVimEditor ijVimEditor = new IjVimEditor(editor);
final boolean number = ijOptions(injector, ijVimEditor).getNumber();
final boolean number = options(injector, new IjVimEditor(editor)).getNumber();
final int caretLine = editor.getCaretModel().getLogicalPosition().line;
// lineNumber is 1 based
if ((lineNumber - 1) == caretLine) {
return number ? lineNumber : 0;
if (number && (lineNumber - 1) == caretLine) {
return lineNumber;
}
else {
final int visualLine = ijVimEditor.bufferLineToVisualLine(lineNumber - 1);
final int caretVisualLine = editor.getCaretModel().getVisualPosition().line;
return Math.abs(caretVisualLine - visualLine);
final int visualLine = new IjVimEditor(editor).bufferLineToVisualLine(lineNumber - 1);
final int currentVisualLine = new IjVimEditor(editor).bufferLineToVisualLine(caretLine);
return Math.abs(currentVisualLine - visualLine);
}
}
@@ -335,51 +324,20 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
}
}
@NotNull
@Override
public @NotNull Collection<VimEditor> getEditorsRaw() {
return getLocalEditors()
public Collection<VimEditor> localEditors() {
return HelperKt.localEditors().stream()
.map(IjVimEditor::new)
.collect(Collectors.toList());
}
@NotNull
@Override
public Collection<VimEditor> getEditors() {
return getLocalEditors()
.filter(UserDataManager::getVimInitialised)
.map(IjVimEditor::new)
.collect(Collectors.toList());
}
@NotNull
@Override
public Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
public Collection<VimEditor> localEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument();
return getLocalEditors()
.filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
return HelperKt.localEditors(document).stream()
.map(IjVimEditor::new)
.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 the current context.
List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.ALL);
if (!appSessions.isEmpty()) {
ClientAppSession localSession = appSessions.get(0);
return localSession.getService(ClientEditorManager.class).editors();
}
else {
return Stream.empty();
}
}
}

View File

@@ -22,17 +22,16 @@ import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.VirtualFileSystem;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.helper.EditorHelper;
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.IjEditorExecutionContext;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
@@ -442,35 +438,14 @@ public class FileGroup extends VimFileBase {
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) {
// The user has changed the editor they are working with - exit insert/replace mode, and complete any
// appropriate repeat
if (event.getOldFile() != null) {
LastTabService.getInstance(event.getManager().getProject()).setLastTab(event.getOldFile());
}
}
@Nullable
@Override
public VimEditor selectEditor(@NotNull String projectId, @NotNull String documentPath, @Nullable String protocol) {
VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol);
if (fileSystem == null) return null;
VirtualFile virtualFile = fileSystem.findFileByPath(documentPath);
if (virtualFile == null) return null;
Project project = Arrays.stream(ProjectManager.getInstance().getOpenProjects())
.filter(p -> injector.getFile().getProjectId(p).equals(projectId))
.findFirst().orElseThrow();
Editor editor = selectEditor(project, virtualFile);
if (editor == null) return null;
return new IjVimEditor(editor);
}
@NotNull
@Override
public String getProjectId(@NotNull Object project) {
if (!(project instanceof Project)) throw new IllegalArgumentException();
return ((Project) project).getName();
}
}

View File

@@ -20,6 +20,7 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
*/
@Suppress("SpellCheckingInspection")
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 ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
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 var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
// 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 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 commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
}
/**
@@ -45,18 +46,6 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
* As a convenience, this class also provides access to the IntelliJ specific global options, via inheritance.
*/
public class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOptions(scope) {
// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
public var breakindent: Boolean by optionProperty(IjOptions.breakindent)
public val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
public var cursorline: Boolean by optionProperty(IjOptions.cursorline)
public var fileformat: String by optionProperty(IjOptions.fileformat)
public var list: Boolean by optionProperty(IjOptions.list)
public var number: Boolean by optionProperty(IjOptions.number)
public var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
public var textwidth: Int by optionProperty(IjOptions.textwidth)
public var wrap: Boolean by optionProperty(IjOptions.wrap)
// IntelliJ specific options
public var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
public var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
public var idearefactormode: String by optionProperty(IjOptions.idearefactormode)

View File

@@ -10,14 +10,9 @@ package com.maddyhome.idea.vim.group
import com.intellij.openapi.application.ApplicationNamesInfo
import com.maddyhome.idea.vim.api.Options
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.ex.exExceptionMessage
import com.maddyhome.idea.vim.options.NumberOption
import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER
import com.maddyhome.idea.vim.options.OptionDeclaredScope.LOCAL_TO_BUFFER
import com.maddyhome.idea.vim.options.OptionDeclaredScope.LOCAL_TO_WINDOW
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.ToggleOption
@@ -38,56 +33,8 @@ public object IjOptions {
Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux"))
}
// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
public val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
public val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") {
override fun checkIfValueValid(value: VimDataType, token: String) {
super.checkIfValueValid(value, token)
if (value != VimString.EMPTY) {
// Each element in the comma-separated string list needs to be a number. No spaces. Vim supports numbers
// beginning "+" or "-" to draw a highlight column relative to the 'textwidth' value. We don't fully support
// that, but we do automatically add "+0" because IntelliJ always displays the right margin
split((value as VimString).asString()).forEach {
if (!it.matches(Regex("[+-]?[0-9]+"))) {
throw exExceptionMessage("E474", token)
}
}
}
}
})
public val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
public val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
public val number: ToggleOption = addOption(ToggleOption("number", LOCAL_TO_WINDOW, "nu", false))
public val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
public val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
public val wrap: ToggleOption = addOption(ToggleOption("wrap", LOCAL_TO_WINDOW, "wrap", true))
// These options are not explicitly listed as local-noglobal in Vim's help, but are set when a new buffer is edited,
// based on the value of 'fileformats' or 'fileencodings'. To prevent unexpected file cnversion, we treat them as
// local-noglobal. See `:help local-noglobal`, `:help 'fileformats'` and `:help 'fileencodings'`
public val bomb: ToggleOption =
addOption(ToggleOption("bomb", LOCAL_TO_BUFFER, "bomb", false, isLocalNoGlobal = true))
public val fileencoding: StringOption = addOption(
StringOption(
"fileencoding",
LOCAL_TO_BUFFER,
"fenc",
VimString.EMPTY,
isLocalNoGlobal = true
)
)
public val fileformat: StringOption = addOption(
StringOption(
"fileformat",
LOCAL_TO_BUFFER,
"ff",
if (injector.systemInfoService.isWindows) "dos" else "unix",
boundedValues = setOf("dos", "unix", "mac"),
isLocalNoGlobal = true
)
)
// IntelliJ specific functionality - custom options
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(
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
)
@@ -134,16 +81,13 @@ public object IjOptions {
"<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 unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
// Temporary feature flags during development, not really intended for external use
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = 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))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isTemporary = true))
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
// derives from Option<VimInt>

View File

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

@@ -1,50 +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.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimRedrawService
import com.maddyhome.idea.vim.api.injector
public class IjVimRedrawService : VimRedrawService {
override fun redraw() {
// The only thing IntelliJ needs to redraw is the status line. Everything else is handled automatically.
redrawStatusLine()
}
override fun redrawStatusLine() {
injector.messages.clearStatusBarMessage()
}
public companion object {
/**
* Simulate Vim's redraw when the current editor changes
*/
public fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
injector.redrawService.redraw()
}
}
/**
* Simulates Vim's redraw when the document changes
*
* Only redraw if lines are added/removed.
*/
public object RedrawListener : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
if (VimPlugin.isNotEnabled()) return
if (event.newFragment.contains("\n") || event.oldFragment.contains("\n")) {
injector.redrawService.redraw()
}
}
}
}

View File

@@ -26,12 +26,10 @@ import com.maddyhome.idea.vim.EventFacade;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.action.change.LazyVimCommand;
import com.maddyhome.idea.vim.api.NativeAction;
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.api.*;
import com.maddyhome.idea.vim.command.MappingMode;
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.newapi.IjNativeAction;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
@@ -101,9 +99,9 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
@Override
public void updateShortcutKeysRegistration() {
for (VimEditor editor : injector.getEditorGroup().getEditors()) {
unregisterShortcutKeys(editor);
registerRequiredShortcutKeys(editor);
for (Editor editor : HelperKt.localEditors()) {
unregisterShortcutKeys(new IjVimEditor(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) {
for (KeyStroke key : keys) {
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_ENTER && key.getModifiers() == 0)) {
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));

View File

@@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.completion.CompletionPhase
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressManager
@@ -27,7 +26,6 @@ import com.maddyhome.idea.vim.newapi.ij
/**
* Used to handle playback of macros
*/
@Service
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
@@ -78,12 +76,11 @@ internal class MacroGroup : VimMacroBase() {
} catch (e: ProcessCanceledException) {
return@runnable
}
val keyHandler = getInstance()
ProgressManager.getInstance().executeNonCancelableSection {
// Prevent autocompletion during macros.
// See https://github.com/JetBrains/ideavim/pull/772 for details
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
keyHandler.handleKey(editor, key, context, keyHandler.keyHandlerState)
getInstance().handleKey(editor, key, context)
}
if (injector.messages.isError()) return@runnable
}

View File

@@ -8,28 +8,43 @@
package com.maddyhome.idea.vim.group
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.fileEditor.impl.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.VimPlugin
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroupBase
import com.maddyhome.idea.vim.api.VimEditor
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.getJump
import com.maddyhome.idea.vim.api.getJumpSpot
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
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.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.normalizeVisualLine
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.api.visualLineToBufferLine
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.MotionType
@@ -38,34 +53,59 @@ import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.handler.Motion
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.TextObjectActionHandler
import com.maddyhome.idea.vim.handler.toMotionOrError
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.fileSize
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.helper.vimStateMachine
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.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import org.jetbrains.annotations.Range
import java.io.File
import kotlin.math.max
import kotlin.math.min
/**
* This handles all motion related commands and marks
*/
@Service
internal class MotionGroup : VimMotionGroupBase() {
override fun onAppCodeMovement(editor: VimEditor, caret: VimCaret, offset: Int, oldOffset: Int) {
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(
editor: VimEditor,
caret: ImmutableVimCaret,
@@ -88,12 +128,85 @@ internal class MotionGroup : VimMotionGroupBase() {
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 {
val width = EditorHelper.getApproximateScreenWidth(editor.ij) / 2
val len = editor.lineLength(editor.currentCaret().getBufferPosition().line)
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 {
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
return moveCaretToColumn(editor, caret, col, false)
@@ -104,7 +217,7 @@ internal class MotionGroup : VimMotionGroupBase() {
caret: ImmutableVimCaret,
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
val bufferLine = caret.getLine()
val bufferLine = caret.getLine().line
return editor.getLeadingCharacterOffset(bufferLine, col)
}
@@ -117,6 +230,36 @@ internal class MotionGroup : VimMotionGroupBase() {
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.
*/
@@ -134,18 +277,30 @@ internal class MotionGroup : VimMotionGroupBase() {
}
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
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 {
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
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 {
@@ -302,21 +457,16 @@ internal class MotionGroup : VimMotionGroupBase() {
}
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
ExEntryPanel.deactivateAll()
val fileEditor = event.oldEditor
if (fileEditor is TextEditor) {
val editor = fileEditor.editor
if (!editor.isDisposed) {
ExOutputModel.getInstance(editor).clear()
editor.vim.let { vimEditor ->
when (vimEditor.vimStateMachine.mode) {
is Mode.VISUAL -> {
vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor)
}
is Mode.CMD_LINE -> {
injector.processGroup.cancelExEntry(vimEditor, false)
ExOutputModel.getInstance(editor).clear()
}
else -> {}
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor)
}
}
}

View File

@@ -21,7 +21,6 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.ide.CopyPasteManager
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.
* 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?) {
// This constructor is used to create an applicationService
@Suppress("unused")
@@ -278,7 +276,7 @@ internal class NotificationService(private val project: Project?) {
}
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) {
CopyPasteManager.getInstance().setContents(StringSelection(id ?: ""))
if (id != null) {
ActionTracker.Util.logCopiedAction(id)
ActionTracker.logCopiedAction(id)
}
notification?.expire()

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicatorProvider
@@ -19,7 +20,6 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.util.execution.ParametersListUtil
import com.intellij.util.text.CharSequenceReader
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
@@ -27,20 +27,26 @@ import com.maddyhome.idea.vim.api.VimProcessGroupBase
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.InvalidCommandException
import com.maddyhome.idea.vim.helper.requestFocus
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
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.VISUAL
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.returnTo
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import java.io.BufferedWriter
import java.io.IOException
import java.io.OutputStreamWriter
import java.io.Reader
import java.io.Writer
import javax.swing.KeyStroke
import javax.swing.SwingUtilities
public class ProcessGroup : VimProcessGroupBase() {
override var lastCommand: String? = null
@@ -48,13 +54,25 @@ public class ProcessGroup : VimProcessGroupBase() {
override var isCommandProcessing: Boolean = false
override var modeBeforeCommandProcessing: Mode? = null
public override fun startExEntry(
editor: VimEditor,
context: ExecutionContext,
command: Command,
label: String,
initialCommandText: String,
) {
public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
// Don't allow searching in one line editors
if (editor.isOneLineMode()) return
val initText = ""
val label = leader.toString()
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, label, initText, count)
}
public override fun endSearchCommand(): String {
val panel = ExEntryPanel.getInstance()
panel.deactivate(true)
return panel.text
}
public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
// Don't allow ex commands in one line editors
if (editor.isOneLineMode()) return
@@ -65,60 +83,103 @@ public class ProcessGroup : VimProcessGroupBase() {
isCommandProcessing = true
modeBeforeCommandProcessing = currentMode
// Make sure the Visual selection marks are up to date before we use them.
val initText = getRange(editor, cmd)
injector.markService.setVisualSelectionMarks(editor)
val rangeText = getRange(editor, command)
// Note that we should remove selection and reset caret offset before we switch back to Normal mode and then enter
// Command-line mode. However, some IdeaVim commands can handle multiple carets, including multiple carets with
// selection (which might or might not be a block selection). Unfortunately, because we're just entering
// Command-line mode, we don't know which command is going to be entered, so we can't remove selection here.
// Therefore, we switch to Normal and then Command-line even though we might still have a Visual selection...
// On the plus side, it means we still show selection while editing the command line, which Vim also does
// (Normal then Command-line is not strictly necessary, but done for completeness and autocmd)
// Caret selection is finally handled in Command.execute
editor.mode = Mode.NORMAL()
editor.mode = Mode.CMD_LINE(currentMode)
injector.commandLine.create(editor, context, ":", rangeText + initialCommandText, 1)
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
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
// is open. So I'll put focus back in the editor and process the key.
val panel = ExEntryPanel.getInstance()
if (panel.isActive) {
processResultBuilder.addExecutionStep { _, _, _ ->
requestFocus(panel.entry)
panel.handleKey(stroke)
}
requestFocus(panel.entry)
panel.handleKey(stroke)
return true
} else {
processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
lambdaEditor.mode = Mode.NORMAL()
getInstance().reset(lambdaEditor)
}
getInstance(editor).mode = NORMAL()
getInstance().reset(editor)
return false
}
}
public override fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean {
val panel = ExEntryPanel.getInstance()
panel.deactivate(true)
var res = true
try {
getInstance(editor).mode = NORMAL()
logger.debug("processing command")
val text = panel.text
if (panel.label != ":") {
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
// We should never be invoked for anything other than an actual ex command.
throw InvalidCommandException("Expected ':' command. Got '" + panel.label + "'", text)
}
logger.debug {
"swing=" + SwingUtilities.isEventDispatchThread()
}
injector.vimscriptExecutor.execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext)
} catch (e: ExException) {
VimPlugin.showMessage(e.message)
VimPlugin.indicateError()
res = false
} catch (bad: Exception) {
logger.error(bad)
VimPlugin.indicateError()
res = false
} finally {
isCommandProcessing = false
modeBeforeCommandProcessing = null
}
return res
}
// commands executed from map command / macro should not be added to history
private fun skipHistory(editor: VimEditor): Boolean {
return getInstance(editor).mappingState.isExecutingMap() || injector.macro.isExecutingMacro
}
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
// If 'cpoptions' contains 'x', then Escape should execute the command line. This is the default for Vi but not Vim.
// IdeaVim does not (currently?) support 'cpoptions', so sticks with Vim's default behaviour. Escape cancels.
editor.mode = editor.mode.returnTo()
editor.vimStateMachine.mode = NORMAL()
getInstance().reset(editor)
val panel = ExEntryPanel.getInstance()
panel.deactivate(true, resetCaret)
}
private fun getRange(editor: VimEditor, cmd: Command) = when {
editor.inVisualMode -> "'<,'>"
cmd.rawCount == 1 -> "."
cmd.rawCount > 1 -> ".,.+" + (cmd.count - 1)
else -> ""
public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
val initText = getRange(editor, cmd) + "!"
val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}
private fun getRange(editor: VimEditor, cmd: Command): String {
var initText = ""
if (editor.vimStateMachine.mode is VISUAL) {
initText = "'<,'>"
} else if (cmd.rawCount > 0) {
initText = if (cmd.count == 1) {
"."
} else {
".,.+" + (cmd.count - 1)
}
}
return initText
}
@Throws(ExecutionException::class, ProcessCanceledException::class)

View File

@@ -14,9 +14,9 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
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.VimRegisterGroupBase;
import com.maddyhome.idea.vim.state.mode.SelectionType;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
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);
public RegisterGroup() {
this.initClipboardOptionListener();
}
public void saveData(final @NotNull Element element) {
logger.debug("Save registers data");
final Element registersElement = new Element("registers");

View File

@@ -24,6 +24,7 @@ import com.maddyhome.idea.vim.helper.ScrollViewHelper
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.vimEditorGroup
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
@@ -257,7 +258,11 @@ internal class ScrollGroup : VimScrollGroup {
object ScrollOptionsChangeListener : EffectiveOptionValueChangeListener {
override fun onEffectiveValueChanged(editor: VimEditor) {
editor.ij.apply { ScrollViewHelper.scrollCaretIntoView(this) }
editor.ij.apply {
if (vimEditorGroup) {
ScrollViewHelper.scrollCaretIntoView(this)
}
}
}
}

View File

@@ -21,10 +21,11 @@ import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.markup.RangeHighlighter;
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.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.command.MotionType;
import com.maddyhome.idea.vim.common.CharacterPosition;
import com.maddyhome.idea.vim.common.Direction;
import com.maddyhome.idea.vim.common.TextRange;
@@ -32,11 +33,12 @@ import com.maddyhome.idea.vim.ex.ExException;
import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.*;
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.regexp.CharPointer;
import com.maddyhome.idea.vim.regexp.CharacterClasses;
import com.maddyhome.idea.vim.regexp.RegExp;
import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.ui.ModalEntry;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.vimscript.model.VimLContext;
@@ -57,23 +59,28 @@ import java.text.ParsePosition;
import java.util.*;
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.newapi.IjVimInjectorKt.globalIjOptions;
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
/**
* @deprecated Replace with IjVimSearchGroup
*/
@State(name = "VimSearchSettings", storages = {
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
})
@Deprecated
/**
* @deprecated Replace with IjVimSearchGroup
*/
public class SearchGroup extends IjVimSearchGroup implements PersistentStateComponent<Element> {
public SearchGroup() {
super();
if (!globalIjOptions(injector).getUseNewRegex()) {
// We use the global option listener instead of the effective listener that gets called for each affected editor
// because we handle updating the affected editors ourselves (e.g., we can filter for visible windows).
// TODO: Investigate migrating these listeners to use the effective value change listener
// This would allow us to update the editor we're told to update, rather than looping over all projects and updating
// the highlights in that project's current document's open editors (see VIM-2779).
// However, we probably only want to update the editors associated with the current document, so maybe the whole
// code needs to be reworked. We're currently using the same update code for changes in the search term as well as
// changes in the search options.
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch, () -> {
resetShowSearchHighlight();
forceUpdateSearchHighlights();
@@ -147,11 +154,11 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
@Override
protected @Nullable String getLastUsedPattern() {
if (globalIjOptions(injector).getUseNewRegex()) return super.getLastUsedPattern();
return switch (lastPatternIdx) {
case RE_SEARCH -> lastSearch;
case RE_SUBST -> lastSubstitute;
default -> null;
};
switch (lastPatternIdx) {
case RE_SEARCH: return lastSearch;
case RE_SUBST: return lastSubstitute;
}
return null;
}
/**
@@ -269,18 +276,11 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* Can include a trailing offset, e.g. /{pattern}/{offset}, or multiple commands separated by a semicolon.
* If the pattern is empty, the last used (search? substitute?) pattern (and offset?) is used.
* @param dir The direction to search
* @return Pair containing the offset to the next occurrence of the pattern, and the [MotionType] based on
* the search offset. The value will be `null` if no result is found.
* @return Offset to the next occurrence of the pattern or -1 if not found
*/
@Nullable
@Override
public Pair<Integer, MotionType> processSearchCommand(@NotNull VimEditor editor,
@NotNull String command,
int startOffset,
int count1,
@NotNull Direction dir) {
if (globalIjOptions(injector).getUseNewRegex()) return super.processSearchCommand(editor, command, startOffset, count1, dir);
public int processSearchCommand(@NotNull VimEditor editor, @NotNull String command, int startOffset, @NotNull Direction dir) {
if (globalIjOptions(injector).getUseNewRegex()) return super.processSearchCommand(editor, command, startOffset, dir);
boolean isNewPattern = false;
String pattern = null;
@@ -288,7 +288,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
final char type = dir == Direction.FORWARDS ? '/' : '?';
if (!command.isEmpty()) {
if (command.length() > 0) {
if (command.charAt(0) != type) {
CharPointer p = new CharPointer(command);
CharPointer end = RegExp.skip_regexp(p.ref(0), type, true);
@@ -340,19 +340,17 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
// Direction is saved in do_search
// IgnoreSmartCase is only ever set for searching words (`*`, `#`, `g*`, etc.) and is reset for all other operations
if (pattern == null || pattern.isEmpty()) {
if (pattern == null || pattern.length() == 0) {
pattern = getLastSearchPattern();
if (pattern == null || pattern.isEmpty()) {
patternOffset = lastPatternOffset;
if (pattern == null || pattern.length() == 0) {
isNewPattern = true;
pattern = getLastSubstitutePattern();
if (pattern == null || pattern.isEmpty()) {
if (pattern == null || pattern.length() == 0) {
VimPlugin.showMessage(MessageHelper.message("e_noprevre"));
return null;
return -1;
}
}
if (patternOffset == null || patternOffset.isEmpty()) {
patternOffset = lastPatternOffset;
}
}
// Save the pattern. If it's explicitly entered, or comes from RE_SUBST, isNewPattern is true, and this becomes
@@ -371,7 +369,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
resetShowSearchHighlight();
forceUpdateSearchHighlights();
return findItOffset(((IjVimEditor)editor).getEditor(), startOffset, count1, lastDir);
return findItOffset(((IjVimEditor)editor).getEditor(), startOffset, 1, lastDir);
}
/**
@@ -413,7 +411,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
resetShowSearchHighlight();
forceUpdateSearchHighlights();
final Pair<Integer, MotionType> result = findItOffset(editor, startOffset, 1, lastDir);
final int result = findItOffset(editor, startOffset, 1, lastDir);
// Set lastPatternOffset AFTER searching so it doesn't affect the result
lastPatternOffset = patternOffset != 0 ? Integer.toString(patternOffset) : "";
@@ -424,7 +422,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
logger.debug("lastDir=" + lastDir);
}
return result != null ? result.getFirst() : -1;
return result;
}
/**
@@ -469,9 +467,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
resetShowSearchHighlight();
forceUpdateSearchHighlights();
final Pair<Integer, MotionType> offsetAndMotion =
findItOffset(((IjVimEditor)editor).getEditor(), range.getStartOffset(), count, lastDir);
return offsetAndMotion == null ? range.getStartOffset() : offsetAndMotion.getFirst();
final int offset = findItOffset(((IjVimEditor)editor).getEditor(), range.getStartOffset(), count, lastDir);
return offset == -1 ? range.getStartOffset() : offset;
}
/**
@@ -514,14 +511,14 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
resetShowSearchHighlight();
updateSearchHighlights();
final int startOffset = caret.getOffset();
Pair<Integer, MotionType> offsetAndMotion = findItOffset(editor, startOffset, count, dir);
if (offsetAndMotion != null && offsetAndMotion.getFirst() == startOffset) {
int offset = findItOffset(editor, startOffset, count, dir);
if (offset == startOffset) {
/* Avoid getting stuck on the current cursor position, which can
* happen when an offset is given and the cursor is on the last char
* in the buffer: Repeat with count + 1. */
offsetAndMotion = findItOffset(editor, startOffset, count + 1, dir);
offset = findItOffset(editor, startOffset, count + 1, dir);
}
return offsetAndMotion != null ? offsetAndMotion.getFirst() : -1;
return offset;
}
@@ -545,24 +542,20 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
*
* @param editor The editor to search in
* @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 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]`
* @return True if the substitution succeeds, false on error. Will succeed even if nothing is modified
* @return True if the substitution succeeds, false on error. Will succeed even if nothing is modified
*/
@Override
@RWLockLabel.SelfSynchronized
public boolean processSubstituteCommand(@NotNull VimEditor editor,
@NotNull VimCaret caret,
@NotNull ExecutionContext context,
@NotNull LineRange range,
@NotNull @NonNls String excmd,
@NotNull @NonNls String exarg,
@NotNull VimLContext parent) {
if (globalIjOptions(injector).getUseNewRegex()) {
return super.processSubstituteCommand(editor, caret, context, range, excmd, exarg, parent);
}
if (globalIjOptions(injector).getUseNewRegex()) return super.processSubstituteCommand(editor, caret, 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.
List<ExException> exceptions = new ArrayList<>();
@@ -811,7 +804,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
CharacterPosition startpos = new CharacterPosition(lnum + regmatch.startpos[0].lnum, regmatch.startpos[0].col);
CharacterPosition endpos = new CharacterPosition(lnum + regmatch.endpos[0].lnum, regmatch.endpos[0].col);
int startoff = startpos.toOffset(((IjVimEditor)editor).getEditor());
int endoff = (endpos.line >= lcount) ? (int) editor.fileSize() : endpos.toOffset(((IjVimEditor)editor).getEditor());
int endoff = endpos.toOffset(((IjVimEditor)editor).getEditor());
if (do_all || line != lastLine) {
boolean doReplace = true;
@@ -819,7 +812,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
RangeHighlighter hl =
SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor)editor).getEditor(), startoff,
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);
switch (choice) {
case SUBSTITUTE_THIS:
@@ -848,7 +841,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
caret.moveToOffset(startoff);
if (expression != null) {
try {
match = expression.evaluate(editor, context, parent).toInsertableString();
match =
expression.evaluate(editor, injector.getExecutionContextManager().onEditor(editor, null), parent).toInsertableString();
}
catch (Exception e) {
exceptions.add((ExException)e);
@@ -870,23 +864,23 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
lastLine = line;
lnum += nmatch - 1;
if (do_all && startoff != endoff) {
if (newpos != null) {
lnum = newpos.line;
searchcol = newpos.column;
}
else {
lnum += Math.max(1, nmatch - 1);
searchcol = endpos.column;
}
}
else {
lnum += Math.max(1, nmatch - 1);
searchcol = 0;
lnum++;
}
}
else {
lnum += Math.max(1, nmatch - 1);
lnum++;
searchcol = 0;
}
}
@@ -897,8 +891,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, editor.offsetToBufferPosition(lastMatch).getLine()));
}
else {
// E486: Pattern not found: {0}
VimPlugin.showMessage(MessageHelper.message("E486", pattern));
VimPlugin.showMessage(MessageHelper.message(Msg.e_patnotf2, pattern));
}
}
@@ -960,17 +953,17 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
if (which_pat == RE_LAST) {
which_pat = lastPatternIdx;
}
String errorMessage = switch (which_pat) {
case RE_SEARCH -> {
String errorMessage = null;
switch (which_pat) {
case RE_SEARCH:
pattern = lastSearch;
yield MessageHelper.message("e_nopresub");
}
case RE_SUBST -> {
errorMessage = MessageHelper.message("e_nopresub");
break;
case RE_SUBST:
pattern = lastSubstitute;
yield MessageHelper.message("e_noprevre");
}
default -> null;
};
errorMessage = MessageHelper.message("e_noprevre");
break;
}
// Pattern was never defined
if (pattern == null) {
@@ -1000,9 +993,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
return new Pair<>(true, new Triple<>(regmatch, pattern, sp));
}
private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor,
@NotNull ExecutionContext context,
@NotNull String match, @NotNull Caret caret, int startoff) {
private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT);
final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> {
final ReplaceConfirmationChoice choice;
@@ -1036,6 +1027,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
else {
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
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);
new IjVimCaret(caret).moveToOffset(startoff);
ModalEntry.INSTANCE.activate(new IjVimEditor(editor), keyStrokeProcessor);
@@ -1093,9 +1085,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
private @Nullable TextRange findNextSearchForGn(@NotNull VimEditor editor, int count, boolean forwards) {
if (forwards) {
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 {
return searchBackward(editor, editor.primaryCaret().getOffset(), count);
return searchBackward(editor, editor.primaryCaret().getOffset().getPoint(), count);
}
}
@@ -1208,56 +1200,47 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
public static DocumentSearchListener INSTANCE = new DocumentSearchListener();
@Contract(pure = true)
private DocumentSearchListener() {
private DocumentSearchListener () {
}
@Override
public void documentChanged(@NotNull DocumentEvent event) {
// Loop over all local editors for the changed document, across all projects, and update search highlights.
// 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();
for (VimEditor vimEditor : injector.getEditorGroup().getEditors(new IjVimDocument(document))) {
final Editor editor = ((IjVimEditor)vimEditor).getEditor();
Collection<RangeHighlighter> existingHighlighters = UserDataManager.getVimLastHighlighters(editor);
if (existingHighlighters == null) {
continue;
}
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
final Document document = event.getDocument();
if (logger.isDebugEnabled()) {
logger.debug("hls=" + existingHighlighters);
logger.debug("event=" + event);
}
// We can only re-highlight whole lines, so clear any highlights in the affected lines.
// If we're deleting lines, this will clear + re-highlight the new current line, which hasn't been modified.
// However, we still want to re-highlight this line in case any highlights cross the line boundaries.
// If we're adding lines, this will clear + re-highlight all new lines.
final LogicalPosition startPosition = editor.offsetToLogicalPosition(event.getOffset());
final LogicalPosition endPosition = editor.offsetToLogicalPosition(event.getOffset() + event.getNewLength());
final int startLineOffset = document.getLineStartOffset(startPosition.line);
final int endLineOffset = document.getLineEndOffset(endPosition.line);
// Remove any highlights that have already been deleted, and remove + clear those that intersect with the change
final Iterator<RangeHighlighter> iter = existingHighlighters.iterator();
while (iter.hasNext()) {
final RangeHighlighter highlighter = iter.next();
if (!highlighter.isValid()) {
iter.remove();
for (Editor editor : localEditors(document, project)) {
Collection<RangeHighlighter> hls = UserDataManager.getVimLastHighlighters(editor);
if (hls == null) {
continue;
}
else if (highlighter.getTextRange().intersects(startLineOffset, endLineOffset)) {
iter.remove();
editor.getMarkupModel().removeHighlighter(highlighter);
if (logger.isDebugEnabled()) {
logger.debug("hls=" + hls);
logger.debug("event=" + event);
}
}
VimPlugin.getSearch().highlightSearchLines(editor, startPosition.line, endPosition.line);
// We can only re-highlight whole lines, so clear any highlights in the affected lines
final LogicalPosition startPosition = editor.offsetToLogicalPosition(event.getOffset());
final LogicalPosition endPosition = editor.offsetToLogicalPosition(event.getOffset() + event.getNewLength());
final int startLineOffset = document.getLineStartOffset(startPosition.line);
final int endLineOffset = document.getLineEndOffset(endPosition.line);
if (logger.isDebugEnabled()) {
existingHighlighters = UserDataManager.getVimLastHighlighters(editor);
logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line);
logger.debug("hls=" + existingHighlighters);
final Iterator<RangeHighlighter> iter = hls.iterator();
while (iter.hasNext()) {
final RangeHighlighter highlighter = iter.next();
if (!highlighter.isValid() || (highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
iter.remove();
editor.getMarkupModel().removeHighlighter(highlighter);
}
}
VimPlugin.getSearch().highlightSearchLines(editor, startPosition.line, endPosition.line);
if (logger.isDebugEnabled()) {
hls = UserDataManager.getVimLastHighlighters(editor);
logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line);
logger.debug("hls=" + hls);
}
}
}
}
@@ -1283,11 +1266,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* @param startOffset The offset to search from
* @param count Find the nth occurrence
* @param dir The direction to search in
* @return Pair containing the offset to the next occurrence of the pattern, and the [MotionType] based
* on the search offset. The value will be `null` if no result is found.
* @return The offset to the occurrence or -1 if not found
*/
@Nullable
private Pair<Integer, MotionType> findItOffset(@NotNull Editor editor, int startOffset, int count, Direction dir) {
private int findItOffset(@NotNull Editor editor, int startOffset, int count, Direction dir) {
boolean wrap = globalOptions(injector).getWrapscan();
logger.debug("Perform search. Direction: " + dir + " wrap: " + wrap);
@@ -1297,7 +1278,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
ParsePosition pp = new ParsePosition(0);
if (!lastPatternOffset.isEmpty()) {
if (lastPatternOffset.length() > 0) {
if (Character.isDigit(lastPatternOffset.charAt(0)) || lastPatternOffset.charAt(0) == '+' || lastPatternOffset.charAt(0) == '-') {
offsetIsLineOffset = true;
@@ -1333,18 +1314,6 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
}
}
// `/{pattern}/{offset}` is inclusive if offset contains `e`, and linewise if there's a line offset
final MotionType motionType;
if (offset != 0 && !hasEndOffset) {
motionType = MotionType.LINE_WISE;
}
else if (hasEndOffset) {
motionType = MotionType.INCLUSIVE;
}
else {
motionType = MotionType.EXCLUSIVE;
}
/*
* If there is a character offset, subtract it from the current
* position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
@@ -1366,7 +1335,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
TextRange range = injector.getSearchHelper().findPattern(new IjVimEditor(editor), getLastUsedPattern(), startOffset, count, searchOptions);
if (range == null) {
logger.warn("No range is found");
return null;
return -1;
}
int res = range.getStartOffset();
@@ -1374,6 +1343,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
if (offsetIsLineOffset) {
int line = editor.offsetToLogicalPosition(range.getStartOffset()).line;
int newLine = EngineEditorHelperKt.normalizeLine(new IjVimEditor(editor), line + offset);
// TODO: Don't move the caret!
res = VimPlugin.getMotion().moveCaretToLineStart(new IjVimEditor(editor), newLine);
}
else if (hasEndOffset || offset != 0) {
@@ -1391,19 +1362,16 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
nextDir = Direction.BACKWARDS;
}
else {
return new Pair(res, motionType);
return res;
}
if (lastPatternOffset.length() - ppos > 2) {
ppos++;
}
Pair<Integer, MotionType> offsetAndMotion =
processSearchCommand(new IjVimEditor(editor), lastPatternOffset.substring(ppos + 1), res, 1, nextDir);
res = offsetAndMotion != null ? offsetAndMotion.getFirst() : -1;
res = processSearchCommand(new IjVimEditor(editor), lastPatternOffset.substring(ppos + 1), res, nextDir);
}
return new Pair<Integer, MotionType>(res, motionType);
return res;
}
@@ -1419,7 +1387,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
addOptionalTextElement(search, "last-search", lastSearch);
addOptionalTextElement(search, "last-substitute", lastSubstitute);
addOptionalTextElement(search, "last-offset", !lastPatternOffset.isEmpty() ? lastPatternOffset : null);
addOptionalTextElement(search, "last-offset", lastPatternOffset.length() > 0 ? lastPatternOffset : null);
addOptionalTextElement(search, "last-replace", lastReplace);
addOptionalTextElement(search, "last-pattern", lastPatternIdx == RE_SEARCH ? lastSearch : lastSubstitute);
addOptionalTextElement(search, "last-dir", Integer.toString(lastDir.toInt()));

View File

@@ -111,7 +111,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
}
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 bufferPosition = editor.offsetToBufferPosition(offset)

View File

@@ -7,7 +7,6 @@
*/
package com.maddyhome.idea.vim.group
import com.intellij.codeWithMe.ClientId
import com.intellij.ide.bookmark.Bookmark
import com.intellij.ide.bookmark.BookmarkGroup
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.diagnostic.Logger
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.DocumentListener
import com.intellij.openapi.fileEditor.FileEditorManager
@@ -29,11 +28,11 @@ import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.asSafely
import com.maddyhome.idea.vim.VimPlugin
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.VimMarkServiceBase
import com.maddyhome.idea.vim.api.injector
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.Mark
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
* 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
*/
override fun beforeDocumentChange(event: DocumentEvent) {
@@ -205,18 +200,15 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
if (event.oldLength == 0) return
val doc = event.document
val anEditor = getAnyEditorForDocument(doc) ?: return
injector.markService.updateMarksFromDelete(anEditor, event.offset, event.oldLength)
val anEditor = getAnEditor(doc) ?: return
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
* 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
*/
override fun documentChanged(event: DocumentEvent) {
@@ -224,19 +216,19 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
val doc = event.document
val anEditor = getAnyEditorForDocument(doc) ?: return
injector.markService.updateMarksFromInsert(anEditor, event.offset, event.newLength)
val anEditor = getAnEditor(doc) ?: return
injector.markService
.updateMarksFromInsert(IjVimEditor(anEditor), event.offset, event.newLength)
}
/**
* Get any editor for the given document
*
* We need an editor to help calculate offsets for marks, and it doesn't matter which one we use, because they would
* all return the same results. However, we cannot use [VimEditorGroup.getEditors] because the change might have
* come from a remote guest and there might not be an open local editor.
*/
private fun getAnyEditorForDocument(doc: Document) =
EditorFactory.getInstance().getEditors(doc).firstOrNull()?.let { IjVimEditor(it) }
private fun getAnEditor(doc: Document): Editor? {
val editors = localEditors(doc)
return if (editors.size > 0) {
editors[0]
} else {
null
}
}
}
class VimBookmarksListener(private val myProject: Project) : BookmarksListener {

View File

@@ -8,11 +8,9 @@
package com.maddyhome.idea.vim.group
import com.intellij.openapi.components.Service
import org.apache.commons.codec.binary.Base64
import org.jdom.Element
@Service
internal class XMLGroup {
/**
* 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.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.RangeMarker
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 java.awt.datatransfer.DataFlavor
@Service
internal class PutGroup : VimPutBase() {
override fun getProviderForPasteViaIde(

View File

@@ -20,17 +20,17 @@ import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.hasVisualSelection
import com.maddyhome.idea.vim.helper.inInsertMode
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.vimDisabled
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.listener.VimListenerManager
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inCommandLineMode
import com.maddyhome.idea.vim.state.mode.inNormalMode
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.returnTo
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.isIdeaRefactorModeKeep
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect
@@ -55,7 +55,9 @@ internal object IdeaSelectionControl {
selectionSource: VimListenerManager.SelectionSource = VimListenerManager.SelectionSource.OTHER,
) {
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")
@@ -69,11 +71,6 @@ internal object IdeaSelectionControl {
}
if (editor.selectionModel.hasSelection(true)) {
if (editor.vim.inCommandLineMode && editor.vim.mode.returnTo().hasVisualSelection) {
logger.trace { "Modifying selection while in Command-line mode, most likely incsearch" }
return@singleTask
}
if (dontChangeMode(editor)) {
IdeaRefactorModeHelper.correctSelection(editor)
logger.trace { "Selection corrected for refactoring" }
@@ -82,7 +79,7 @@ internal object IdeaSelectionControl {
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))
} else {
@@ -124,7 +121,7 @@ internal object IdeaSelectionControl {
is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType)
is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType)
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
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.maddyhome.idea.vim.api.getLineEndForOffset
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.moveToInlayAwareOffset
import com.maddyhome.idea.vim.helper.vimSelectionStart
import com.maddyhome.idea.vim.newapi.IjVimEditor
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) {
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.VimVisualMotionGroupBase
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.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
/**
* @author Alex Plate

View File

@@ -27,6 +27,7 @@ import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.removeUserData
import com.maddyhome.idea.vim.KeyHandler
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.key
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.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.mode
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
@@ -337,9 +339,8 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
val enterKey = key(key)
val context = dataContext?.vim ?: injector.executionContextManager.getEditorExecutionContext(editor.vim)
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState)
val context = injector.executionContextManager.onEditor(editor.vim, dataContext?.vim)
KeyHandler.getInstance().handleKey(editor.vim, enterKey, context)
}
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
@@ -361,4 +362,4 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): 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
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Caret
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.globalOptions
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.vim
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
import com.maddyhome.idea.vim.options.helpers.GuiCursorMode
import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper
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.mode
import org.jetbrains.annotations.TestOnly
import java.awt.Color
@@ -89,12 +85,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// 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() {
@@ -143,31 +134,3 @@ private object AttributesCache {
@TestOnly
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

@@ -1,34 +1,29 @@
/*
* Copyright 2003-2024 The IdeaVim authors
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.ui.ex
package com.maddyhome.idea.vim.helper
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.maddyhome.idea.vim.action.change.Extension
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCommandLine
import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
public class ExEntryPanelService : VimCommandLineService {
public override fun getActiveCommandLine(): VimCommandLine? {
return ExEntryPanel.instance
}
@Service
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
if (vimEditor.vimStateMachine.isDotRepeatInProgress) {
val input = Extension.consumeString()
@@ -57,26 +52,27 @@ public class ExEntryPanelService : VimCommandLineService {
} else {
var text: String? = null
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
val commandLine = injector.commandLine.create(vimEditor, context, prompt.ifEmpty { " " }, "", 1)
val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts()
exEntryPanel.activate(editor, injector.executionContextManager.onEditor(editor.vim).ij, prompt.ifEmpty { " " }, "", 1)
ModalEntry.activate(editor.vim) { key: KeyStroke ->
return@activate when {
key.isCloseKeyStroke() -> {
commandLine.deactivate(true)
exEntryPanel.deactivate(true)
false
}
key.keyCode == KeyEvent.VK_ENTER -> {
text = commandLine.text
commandLine.deactivate(true)
text = exEntryPanel.text
exEntryPanel.deactivate(true)
false
}
finishOn != null && key.keyChar == finishOn -> {
commandLine.handleKey(key)
text = commandLine.text
commandLine.deactivate(true)
exEntryPanel.handleKey(key)
text = exEntryPanel.text
exEntryPanel.deactivate(true)
false
}
else -> {
commandLine.handleKey(key)
exEntryPanel.handleKey(key)
true
}
}
@@ -87,10 +83,4 @@ public class ExEntryPanelService : VimCommandLineService {
return text
}
}
public override fun create(editor: VimEditor, context: ExecutionContext, label: String, initText: String, count: Int): VimCommandLine {
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, label, initText, count)
return panel
}
}

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.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.mode
internal val Mode.hasVisualSelection
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.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(
private val editor: Editor,
private val editorContext: DataContext,

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