mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-17 16:31:45 +02:00
Compare commits
15 Commits
customized
...
customized
Author | SHA1 | Date | |
---|---|---|---|
4ca05ba6df
|
|||
114ac40990
|
|||
2829109eb7
|
|||
f1323139b4
|
|||
df4aa59310
|
|||
7a3bb5b2d7
|
|||
c4b05957fc
|
|||
db83b89931
|
|||
ce27c3e5ba
|
|||
31358bc983
|
|||
f7e1c9c837
|
|||
c46109caa3
|
|||
9f7ca83306
|
|||
b5761f20d2
|
|||
305c6d2bf9
|
1
.github/workflows/checkNewPlugins.yml
vendored
1
.github/workflows/checkNewPlugins.yml
vendored
@@ -14,7 +14,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- name: Fetch origin repo
|
||||
|
1
.github/workflows/closeYoutrackOnCommit.yml
vendored
1
.github/workflows/closeYoutrackOnCommit.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
1
.github/workflows/integrationsTest.yml
vendored
1
.github/workflows/integrationsTest.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
1
.github/workflows/kover.yml
vendored
1
.github/workflows/kover.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
2
.github/workflows/mergeDependabotPR.yml
vendored
2
.github/workflows/mergeDependabotPR.yml
vendored
@@ -8,7 +8,7 @@ permissions:
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor == 'dependabot[bot]' && github.repository == 'JetBrains/ideavim' }}
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Auto-merge Dependabot PR
|
||||
run: gh pr merge --auto --rebase "$PR_URL"
|
||||
|
2
.github/workflows/mergePr.yml
vendored
2
.github/workflows/mergePr.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
|
||||
if: github.event.pull_request.merged == true && github.repository == 'JetBrains/ideavim'
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
25
.github/workflows/runUiTests.yml
vendored
25
.github/workflows/runUiTests.yml
vendored
@@ -5,23 +5,20 @@ on:
|
||||
- cron: '0 12 * * *'
|
||||
jobs:
|
||||
build-for-ui-test-mac-os:
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v2.1.0
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 11
|
||||
- name: Setup FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
uses: FedericoCarboni/setup-ffmpeg@v1
|
||||
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
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build Plugin
|
||||
run: gradle :buildPlugin
|
||||
- name: Run Idea
|
||||
@@ -29,7 +26,7 @@ jobs:
|
||||
mkdir -p build/reports
|
||||
gradle :runIdeForUiTests > build/reports/idea.log &
|
||||
- name: Wait for Idea started
|
||||
uses: jtalk/url-health-check-action@v3
|
||||
uses: jtalk/url-health-check-action@1.5
|
||||
with:
|
||||
url: http://127.0.0.1:8082
|
||||
max-attempts: 20
|
||||
@@ -37,19 +34,15 @@ jobs:
|
||||
- name: Tests
|
||||
run: gradle :testUi
|
||||
- name: Move video
|
||||
if: always()
|
||||
if: ${{ failure() }}
|
||||
run: mv video build/reports
|
||||
- name: Move sandbox logs
|
||||
if: always()
|
||||
run: mv build/idea-sandbox/system/log sandbox-idea-log
|
||||
- name: Save report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Save fails report
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ui-test-fails-report-mac
|
||||
path: |
|
||||
build/reports
|
||||
sandbox-idea-log
|
||||
# build-for-ui-test-linux:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
|
1
.github/workflows/syncDoc.yml
vendored
1
.github/workflows/syncDoc.yml
vendored
@@ -14,7 +14,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- name: Fetch origin repo
|
||||
|
1
.github/workflows/updateAffectedRate.yml
vendored
1
.github/workflows/updateAffectedRate.yml
vendored
@@ -14,7 +14,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- name: Fetch origin repo
|
||||
|
1
.github/workflows/updateAuthors.yml
vendored
1
.github/workflows/updateAuthors.yml
vendored
@@ -15,7 +15,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
1
.github/workflows/updateChangelog.yml
vendored
1
.github/workflows/updateChangelog.yml
vendored
@@ -15,7 +15,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
1
.github/workflows/updateFormatting.yml
vendored
1
.github/workflows/updateFormatting.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,9 +23,6 @@
|
||||
|
||||
# Generated by gradle task "generateGrammarSource"
|
||||
src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
|
||||
# Generated JSONs for lazy classloading
|
||||
/vim-engine/src/main/resources/ksp-generated
|
||||
/src/main/resources/ksp-generated
|
||||
|
||||
# Created by github automation
|
||||
settings.xml
|
||||
|
2
.idea/copyright/IdeaVim.xml
generated
2
.idea/copyright/IdeaVim.xml
generated
@@ -1,6 +1,6 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright 2003-&#36;today.year 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." />
|
||||
<option name="notice" value="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." />
|
||||
<option name="myName" value="IdeaVim" />
|
||||
</copyright>
|
||||
</component>
|
18
.teamcity/_Self/Constants.kt
vendored
18
.teamcity/_Self/Constants.kt
vendored
@@ -5,13 +5,15 @@ object Constants {
|
||||
const val EAP_CHANNEL = "eap"
|
||||
const val DEV_CHANNEL = "Dev"
|
||||
|
||||
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 VERSION = "2.4.0"
|
||||
|
||||
const val RELEASE_DEV = "2023.3.2"
|
||||
const val RELEASE_EAP = "2023.3.2"
|
||||
const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val RELEASE = "2023.1.2"
|
||||
|
||||
const val RELEASE_DEV = "2023.1.2"
|
||||
const val RELEASE_EAP = "2023.1.2"
|
||||
}
|
||||
|
6
.teamcity/_Self/Project.kt
vendored
6
.teamcity/_Self/Project.kt
vendored
@@ -24,7 +24,6 @@ object Project : Project({
|
||||
|
||||
// Active tests
|
||||
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||
buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
|
||||
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||
|
||||
buildType(PropertyBased)
|
||||
@@ -39,11 +38,6 @@ object Project : Project({
|
||||
|
||||
// Common build type for all configurations
|
||||
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
|
||||
artifactRules = """
|
||||
+:build/reports => build/reports
|
||||
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
|
||||
""".trimIndent()
|
||||
|
||||
init()
|
||||
|
||||
requirements {
|
||||
|
1
.teamcity/_Self/buildTypes/ReleasePlugin.kt
vendored
1
.teamcity/_Self/buildTypes/ReleasePlugin.kt
vendored
@@ -118,7 +118,6 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
|
||||
then
|
||||
git checkout release
|
||||
echo checkout release branch
|
||||
git branch --set-upstream-to=origin/release release
|
||||
git push --tags
|
||||
git push origin --force
|
||||
fi
|
||||
|
2
.teamcity/_Self/subprojects/OldTests.kt
vendored
2
.teamcity/_Self/subprojects/OldTests.kt
vendored
@@ -20,6 +20,4 @@ object OldTests : Project({
|
||||
buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
|
||||
})
|
||||
|
@@ -1,29 +0,0 @@
|
||||
package patches.buildTypes
|
||||
|
||||
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.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.
|
||||
To apply the patch, change the buildType with id = 'IdeaVimTests_Latest_EAP'
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType(RelativeId("IdeaVimTests_Latest_EAP")) {
|
||||
expectSteps {
|
||||
gradle {
|
||||
tasks = "clean test"
|
||||
buildFile = ""
|
||||
enableStacktrace = true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
update<GradleBuildStep>(0) {
|
||||
clearConditions()
|
||||
jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
|
||||
}
|
||||
}
|
||||
}
|
20
.teamcity/patches/buildTypes/ReleaseMinor.kts
vendored
20
.teamcity/patches/buildTypes/ReleaseMinor.kts
vendored
@@ -1,20 +0,0 @@
|
||||
package patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with id = 'ReleaseMinor'
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType(RelativeId("ReleaseMinor")) {
|
||||
params {
|
||||
expect {
|
||||
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:3cd3e867-282c-451f-b958-bc95d56a8450", display = ParameterDisplay.HIDDEN)
|
||||
}
|
||||
update {
|
||||
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:7bc0eb3a-b86a-4ebd-b622-d4ef12d7e1d3", display = ParameterDisplay.HIDDEN)
|
||||
}
|
||||
}
|
||||
}
|
17
.teamcity/patches/projects/_Self.kts
vendored
17
.teamcity/patches/projects/_Self.kts
vendored
@@ -1,17 +0,0 @@
|
||||
package patches.projects
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
||||
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 root project
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeProject(DslContext.projectId) {
|
||||
check(description == "Vim engine for IDEs based on the IntelliJ platform") {
|
||||
"Unexpected description: '$description'"
|
||||
}
|
||||
description = "Vim engine for JetBrains IDEs"
|
||||
}
|
@@ -483,14 +483,6 @@ Contributors:
|
||||
[![icon][github]](https://github.com/ludwig-jb)
|
||||
|
||||
ludwig-jb
|
||||
* [![icon][mail]](mailto:pvydmuch@gmail.com)
|
||||
[![icon][github]](https://github.com/pWydmuch)
|
||||
|
||||
pWydmuch
|
||||
* [![icon][mail]](mailto:leonid989@gmail.com)
|
||||
[![icon][github]](https://github.com/Infonautica)
|
||||
|
||||
Leonid Danilov
|
||||
|
||||
Previous contributors:
|
||||
|
||||
|
62
CHANGES.md
62
CHANGES.md
@@ -25,73 +25,11 @@ usual beta standards.
|
||||
|
||||
## To Be Released
|
||||
|
||||
### Fixes:
|
||||
* [VIM-3130](https://youtrack.jetbrains.com/issue/VIM-3130) Change the build version to 2023.1.2
|
||||
* [VIM-3168](https://youtrack.jetbrains.com/issue/VIM-3168) Do not switch to block caret after enter if the IdeaVim is disabled
|
||||
* [VIM-3165](https://youtrack.jetbrains.com/issue/VIM-3165) Do not process enter key as IdeaVim shortcut if it's not an actual keypress
|
||||
* [VIM-3159](https://youtrack.jetbrains.com/issue/VIM-3159) Shift-enter now works in normal mode again
|
||||
* [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
|
||||
* [VIM-3195](https://youtrack.jetbrains.com/issue/VIM-3195) Fix escape in injected editor
|
||||
* [VIM-3190](https://youtrack.jetbrains.com/issue/VIM-3190) Do not use octopus handler if the enter key is used with modifiers like shift or control
|
||||
* [VIM-3203](https://youtrack.jetbrains.com/issue/VIM-3203) Split action not works in normal mode
|
||||
* [VIM-3184](https://youtrack.jetbrains.com/issue/VIM-3184) Revert "VIM-3184: Temporally disable new handlers for the thin client"
|
||||
* [VIM-3186](https://youtrack.jetbrains.com/issue/VIM-3186) Do not multiply the enter action by the amount of carets
|
||||
* [VIM-3177](https://youtrack.jetbrains.com/issue/VIM-3177) Formatting of commit message works again
|
||||
* [VIM-1611](https://youtrack.jetbrains.com/issue/VIM-1611) actions related to resolving conflicts doesn't seem to work
|
||||
* [VIM-3204](https://youtrack.jetbrains.com/issue/VIM-3204) Add checker that verifies the configuratin of the keymap
|
||||
* [VIM-3084](https://youtrack.jetbrains.com/issue/VIM-3084) Double update for the status bar icon
|
||||
* [VIM-3176](https://youtrack.jetbrains.com/issue/VIM-3176) Reselecting visual selection after pasting above it select wrong lines
|
||||
* [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape
|
||||
* [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode
|
||||
* [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction
|
||||
|
||||
### Merged PRs:
|
||||
* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
|
||||
* [772](https://github.com/JetBrains/ideavim/pull/772) by [chylex](https://github.com/chylex): Prevent code completion popup from appearing after running a macro
|
||||
* [787](https://github.com/JetBrains/ideavim/pull/787) by [Leonid Danilov](https://github.com/Infonautica): Added "Which-Key" to Plugins
|
||||
* [778](https://github.com/JetBrains/ideavim/pull/778) by [lippfi](https://github.com/lippfi): Showmode
|
||||
* [788](https://github.com/JetBrains/ideavim/pull/788) by [Matt Ellis](https://github.com/citizenmatt): Refactor VimOptionGroupBase
|
||||
|
||||
## 2.7.0, 2023-11-07
|
||||
|
||||
### Fixes:
|
||||
* [VIM-2933](https://youtrack.jetbrains.com/issue/VIM-2933) Reloading/sourcing .ideavimrc does not initialize new plugins
|
||||
* [VIM-3138](https://youtrack.jetbrains.com/issue/VIM-3138) Do not try to register disposer if the caret is already disposed
|
||||
|
||||
### Merged PRs:
|
||||
* [734](https://github.com/JetBrains/ideavim/pull/734) by [Matt Ellis](https://github.com/citizenmatt): Support `~/` on Windows
|
||||
* [736](https://github.com/JetBrains/ideavim/pull/736) by [chylex](https://github.com/chylex): Fix(VIM-2933): Reloading/sourcing .ideavimrc does not initialize new plugins
|
||||
|
||||
## 2.6.3, 2023-10-30
|
||||
|
||||
### Changes:
|
||||
- 2.6.0 and 2.6.1 releases are broken. Version 2.6.3 reverts IdeaVim plugin to the working state as for 2.5.1.
|
||||
|
||||
## 2.6.0, 2023-10-27
|
||||
|
||||
This version of IdeaVim contains a lot of issues. Version 2.6.3 reverts these changes.
|
||||
|
||||
### Features:
|
||||
|
||||
* `ShowHoverInfo` action can be used in mappings to open a tooltip that is shown by
|
||||
mouse hovering | [VIM-2106](https://youtrack.jetbrains.com/issue/VIM-2106)
|
||||
* `has` Vim Script function supports the most common OS checks: win32, win64, linux, mac, macunix, osx, osxdarwin, bsd, sun, unix
|
||||
* See https://github.com/JetBrains/ideavim#vim-script for details about Vim Script
|
||||
|
||||
### Fixes:
|
||||
* [VIM-3060](https://youtrack.jetbrains.com/issue/VIM-3060) Clipboard interaction stopped working
|
||||
* [VIM-3095](https://youtrack.jetbrains.com/issue/VIM-3095) Fix missing ellipsis digraph
|
||||
* [VIM-2562](https://youtrack.jetbrains.com/issue/VIM-2562) Fix hang with multi-width chars in command line
|
||||
* [VIM-696](https://youtrack.jetbrains.com/issue/VIM-696) Vim selection issue after undo
|
||||
* [VIM-1639](https://youtrack.jetbrains.com/issue/VIM-1639) Ctrl-o and Ctrl-i jumping in files of different projects
|
||||
|
||||
### Merged PRs:
|
||||
* [697](https://github.com/JetBrains/ideavim/pull/697) by [Matt Ellis](https://github.com/citizenmatt): Support per-window "global" values for local-to-window options
|
||||
* [717](https://github.com/JetBrains/ideavim/pull/717) by [Matt Ellis](https://github.com/citizenmatt): Fix(VIM-2562): Fix hang with multi-width chars in command line
|
||||
* [732](https://github.com/JetBrains/ideavim/pull/732) by [pWydmuch](https://github.com/pWydmuch): Fix md links in doc
|
||||
* [733](https://github.com/JetBrains/ideavim/pull/733) by [Matt Ellis](https://github.com/citizenmatt): Add support for ShowHoverInfo action to 2023.1 and 2023.2
|
||||
* [729](https://github.com/JetBrains/ideavim/pull/729) by [chylex](https://github.com/chylex): Add operating system type to `has()` function
|
||||
* [726](https://github.com/JetBrains/ideavim/pull/726) by [Matt Ellis](https://github.com/citizenmatt): Fix range for fall back comment mode
|
||||
|
||||
## 2.5.0, 2023-09-01
|
||||
|
||||
|
@@ -255,7 +255,8 @@ Ex commands or via `:map` command mappings:
|
||||
##### Some popular actions:
|
||||
|
||||
```
|
||||
ShowHoverInfo - Quick Documentation and Error Description
|
||||
QuickJavaDoc - Quick Documentation (not only for java, all languages)
|
||||
ShowErrorDescription - Show description of the error under the caret (cursor hovering)
|
||||
QuickImplementations - Quick Definition
|
||||
```
|
||||
|
||||
@@ -324,7 +325,7 @@ IdeaVim tips and tricks
|
||||
- Use the power of IJ and Vim:
|
||||
- `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
|
||||
- Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
|
||||
- Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
|
||||
- Sync IJ bookmarks and Vim marks: `set ideamarks`
|
||||
- Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
|
||||
|
||||
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
|
||||
|
@@ -8,11 +8,8 @@
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.serialization") version "1.8.21"
|
||||
}
|
||||
|
||||
val kotlinxSerializationVersion: String by project
|
||||
|
||||
group = "com.intellij"
|
||||
version = "SNAPSHOT"
|
||||
|
||||
@@ -21,10 +18,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.16")
|
||||
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")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
||||
}
|
||||
}
|
||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
|
||||
}
|
@@ -1,55 +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 com.intellij.vim.annotations
|
||||
|
||||
// TODO support numpad keys parsing, see :keycodes
|
||||
/**
|
||||
* It's not necessary a Vim command
|
||||
* This annotation may be used for:
|
||||
* - commands
|
||||
* - motions
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class CommandOrMotion(val keys: Array<String>, vararg val modes: Mode)
|
||||
|
||||
annotation class TextObject(val keys: String)
|
||||
|
||||
|
||||
enum class Mode(val abbrev: Char) {
|
||||
/**
|
||||
* Indicates this key mapping applies to Normal mode
|
||||
*/
|
||||
NORMAL('N'),
|
||||
|
||||
/**
|
||||
* Indicates this key mapping applies to Visual mode
|
||||
*/
|
||||
VISUAL('X'),
|
||||
|
||||
/**
|
||||
* Indicates this key mapping applies to Select mode
|
||||
*/
|
||||
SELECT('S'),
|
||||
|
||||
/**
|
||||
* Indicates this key mapping applies to Operator Pending mode
|
||||
*/
|
||||
OP_PENDING('O'),
|
||||
|
||||
/**
|
||||
* Indicates this key mapping applies to Insert or Replace modes
|
||||
*/
|
||||
INSERT('I'),
|
||||
|
||||
/**
|
||||
* Indicates this key mapping applies to Command Line mode
|
||||
*/
|
||||
CMD_LINE('C'),
|
||||
}
|
@@ -1,14 +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 com.intellij.vim.processors
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CommandBean(val keys: String, val `class`: String, val modes: String)
|
@@ -1,62 +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 com.intellij.vim.processors
|
||||
|
||||
import com.google.devtools.ksp.KspExperimental
|
||||
import com.google.devtools.ksp.getAnnotationsByType
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSFile
|
||||
import com.google.devtools.ksp.symbol.KSVisitorVoid
|
||||
import com.intellij.vim.annotations.CommandOrMotion
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
class CommandOrMotionProcessor(private val environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||
private val visitor = CommandOrMotionVisitor()
|
||||
private val commands = mutableListOf<CommandBean>()
|
||||
|
||||
private val json = Json { prettyPrint = true }
|
||||
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
|
||||
|
||||
val generatedDirPath = Path(environment.options["generated_directory"]!!)
|
||||
Files.createDirectories(generatedDirPath)
|
||||
|
||||
val filePath = generatedDirPath.resolve(environment.options["commands_file"]!!)
|
||||
val fileContent = json.encodeToString(commands)
|
||||
filePath.writeText(fileContent)
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private inner class CommandOrMotionVisitor : KSVisitorVoid() {
|
||||
@OptIn(KspExperimental::class)
|
||||
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
|
||||
val commandAnnotation = classDeclaration.getAnnotationsByType(CommandOrMotion::class).firstOrNull() ?: return
|
||||
for (key in commandAnnotation.keys) {
|
||||
commands.add(
|
||||
CommandBean(key, classDeclaration.qualifiedName!!.asString(), commandAnnotation.modes.map { it.abbrev }.joinToString(separator = ""))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFile(file: KSFile, data: Unit) {
|
||||
file.declarations.forEach { it.accept(this, Unit) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -20,7 +20,6 @@ import com.google.devtools.ksp.symbol.KSVisitorVoid
|
||||
import com.intellij.vim.annotations.ExCommand
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
@@ -32,11 +31,7 @@ class ExCommandProcessor(private val environment: SymbolProcessorEnvironment): S
|
||||
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
|
||||
|
||||
val generatedDirPath = Path(environment.options["generated_directory"]!!)
|
||||
Files.createDirectories(generatedDirPath)
|
||||
|
||||
val filePath = generatedDirPath.resolve(environment.options["ex_commands_file"]!!)
|
||||
val filePath = Path(environment.options["generated_directory"]!!, environment.options["ex_commands_file"]!!)
|
||||
val fileContent = json.encodeToString(commandToClass)
|
||||
filePath.writeText(fileContent)
|
||||
|
||||
|
@@ -20,7 +20,6 @@ import com.google.devtools.ksp.symbol.KSVisitorVoid
|
||||
import com.intellij.vim.annotations.VimscriptFunction
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
@@ -32,11 +31,7 @@ class VimscriptFunctionProcessor(private val environment: SymbolProcessorEnviron
|
||||
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
|
||||
|
||||
val generatedDirPath = Path(environment.options["generated_directory"]!!)
|
||||
Files.createDirectories(generatedDirPath)
|
||||
|
||||
val filePath = generatedDirPath.resolve(environment.options["vimscript_functions_file"]!!)
|
||||
val filePath = Path(environment.options["generated_directory"]!!, environment.options["vimscript_functions_file"]!!)
|
||||
val fileContent = json.encodeToString(nameToClass)
|
||||
filePath.writeText(fileContent)
|
||||
|
||||
|
@@ -1,20 +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 com.intellij.vim.providers
|
||||
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
import com.intellij.vim.processors.CommandOrMotionProcessor
|
||||
|
||||
class CommandOrMotionProcessorProvider : SymbolProcessorProvider {
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||
return CommandOrMotionProcessor(environment)
|
||||
}
|
||||
}
|
@@ -1,3 +1,2 @@
|
||||
com.intellij.vim.providers.CommandOrMotionProcessorProvider
|
||||
com.intellij.vim.providers.ExCommandProcessorProvider
|
||||
com.intellij.vim.providers.VimscriptFunctionProcessorProvider
|
||||
com.intellij.vim.providers.ExCommandProcessorProvider
|
@@ -49,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.8.0.202311291450-r")
|
||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
|
||||
classpath("org.kohsuke:github-api:1.305")
|
||||
|
||||
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")
|
||||
classpath("io.ktor:ktor-client-core:2.3.4")
|
||||
classpath("io.ktor:ktor-client-cio:2.3.4")
|
||||
classpath("io.ktor:ktor-client-auth:2.3.4")
|
||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.4")
|
||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.4")
|
||||
|
||||
// This comes from the changelog plugin
|
||||
// classpath("org.jetbrains:markdown:0.3.1")
|
||||
@@ -69,7 +69,7 @@ plugins {
|
||||
kotlin("jvm") version "1.8.21"
|
||||
application
|
||||
|
||||
id("org.jetbrains.intellij") version "1.16.1"
|
||||
id("org.jetbrains.intellij") version "1.15.0"
|
||||
id("org.jetbrains.changelog") version "2.2.0"
|
||||
|
||||
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
|
||||
@@ -82,10 +82,9 @@ plugins {
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
|
||||
arg("generated_directory", "$projectDir/src/main/resources")
|
||||
arg("vimscript_functions_file", "intellij_vimscript_functions.json")
|
||||
arg("ex_commands_file", "intellij_ex_commands.json")
|
||||
arg("commands_file", "intellij_commands.json")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
@@ -116,7 +115,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||
compileOnly("org.jetbrains:annotations:24.0.1")
|
||||
|
||||
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
|
||||
testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
|
||||
@@ -126,24 +125,24 @@ dependencies {
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
||||
|
||||
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
|
||||
|
||||
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
|
||||
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
|
||||
testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
|
||||
testImplementation("com.automation-remarks:video-recorder-junit:2.0")
|
||||
runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
|
||||
antlr("org.antlr:antlr4:$antlrVersion")
|
||||
|
||||
api(project(":vim-engine"))
|
||||
|
||||
ksp(project(":annotation-processors"))
|
||||
implementation(project(":annotation-processors"))
|
||||
compileOnly(project(":annotation-processors"))
|
||||
|
||||
testApi("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
testApi("com.squareup.okhttp3:okhttp:4.11.0")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0")
|
||||
}
|
||||
|
||||
configurations {
|
||||
@@ -184,14 +183,6 @@ tasks {
|
||||
include("**/*test.class")
|
||||
include("**/*Tests.class")
|
||||
exclude("**/ParserTest.class")
|
||||
|
||||
// Set teamcity env variable locally to run additional tests for leaks.
|
||||
// By default, this test runs on TC only, but this test doesn't take a lot of time,
|
||||
// so we can turn it on for local development
|
||||
if (environment["TEAMCITY_VERSION"] == null) {
|
||||
println("Set env TEAMCITY_VERSION to X")
|
||||
environment("TEAMCITY_VERSION" to "X")
|
||||
}
|
||||
}
|
||||
|
||||
val testWithNeovim by getting(Test::class) {
|
||||
@@ -302,7 +293,6 @@ tasks {
|
||||
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
|
||||
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
|
||||
systemProperty("jb.consents.confirmation.enabled", "false")
|
||||
systemProperty("ide.show.tips.on.startup.default.value", "false")
|
||||
}
|
||||
|
||||
runPluginVerifier {
|
||||
@@ -353,6 +343,8 @@ tasks {
|
||||
val pluginVersion = version
|
||||
// Don't forget to update plugin.xml
|
||||
patchPluginXml {
|
||||
sinceBuild.set("231.7515.13")
|
||||
|
||||
// Get the latest available change notes from the changelog file
|
||||
changeNotes.set(
|
||||
provider {
|
||||
@@ -531,12 +523,10 @@ tasks.register("releaseActions") {
|
||||
if (tickets.isNotEmpty()) {
|
||||
println("Updating statuses for tickets: $tickets")
|
||||
setYoutrackStatus(tickets, "Fixed")
|
||||
println("Checking if version $version exists...")
|
||||
val versionId = getVersionIdByName(version.toString())
|
||||
if (versionId == null) {
|
||||
if (getVersionIdByName(version.toString()) != null) {
|
||||
addReleaseToYoutrack(version.toString())
|
||||
} else {
|
||||
println("Version $version already exists in YouTrack. Version id: $versionId")
|
||||
println("Version $version is already exists in YouTrack")
|
||||
}
|
||||
setYoutrackFixVersion(tickets, version.toString())
|
||||
} else {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
Welcome to the IdeaVim wiki!
|
||||
|
||||
- List of IdeaVim plugins: [plugins](IdeaVim%20Plugins.md)
|
||||
- Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples.md)
|
||||
- List of "set" commands: ["set" commands](set-commands.md)
|
||||
- Docs about "select" mode in vim: [select mode](Select-mode.md)
|
||||
- List of IdeaVim plugins: [[plugins|IdeaVim Plugins]]
|
||||
- Examples of `ideajoin` option (also known as "smart join"): [["ideajoin" examples|ideajoin-examples]]
|
||||
- List of "set" commands: [["set" commands|set-commands]]
|
||||
- Docs about "select" mode in vim: [[select mode|Select-mode]]
|
||||
|
@@ -77,7 +77,7 @@ Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
|
||||
|
||||
### Instructions
|
||||
|
||||
[See here](NERDTree-support.md).
|
||||
[[See here|NERDTree-support]].
|
||||
|
||||
</details>
|
||||
|
||||
@@ -396,19 +396,3 @@ Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
|
||||
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary><h2>Which-Key</h2></summary>
|
||||
|
||||
Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
|
||||
|
||||
### Setup:
|
||||
- Install [Which-Key](https://plugins.jetbrains.com/plugin/15976-which-key) plugin.
|
||||
- Add the following command to `~/.ideavimrc`: `set which-key`
|
||||
|
||||
### Instructions
|
||||
|
||||
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
|
||||
|
||||
</details>
|
||||
|
@@ -3,11 +3,6 @@ Put `set ideajoin` to your `~/.ideavimrc` to enable this functionality.
|
||||
|
||||
Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together.
|
||||
|
||||
:warning: This feature is language-specific. This means that the IDE should implement this feature for a particular
|
||||
language in order for the IDE to work as described below. If any of the examples provided below don't match your case,
|
||||
please file an issue in the project related to your IDE: https://youtrack.jetbrains.com/.
|
||||
Here is a list of known requests: https://youtrack.jetbrains.com/issues?q=links:VIM-3214.
|
||||
|
||||
* Automatic join concatenated lines:
|
||||
|
||||
```
|
||||
|
@@ -8,26 +8,21 @@
|
||||
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
|
||||
ideaVersion=2023.3.2
|
||||
ideaVersion=2023.2
|
||||
downloadIdeaSources=true
|
||||
instrumentPluginCode=true
|
||||
version=chylex-26
|
||||
version=chylex-19
|
||||
javaVersion=17
|
||||
remoteRobotVersion=0.11.21
|
||||
remoteRobotVersion=0.11.17
|
||||
antlrVersion=4.10.1
|
||||
|
||||
kotlin.incremental.useClasspathSnapshot=false
|
||||
|
||||
# Please don't forget to update kotlin version in buildscript section
|
||||
# Also update kotlinxSerializationVersion version
|
||||
kotlinVersion=1.8.21
|
||||
publishToken=token
|
||||
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.5.1
|
||||
|
||||
slackUrl=
|
||||
youtrackToken=
|
||||
|
||||
|
@@ -20,17 +20,17 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.10")
|
||||
|
||||
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("io.ktor:ktor-client-core:2.3.4")
|
||||
implementation("io.ktor:ktor-client-cio:2.3.4")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.4")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.4")
|
||||
implementation("io.ktor:ktor-client-auth:2.3.4")
|
||||
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.8.0.202311291450-r")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
|
||||
implementation("com.vdurmont:semver4j:3.1.0")
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
|
||||
println("HI!")
|
||||
val projectDir = args[0]
|
||||
println("Working directory: $projectDir")
|
||||
val (lastVersion, objectId) = getVersion(projectDir, ReleaseType.STABLE_NO_PATCH)
|
||||
val (lastVersion, objectId) = getVersion(projectDir, onlyStable = true)
|
||||
println("Last version: $lastVersion, hash: ${objectId.name}")
|
||||
|
||||
val branch = withRepo(projectDir) { it.branch }
|
||||
|
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
|
||||
println("HI!")
|
||||
val projectDir = args[0]
|
||||
println("Working directory: $projectDir")
|
||||
val (lastVersion, _) = getVersion(projectDir, ReleaseType.ANY)
|
||||
val (lastVersion, _) = getVersion(projectDir, onlyStable = false)
|
||||
|
||||
val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
|
||||
lastVersion.nextMinor().withSuffix("eap.1").value
|
||||
|
@@ -14,7 +14,7 @@ fun main(args: Array<String>) {
|
||||
val releaseType = args[1]
|
||||
println("Working directory: $projectDir")
|
||||
println("Release type: $releaseType")
|
||||
val (lastVersion, _) = getVersion(projectDir, ReleaseType.ONLY_STABLE)
|
||||
val (lastVersion, _) = getVersion(projectDir, onlyStable = true)
|
||||
|
||||
val nextVersion = when (releaseType) {
|
||||
"major" -> lastVersion.nextMajor()
|
||||
|
@@ -9,7 +9,6 @@
|
||||
package scripts.release
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import org.eclipse.jgit.api.CreateBranchCommand
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.eclipse.jgit.lib.Repository
|
||||
@@ -58,13 +57,7 @@ internal fun checkBranch(rootDir: String, releaseType: String) {
|
||||
}
|
||||
}
|
||||
|
||||
enum class ReleaseType {
|
||||
ANY,
|
||||
ONLY_STABLE,
|
||||
STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
|
||||
}
|
||||
|
||||
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
|
||||
internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, ObjectId> {
|
||||
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
|
||||
val git = Git(repository)
|
||||
println(git.log().call().first())
|
||||
@@ -81,24 +74,19 @@ internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semv
|
||||
}
|
||||
.sortedBy { it.first }
|
||||
|
||||
val version = when (releaseType) {
|
||||
ReleaseType.ANY -> versions.last()
|
||||
ReleaseType.ONLY_STABLE -> versions.last { it.first.isStable }
|
||||
ReleaseType.STABLE_NO_PATCH -> versions.last { it.first.isStable && it.first.patch == 0 }
|
||||
val version = if (onlyStable) {
|
||||
versions.last { it.first.isStable }
|
||||
} else {
|
||||
versions.last()
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
internal fun Git.checkoutBranch(name: String) {
|
||||
println("Checking out $name")
|
||||
val shouldCreateBranch = this.branchList().call().any { it.name == "refs/heads/$name" }.not()
|
||||
val checkoutCommand = checkout()
|
||||
checkout()
|
||||
.setCreateBranch(shouldCreateBranch)
|
||||
.setName(name)
|
||||
if (shouldCreateBranch) {
|
||||
// Without starting point the branch will be created on HEAD.
|
||||
checkoutCommand.setStartPoint("origin/$name").setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
|
||||
}
|
||||
checkoutCommand.call()
|
||||
.call()
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ package com.maddyhome.idea.vim
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManagerListener
|
||||
import com.intellij.openapi.startup.ProjectActivity
|
||||
import com.intellij.openapi.startup.StartupActivity
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.localEditors
|
||||
@@ -20,11 +20,16 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
/**
|
||||
* @author Alex Plate
|
||||
*/
|
||||
internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
|
||||
// This service should be migrated to ProjectActivity. But we should cariful because simple replacement
|
||||
// leads to deadlock in tests. I'm not sure about the exact reasons, but "invokeAndWait" inside "initialize" function
|
||||
// causes this deadlock. Good new: it's easy reproducible in tests.
|
||||
// Previous migration: fc7efd5484a13b40ba9bf86a1d5429e215d973f3
|
||||
// Revert: 24dd84b31cffb99eb6114524859a46d02717d33f
|
||||
internal class PluginStartup : StartupActivity.DumbAware/*, LightEditCompatible*/ {
|
||||
|
||||
private var firstInitializationOccurred = false
|
||||
|
||||
override suspend fun execute(project: Project) {
|
||||
override fun runActivity(project: Project) {
|
||||
if (firstInitializationOccurred) return
|
||||
firstInitializationOccurred = true
|
||||
|
||||
|
79
src/main/java/com/maddyhome/idea/vim/RegisterActions.java
Normal file
79
src/main/java/com/maddyhome/idea/vim/RegisterActions.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.maddyhome.idea.vim.group.KeyGroup;
|
||||
import com.maddyhome.idea.vim.handler.ActionBeanClass;
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
||||
import com.maddyhome.idea.vim.key.MappingOwner;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
public class RegisterActions {
|
||||
|
||||
public static final ExtensionPointName<ActionBeanClass> VIM_ACTIONS_EP =
|
||||
ExtensionPointName.create("IdeaVIM.vimAction");
|
||||
|
||||
/**
|
||||
* Register all the key/action mappings for the plugin.
|
||||
*/
|
||||
public static void registerActions() {
|
||||
registerVimCommandActions();
|
||||
registerEmptyShortcuts();
|
||||
registerEpListener();
|
||||
}
|
||||
|
||||
private static void registerEpListener() {
|
||||
// IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
|
||||
// but let's support dynamic plugins in a more classic way and reload actions on every EP change.
|
||||
VIM_ACTIONS_EP.addChangeListener(() -> {
|
||||
unregisterActions();
|
||||
registerActions();
|
||||
}, VimPlugin.getInstance());
|
||||
}
|
||||
|
||||
public static @Nullable EditorActionHandlerBase findAction(@NotNull String id) {
|
||||
return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream()
|
||||
.filter(vimActionBean -> vimActionBean.getActionId().equals(id)).findFirst().map(ActionBeanClass::getInstance)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public static @NotNull EditorActionHandlerBase findActionOrDie(@NotNull String id) {
|
||||
EditorActionHandlerBase action = findAction(id);
|
||||
if (action == null) throw new RuntimeException("Action " + id + " is not registered");
|
||||
return action;
|
||||
}
|
||||
|
||||
public static void unregisterActions() {
|
||||
KeyGroup keyGroup = VimPlugin.getKeyIfCreated();
|
||||
if (keyGroup != null) {
|
||||
keyGroup.unregisterCommandActions();
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerVimCommandActions() {
|
||||
KeyGroup parser = VimPlugin.getKey();
|
||||
VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map(IjVimActionsInitiator::new)
|
||||
.forEach(parser::registerCommandAction);
|
||||
}
|
||||
|
||||
private static void registerEmptyShortcuts() {
|
||||
final KeyGroup 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.INSTANCE);
|
||||
}
|
||||
}
|
@@ -1,100 +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 com.maddyhome.idea.vim
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.maddyhome.idea.vim.action.EngineCommandProvider
|
||||
import com.maddyhome.idea.vim.action.IntellijCommandProvider
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.handler.ActionBeanClass
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.key.MappingOwner
|
||||
import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public object RegisterActions {
|
||||
@Deprecated("Please use @CommandOrMotion annotation instead")
|
||||
internal val VIM_ACTIONS_EP: ExtensionPointName<ActionBeanClass> = ExtensionPointName.create("IdeaVIM.vimAction")
|
||||
|
||||
/**
|
||||
* Register all the key/action mappings for the plugin.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun registerActions() {
|
||||
registerVimCommandActions()
|
||||
if (!injector.globalIjOptions().commandOrMotionAnnotation) {
|
||||
registerEmptyShortcuts()
|
||||
registerEpListener()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Moving to annotations approach instead of xml")
|
||||
private fun registerEpListener() {
|
||||
// IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
|
||||
// but let's support dynamic plugins in a more classic way and reload actions on every EP change.
|
||||
VIM_ACTIONS_EP.addChangeListener({
|
||||
unregisterActions()
|
||||
registerActions()
|
||||
}, VimPlugin.getInstance())
|
||||
}
|
||||
|
||||
public fun findAction(id: String): EditorActionHandlerBase? {
|
||||
if (injector.globalIjOptions().commandOrMotionAnnotation) {
|
||||
val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
|
||||
?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
|
||||
return commandBean.instance
|
||||
} else {
|
||||
return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream()
|
||||
.filter { vimActionBean: ActionBeanClass -> vimActionBean.actionId == id }
|
||||
.findFirst().map { obj: ActionBeanClass -> obj.instance }
|
||||
.orElse(null)
|
||||
}
|
||||
}
|
||||
|
||||
public fun findActionOrDie(id: String): EditorActionHandlerBase {
|
||||
return findAction(id) ?: throw RuntimeException("Action $id is not registered")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun unregisterActions() {
|
||||
val keyGroup = VimPlugin.getKeyIfCreated()
|
||||
keyGroup?.unregisterCommandActions()
|
||||
}
|
||||
|
||||
private fun registerVimCommandActions() {
|
||||
val parser = VimPlugin.getKey()
|
||||
if (injector.globalIjOptions().commandOrMotionAnnotation) {
|
||||
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||
} else {
|
||||
VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map { bean: ActionBeanClass? ->
|
||||
IjVimActionsInitiator(
|
||||
bean!!
|
||||
)
|
||||
}
|
||||
.forEach { actionHolder: IjVimActionsInitiator? ->
|
||||
parser.registerCommandAction(
|
||||
actionHolder!!
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo do we really need this?
|
||||
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)
|
||||
}
|
||||
}
|
@@ -219,10 +219,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
return getInstance().enabled;
|
||||
}
|
||||
|
||||
public static boolean isNotEnabled() {
|
||||
return !isEnabled();
|
||||
}
|
||||
|
||||
public static void setEnabled(final boolean enabled) {
|
||||
if (isEnabled() == enabled) return;
|
||||
|
||||
@@ -236,13 +232,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
getInstance().turnOnPlugin();
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
|
||||
} else {
|
||||
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
|
||||
}
|
||||
|
||||
StatusBarIconFactory.Util.INSTANCE.updateIcon();
|
||||
StatusBarIconFactory.Companion.updateIcon();
|
||||
}
|
||||
|
||||
public static String getMessage() {
|
||||
@@ -274,8 +264,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
if (enabled) {
|
||||
Application application = ApplicationManager.getApplication();
|
||||
if (application.isUnitTestMode()) {
|
||||
turnOnPlugin();
|
||||
//application.invokeAndWait(this::turnOnPlugin);
|
||||
application.invokeAndWait(this::turnOnPlugin);
|
||||
}
|
||||
else {
|
||||
application.invokeLater(this::turnOnPlugin);
|
||||
@@ -317,6 +306,11 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
* This is required to ensure that all options are correctly initialised and registered. Must be before any commands
|
||||
* are executed.</li>
|
||||
* <li>~/.ideavimrc execution<br>
|
||||
* <ul>
|
||||
* <li>4.1 executes commands from the .ideavimrc file and 4.2 initializes extensions.</li>
|
||||
* <li>4.1 MUST BE BEFORE 4.2. This is a flow of vim/IdeaVim initialization, firstly .ideavimrc is executed and then
|
||||
* the extensions are initialized.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Components initialization<br>
|
||||
* This should happen after ideavimrc execution because VimListenerManager accesses `number` option
|
||||
@@ -345,9 +339,13 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
VimInjectorKt.getInjector().getOptionGroup().initialiseOptions();
|
||||
|
||||
// 4) ~/.ideavimrc execution
|
||||
// 4.1) Execute ~/.ideavimrc
|
||||
// Evaluate in the context of the fallback window, to capture local option state, to copy to the first editor window
|
||||
registerIdeavimrc(VimInjectorKt.getInjector().getFallbackWindow());
|
||||
|
||||
// 4.2) Initialize extensions. Always after 4.1
|
||||
VimExtensionRegistrar.enableDelayedExtensions();
|
||||
|
||||
// Turing on should be performed after all commands registration
|
||||
getSearch().turnOn();
|
||||
VimListenerManager.INSTANCE.turnOn();
|
||||
|
@@ -28,11 +28,8 @@ import javax.swing.KeyStroke
|
||||
* Accepts all regular keystrokes and passes them on to the Vim key handler.
|
||||
*
|
||||
* IDE shortcut keys used by Vim commands are handled by [com.maddyhome.idea.vim.action.VimShortcutKeyAction].
|
||||
*
|
||||
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
|
||||
* way to get ideavim keys for this plugin. See VIM-3085
|
||||
*/
|
||||
public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
|
||||
internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
|
||||
private val handler = KeyHandler.getInstance()
|
||||
private val traceTime = injector.globalOptions().ideatracetime
|
||||
|
||||
@@ -89,7 +86,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
|
||||
}
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
private val LOG = logger<VimTypedActionHandler>()
|
||||
}
|
||||
}
|
||||
|
@@ -7,8 +7,6 @@
|
||||
*/
|
||||
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
|
||||
@@ -16,7 +14,6 @@ import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
|
||||
@CommandOrMotion(keys = [":"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||
internal class ExEntryAction : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_READONLY
|
||||
|
||||
|
@@ -1,13 +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 com.maddyhome.idea.vim.action
|
||||
|
||||
public object IntellijCommandProvider : CommandProvider {
|
||||
override val commandListFileName: String = "intellij_commands.json"
|
||||
}
|
@@ -14,10 +14,14 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.AnActionWrapper
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionManager
|
||||
import com.intellij.openapi.keymap.KeymapManager
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.util.Key
|
||||
@@ -28,6 +32,7 @@ import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||
import com.maddyhome.idea.vim.group.IjOptions
|
||||
import com.maddyhome.idea.vim.handler.enableOctopus
|
||||
import com.maddyhome.idea.vim.handler.isOctopusEnabled
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.HandlerInjector
|
||||
@@ -54,17 +59,9 @@ import javax.swing.KeyStroke
|
||||
*
|
||||
*
|
||||
* These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
|
||||
*
|
||||
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
|
||||
* way to get ideavim keys for this plugin. See VIM-3085
|
||||
*/
|
||||
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
||||
private val traceTime: Boolean
|
||||
get() {
|
||||
// Make sure the injector is initialized
|
||||
VimPlugin.getInstance()
|
||||
return injector.globalOptions().ideatracetime
|
||||
}
|
||||
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
||||
private val traceTime = injector.globalOptions().ideatracetime
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
LOG.trace("Executing shortcut key action")
|
||||
@@ -98,29 +95,29 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
||||
|
||||
// There is a chance that we can use BGT, but we call for isCell inside the update.
|
||||
// Not sure if can can use BGT with this call. Let's use EDT for now.
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
|
||||
override fun getActionUpdateThread() = ActionUpdateThread.EDT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val start = if (traceTime) System.currentTimeMillis() else null
|
||||
val keyStroke = getKeyStroke(e)
|
||||
val actionEnableStatus = isEnabled(e, keyStroke)
|
||||
val actionEnableStatus = isEnabled(e)
|
||||
e.presentation.isEnabled = actionEnableStatus.isEnabled
|
||||
actionEnableStatus.printLog(keyStroke)
|
||||
actionEnableStatus.printLog()
|
||||
if (start != null) {
|
||||
val keyStroke = getKeyStroke(e)
|
||||
val duration = System.currentTimeMillis() - start
|
||||
LOG.info("VimShortcut update '$keyStroke': $duration ms")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
|
||||
if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
|
||||
private fun isEnabled(e: AnActionEvent): ActionEnableStatus {
|
||||
if (!VimPlugin.isEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
|
||||
val editor = getEditor(e)
|
||||
val keyStroke = getKeyStroke(e)
|
||||
if (editor != null && keyStroke != null) {
|
||||
if (isOctopusEnabled(keyStroke, editor)) {
|
||||
return ActionEnableStatus.no(
|
||||
"Processing VimShortcutKeyAction for the key that is used in the octopus handler",
|
||||
LogLevel.ERROR
|
||||
)
|
||||
if (enableOctopus) {
|
||||
if (isOctopusEnabled(keyStroke, editor)) {
|
||||
return ActionEnableStatus.no("Octopus handler is enabled", LogLevel.DEBUG)
|
||||
}
|
||||
}
|
||||
if (editor.isIdeaVimDisabledHere) {
|
||||
return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO)
|
||||
@@ -167,6 +164,14 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
||||
return ActionEnableStatus.no("App code template is active", LogLevel.INFO)
|
||||
}
|
||||
|
||||
val nextTemplateVariableShortcuts = KeymapManager.getInstance().activeKeymap.getShortcuts(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
|
||||
if (nextTemplateVariableShortcuts.any { it is KeyboardShortcut && it.firstKeyStroke == keyStroke }) {
|
||||
val handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
|
||||
if (handler.isEnabled(editor, null, e.dataContext)) {
|
||||
return ActionEnableStatus.no("Next template variable or finish in-place refactoring", LogLevel.INFO)
|
||||
}
|
||||
}
|
||||
|
||||
if (editor.inInsertMode) {
|
||||
if (keyCode == KeyEvent.VK_TAB) {
|
||||
// TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
|
||||
@@ -232,9 +237,9 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
||||
/**
|
||||
* getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
|
||||
* but we should cache the value because on the second call (isEnabled -> actionPerformed)
|
||||
* the event is already consumed and getDefaultKeyStroke returns null
|
||||
* the event is already consumed
|
||||
*/
|
||||
private var keyStrokeCache: Pair<Long?, KeyStroke?> = null to null
|
||||
private var keyStrokeCache: Pair<KeyEvent?, KeyStroke?> = null to null
|
||||
|
||||
private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
|
||||
val inputEvent = e.inputEvent
|
||||
@@ -242,9 +247,9 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
||||
val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
|
||||
val strokeCache = keyStrokeCache
|
||||
if (defaultKeyStroke != null) {
|
||||
keyStrokeCache = inputEvent.`when` to defaultKeyStroke
|
||||
keyStrokeCache = inputEvent to defaultKeyStroke
|
||||
return defaultKeyStroke
|
||||
} else if (strokeCache.first == inputEvent.`when`) {
|
||||
} else if (strokeCache.first === inputEvent) {
|
||||
keyStrokeCache = null to null
|
||||
return strokeCache.second
|
||||
}
|
||||
@@ -277,7 +282,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
||||
.toSet()
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
@JvmField
|
||||
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
|
||||
ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))
|
||||
@@ -368,12 +373,10 @@ private class ActionEnableStatus(
|
||||
val message: String,
|
||||
val logLevel: LogLevel,
|
||||
) {
|
||||
fun printLog(keyStroke: KeyStroke?) {
|
||||
val message = "IdeaVim keys are enabled = $isEnabled for key '$keyStroke': $message"
|
||||
fun printLog() {
|
||||
when (logLevel) {
|
||||
LogLevel.INFO -> LOG.info(message)
|
||||
LogLevel.DEBUG -> LOG.debug(message)
|
||||
LogLevel.ERROR -> LOG.error(message)
|
||||
LogLevel.INFO -> LOG.info("IdeaVim keys are enabled = $isEnabled: $message")
|
||||
LogLevel.DEBUG -> LOG.debug("IdeaVim keys are enabled = $isEnabled: $message")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,5 +389,5 @@ private class ActionEnableStatus(
|
||||
}
|
||||
|
||||
private enum class LogLevel {
|
||||
DEBUG, INFO, ERROR,
|
||||
DEBUG, INFO,
|
||||
}
|
@@ -7,8 +7,6 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.action.change
|
||||
|
||||
import com.intellij.vim.annotations.CommandOrMotion
|
||||
import com.intellij.vim.annotations.Mode
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
@@ -18,7 +16,9 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.setChangeMarks
|
||||
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.OperatorArguments
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.common.argumentCaptured
|
||||
import com.maddyhome.idea.vim.group.MotionGroup
|
||||
@@ -26,9 +26,10 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import java.util.*
|
||||
|
||||
// todo make it multicaret
|
||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
|
||||
@@ -46,7 +47,6 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
|
||||
return result
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL])
|
||||
internal class OperatorAction : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||
|
||||
@@ -97,10 +97,11 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
|
||||
}
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["g@"], modes = [Mode.VISUAL])
|
||||
internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
||||
|
||||
override fun executeAction(
|
||||
editor: VimEditor,
|
||||
caret: VimCaret,
|
||||
|
@@ -7,8 +7,6 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.action.change
|
||||
|
||||
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
|
||||
@@ -20,7 +18,6 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
|
||||
@CommandOrMotion(keys = ["."], modes = [Mode.NORMAL])
|
||||
internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_WRITABLE
|
||||
|
||||
|
@@ -7,8 +7,6 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.action.change.delete
|
||||
|
||||
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.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
@@ -19,7 +17,6 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
|
||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
|
||||
public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
override fun runAsMulticaret(
|
||||
|
@@ -7,8 +7,6 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.action.change.delete
|
||||
|
||||
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
|
||||
@@ -18,7 +16,6 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
|
||||
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
|
||||
public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
||||
|
@@ -7,25 +7,27 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.action.change.delete
|
||||
|
||||
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.VimCaret
|
||||
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.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL])
|
||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
||||
|
||||
override fun executeForAllCarets(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
|
@@ -7,25 +7,27 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.action.change.delete
|
||||
|
||||
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.VimCaret
|
||||
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.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
@CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL])
|
||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
||||
|
||||
override fun executeForAllCarets(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
|
@@ -8,8 +8,6 @@
|
||||
|
||||
package com.maddyhome.idea.vim.action.editor
|
||||
|
||||
import com.intellij.vim.annotations.CommandOrMotion
|
||||
import com.intellij.vim.annotations.Mode
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.maddyhome.idea.vim.action.ComplicatedKeysAction
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
@@ -25,7 +23,6 @@ import java.awt.event.KeyEvent
|
||||
import java.util.*
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
|
||||
internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE), ComplicatedKeysAction {
|
||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK)),
|
||||
@@ -34,7 +31,6 @@ internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BA
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
|
||||
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE), ComplicatedKeysAction {
|
||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)),
|
||||
@@ -43,7 +39,6 @@ internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELET
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["<Down>", "<kDown>"], modes = [Mode.INSERT])
|
||||
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN), ComplicatedKeysAction {
|
||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)),
|
||||
@@ -53,7 +48,6 @@ internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CA
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
|
||||
internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB), ComplicatedKeysAction {
|
||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_DOWN_MASK)),
|
||||
@@ -63,7 +57,6 @@ internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB), Co
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["<Up>", "<kUp>"], modes = [Mode.INSERT])
|
||||
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP), ComplicatedKeysAction {
|
||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)),
|
||||
@@ -73,7 +66,6 @@ internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARE
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["K"], modes = [Mode.NORMAL])
|
||||
internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_READONLY
|
||||
|
||||
|
@@ -7,8 +7,6 @@
|
||||
*/
|
||||
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.action.ComplicatedKeysAction
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
@@ -25,7 +23,6 @@ import javax.swing.KeyStroke
|
||||
*
|
||||
* 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(), ComplicatedKeysAction {
|
||||
override val keyStrokesSet: Set<List<KeyStroke>> =
|
||||
parseKeysSet("<CR>", "<C-M>", 0x0a.toChar().toString(), 0x0d.toChar().toString())
|
||||
|
@@ -27,7 +27,7 @@ public class CommandState(private val machine: VimStateMachine) {
|
||||
get() {
|
||||
val myMode = machine.mode
|
||||
return when (myMode) {
|
||||
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
|
||||
com.maddyhome.idea.vim.state.mode.Mode.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
|
||||
|
@@ -9,7 +9,6 @@ package com.maddyhome.idea.vim.extension
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
@@ -18,6 +17,7 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.common.CommandAlias
|
||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
||||
import com.maddyhome.idea.vim.helper.CommandLineHelper
|
||||
@@ -26,7 +26,6 @@ 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 java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
@@ -39,9 +38,6 @@ import javax.swing.KeyStroke
|
||||
* @author vlan
|
||||
*/
|
||||
public object VimExtensionFacade {
|
||||
|
||||
private val LOG = logger<VimExtensionFacade>()
|
||||
|
||||
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
||||
@JvmStatic
|
||||
public fun putExtensionHandlerMapping(
|
||||
@@ -144,12 +140,10 @@ public object VimExtensionFacade {
|
||||
public fun inputKeyStroke(editor: Editor): KeyStroke {
|
||||
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
|
||||
val input = Extension.consumeKeystroke()
|
||||
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
|
||||
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
|
||||
}
|
||||
|
||||
val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
LOG.trace("Unit test mode is active")
|
||||
val mappingStack = KeyHandler.getInstance().keyStack
|
||||
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
|
||||
if (editor.vim.vimStateMachine.isRecording) {
|
||||
@@ -157,13 +151,11 @@ public object VimExtensionFacade {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.trace("Getting char from the modal entry...")
|
||||
var ref: KeyStroke? = null
|
||||
ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
|
||||
ref = stroke
|
||||
false
|
||||
}
|
||||
LOG.trace("Got char $ref")
|
||||
ref
|
||||
}
|
||||
val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())
|
||||
|
@@ -82,31 +82,6 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* During vim initialization process, it firstly loads the .vimrc file, then executes scripts from the plugins folder.
|
||||
* This practically means that the .vimrc file is initialized first, then the plugins are loaded.
|
||||
* See `:h initialization`
|
||||
*
|
||||
* In IdeaVim we don't have a separate plugins folder to load it after .ideavimrc load. However, we can collect
|
||||
* the list of plugins mentioned in the .ideavimrc and load them after .ideavimrc execution is finished.
|
||||
*
|
||||
* Why this matters? Because this affects the order of commands are executed. For example:
|
||||
* ```
|
||||
* plug 'tommcdo/vim-exchange'
|
||||
* let g:exchange_no_mappings=1
|
||||
* ```
|
||||
* Here the user will expect that the exchange plugin won't have default mappings. However, if we load vim-exchange
|
||||
* immediately, this variable won't be initialized at the moment of plugin initialization.
|
||||
*
|
||||
* There is also a tricky case for mappings override:
|
||||
* ```
|
||||
* plug 'tommcdo/vim-exchange'
|
||||
* map X <Plug>(ExchangeLine)
|
||||
* ```
|
||||
* For this case, a plugin with a good implementation detects that there is already a defined mapping for
|
||||
* `<Plug>(ExchangeLine)` and doesn't register the default cxx mapping. However, such detection requires the mapping
|
||||
* to be defined before the plugin initialization.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun enableDelayedExtensions() {
|
||||
delayedExtensionEnabling.forEach {
|
||||
|
@@ -11,7 +11,6 @@ import com.intellij.codeInsight.actions.AsyncActionExecutionService
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -75,26 +74,19 @@ internal class CommentaryExtension : VimExtension {
|
||||
listOf(IdeActions.ACTION_COMMENT_BLOCK, IdeActions.ACTION_COMMENT_LINE)
|
||||
}
|
||||
|
||||
val project = editor.ij.project!!
|
||||
val callback = { afterCommenting(mode, editor, resetCaret, range) }
|
||||
actions.any { executeActionWithCallbackOnSuccess(it, project, context, callback) }
|
||||
val res = Ref.create<Boolean>(true)
|
||||
AsyncActionExecutionService.getInstance(editor.ij.project!!).withExecutionAfterAction(actions[0], {
|
||||
res.set(injector.actionExecutor.executeAction(actions[0], context))
|
||||
}, { afterCommenting(mode, editor, resetCaret, range) })
|
||||
if (!res.get()) {
|
||||
AsyncActionExecutionService.getInstance(editor.ij.project!!).withExecutionAfterAction(actions[1], {
|
||||
res.set(injector.actionExecutor.executeAction(actions[1], context))
|
||||
}, { afterCommenting(mode, editor, resetCaret, range) })
|
||||
}
|
||||
res.get()
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeActionWithCallbackOnSuccess(
|
||||
action: String,
|
||||
project: Project,
|
||||
context: ExecutionContext,
|
||||
callback: () -> Unit,
|
||||
): Boolean {
|
||||
val res = Ref.create<Boolean>(false)
|
||||
AsyncActionExecutionService.getInstance(project).withExecutionAfterAction(
|
||||
action,
|
||||
{ res.set(injector.actionExecutor.executeAction(action, context)) },
|
||||
{ if (res.get()) callback() })
|
||||
return res.get()
|
||||
}
|
||||
|
||||
private fun afterCommenting(
|
||||
mode: Mode,
|
||||
editor: VimEditor,
|
||||
@@ -156,6 +148,11 @@ internal class CommentaryExtension : VimExtension {
|
||||
private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
|
||||
override val isRepeatable = true
|
||||
|
||||
// In this operator we process selection by ourselves. This is necessary for rider, VIM-1758
|
||||
override fun postProcessSelection(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
setOperatorFunction(this)
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
||||
|
@@ -217,8 +217,6 @@ private object FileTypePatterns {
|
||||
|
||||
return if (fileTypeName in htmlLikeFileTypes) {
|
||||
this.htmlPatterns
|
||||
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
|
||||
this.javaPatterns
|
||||
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
|
||||
this.rubyPatterns
|
||||
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
|
||||
@@ -244,7 +242,6 @@ private object FileTypePatterns {
|
||||
)
|
||||
|
||||
private val htmlPatterns = createHtmlPatterns()
|
||||
private val javaPatterns = createJavaPatterns()
|
||||
private val rubyPatterns = createRubyPatterns()
|
||||
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
|
||||
private val phpPatterns = createPhpPatterns()
|
||||
@@ -273,14 +270,6 @@ private object FileTypePatterns {
|
||||
LanguagePatterns(linkedMapOf(openingTagPattern to htmlSearchPair), linkedMapOf(closingTagPattern to htmlSearchPair))
|
||||
)
|
||||
}
|
||||
|
||||
private fun createJavaPatterns(): LanguagePatterns {
|
||||
return (
|
||||
LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") +
|
||||
LanguagePatterns("\\bdo\\b", "\\bwhile\\b") +
|
||||
LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b")
|
||||
)
|
||||
}
|
||||
|
||||
private fun createRubyPatterns(): LanguagePatterns {
|
||||
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
|
||||
|
@@ -8,7 +8,6 @@
|
||||
package com.maddyhome.idea.vim.extension.surround
|
||||
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
@@ -256,7 +255,6 @@ internal class VimSurroundExtension : VimExtension {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
// Deleting surround is just changing the surrounding to "nothing"
|
||||
val charFrom = getChar(editor.ij)
|
||||
LOG.debug("DSurroundHandler: charFrom = $charFrom")
|
||||
if (charFrom.code == 0) return
|
||||
|
||||
runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
|
||||
@@ -309,101 +307,96 @@ internal class VimSurroundExtension : VimExtension {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val LOG = logger<VimSurroundExtension>()
|
||||
companion object {
|
||||
private const val REGISTER = '"'
|
||||
|
||||
private const val REGISTER = '"'
|
||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
|
||||
|
||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
|
||||
|
||||
private val SURROUND_PAIRS = mapOf(
|
||||
'b' to ("(" to ")"),
|
||||
'(' to ("( " to " )"),
|
||||
')' to ("(" to ")"),
|
||||
'B' to ("{" to "}"),
|
||||
'{' to ("{ " to " }"),
|
||||
'}' to ("{" to "}"),
|
||||
'r' to ("[" to "]"),
|
||||
'[' to ("[ " to " ]"),
|
||||
']' to ("[" to "]"),
|
||||
'a' to ("<" to ">"),
|
||||
'>' to ("<" to ">"),
|
||||
's' to (" " to ""),
|
||||
)
|
||||
|
||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
||||
SURROUND_PAIRS[c]
|
||||
} else if (!c.isLetter()) {
|
||||
val s = c.toString()
|
||||
s to s
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
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)
|
||||
val tagAttributes = matcher.group(2)
|
||||
"<$tagName$tagAttributes>" to "</$tagName>"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun inputFunctionName(
|
||||
editor: Editor,
|
||||
withInternalSpaces: Boolean,
|
||||
): Pair<String, String>? {
|
||||
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): Pair<String, String>? = when (c) {
|
||||
'<', 't' -> inputTagPair(editor)
|
||||
'f' -> inputFunctionName(editor, false)
|
||||
'F' -> inputFunctionName(editor, true)
|
||||
else -> getSurroundPair(c)
|
||||
}
|
||||
|
||||
private fun getChar(editor: Editor): Char {
|
||||
val key = inputKeyStroke(editor)
|
||||
val keyChar = key.keyChar
|
||||
val res = if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
|
||||
0.toChar()
|
||||
} else {
|
||||
keyChar
|
||||
}
|
||||
LOG.trace("getChar: $res")
|
||||
return res
|
||||
}
|
||||
|
||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||
runWriteAction {
|
||||
val editor = caret.editor
|
||||
val change = VimPlugin.getChange()
|
||||
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||
|
||||
val isEOF = range.endOffset == editor.text().length
|
||||
val hasNewLine = editor.endsWithNewLine()
|
||||
val rightSurround = (if (tagsOnNewLines) {
|
||||
if (isEOF && !hasNewLine) {
|
||||
"\n" + pair.second
|
||||
} else {
|
||||
pair.second + "\n"
|
||||
}
|
||||
} else {
|
||||
pair.second
|
||||
}).let { RepeatedCharSequence.of(it, count) }
|
||||
|
||||
change.insertText(editor, caret, range.startOffset, leftSurround)
|
||||
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
||||
injector.markService.setChangeMarks(
|
||||
caret,
|
||||
TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length)
|
||||
private val SURROUND_PAIRS = mapOf(
|
||||
'b' to ("(" to ")"),
|
||||
'(' to ("( " to " )"),
|
||||
')' to ("(" to ")"),
|
||||
'B' to ("{" to "}"),
|
||||
'{' to ("{ " to " }"),
|
||||
'}' to ("{" to "}"),
|
||||
'r' to ("[" to "]"),
|
||||
'[' to ("[ " to " ]"),
|
||||
']' to ("[" to "]"),
|
||||
'a' to ("<" to ">"),
|
||||
'>' to ("<" to ">"),
|
||||
's' to (" " to ""),
|
||||
)
|
||||
|
||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
||||
SURROUND_PAIRS[c]
|
||||
} else if (!c.isLetter()) {
|
||||
val s = c.toString()
|
||||
s to s
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
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)
|
||||
val tagAttributes = matcher.group(2)
|
||||
"<$tagName$tagAttributes>" to "</$tagName>"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun inputFunctionName(
|
||||
editor: Editor,
|
||||
withInternalSpaces: Boolean,
|
||||
): Pair<String, String>? {
|
||||
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): Pair<String, String>? = when (c) {
|
||||
'<', 't' -> inputTagPair(editor)
|
||||
'f' -> inputFunctionName(editor, false)
|
||||
'F' -> inputFunctionName(editor, true)
|
||||
else -> getSurroundPair(c)
|
||||
}
|
||||
|
||||
private fun getChar(editor: Editor): Char {
|
||||
val key = inputKeyStroke(editor)
|
||||
val keyChar = key.keyChar
|
||||
return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
|
||||
0.toChar()
|
||||
} else {
|
||||
keyChar
|
||||
}
|
||||
}
|
||||
|
||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||
runWriteAction {
|
||||
val editor = caret.editor
|
||||
val change = VimPlugin.getChange()
|
||||
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||
|
||||
val isEOF = range.endOffset == editor.text().length
|
||||
val hasNewLine = editor.endsWithNewLine()
|
||||
val rightSurround = (if (tagsOnNewLines) {
|
||||
if (isEOF && !hasNewLine) {
|
||||
"\n" + pair.second
|
||||
} else {
|
||||
pair.second + "\n"
|
||||
}
|
||||
} else {
|
||||
pair.second
|
||||
}).let { RepeatedCharSequence.of(it, count) }
|
||||
|
||||
change.insertText(editor, caret, range.startOffset, leftSurround)
|
||||
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
||||
injector.markService.setChangeMarks(caret, TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,11 +16,12 @@ import com.intellij.openapi.command.UndoConfirmationPolicy
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.LogicalPosition
|
||||
import com.intellij.openapi.editor.actions.EnterAction
|
||||
import com.intellij.openapi.editor.event.EditorMouseEvent
|
||||
import com.intellij.openapi.editor.event.EditorMouseListener
|
||||
import com.intellij.openapi.editor.impl.TextRangeInterval
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.openapi.ui.popup.Balloon
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import com.intellij.psi.util.PsiUtilBase
|
||||
@@ -51,7 +52,6 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
||||
import com.maddyhome.idea.vim.handler.commandContinuation
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||
@@ -62,28 +62,29 @@ import com.maddyhome.idea.vim.helper.endOffsetInclusive
|
||||
import com.maddyhome.idea.vim.helper.inInsertMode
|
||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
|
||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||
import com.maddyhome.idea.vim.icons.VimIcons
|
||||
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.listener.VimInsertListener
|
||||
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.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.math.BigInteger
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Provides all the insert/replace related functionality
|
||||
*/
|
||||
public class ChangeGroup : VimChangeGroupBase() {
|
||||
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
|
||||
private var lastShownTime = 0L
|
||||
private val listener: EditorMouseListener = object : EditorMouseListener {
|
||||
override fun mouseClicked(event: EditorMouseEvent) {
|
||||
val editor = event.editor
|
||||
@@ -97,6 +98,10 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
|
||||
}
|
||||
|
||||
public fun editorReleased(editor: Editor?) {
|
||||
EventFacade.getInstance().removeEditorMouseListener(editor!!, listener)
|
||||
}
|
||||
|
||||
override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
val ijContext = context.ij
|
||||
@@ -111,35 +116,6 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
injector.scroll.scrollCaretIntoView(vimEditor)
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is REPLACE mode we need to turn off OVERWRITE before and then turn OVERWRITE back on after sending the
|
||||
* "ENTER" key.
|
||||
*/
|
||||
override fun processEnter(
|
||||
editor: VimEditor,
|
||||
caret: VimCaret,
|
||||
context: ExecutionContext,
|
||||
) {
|
||||
if (editor.mode is Mode.REPLACE) {
|
||||
editor.insertMode = true
|
||||
}
|
||||
try {
|
||||
val continuation = (context.context as UserDataHolder).getUserData(commandContinuation)
|
||||
val ijEditor = editor.ij
|
||||
val ij = context.ij
|
||||
val ijCaret = caret.ij
|
||||
if (continuation != null) {
|
||||
continuation.execute(ijEditor, ijCaret, ij)
|
||||
} else {
|
||||
EnterAction().handler.execute(ijEditor, ijCaret, ij)
|
||||
}
|
||||
} finally {
|
||||
if (editor.mode is Mode.REPLACE) {
|
||||
editor.insertMode = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDeleteRangeAndType2(
|
||||
editor: VimEditor,
|
||||
caret: VimCaret,
|
||||
@@ -394,7 +370,6 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
context: ExecutionContext,
|
||||
range: TextRange,
|
||||
) {
|
||||
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
|
||||
@@ -419,7 +394,11 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
}
|
||||
}
|
||||
val afterAction = {
|
||||
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
|
||||
val firstLine = editor.offsetToBufferPosition(
|
||||
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
|
||||
).line
|
||||
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
|
||||
caret.moveToOffset(newOffset)
|
||||
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
||||
}
|
||||
if (project != null) {
|
||||
@@ -632,6 +611,25 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
avalanche: Boolean,
|
||||
): Boolean {
|
||||
|
||||
// Just an easter egg
|
||||
if (avalanche) {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (currentTime - lastShownTime > 60000) {
|
||||
lastShownTime = currentTime
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
val balloon = JBPopupFactory.getInstance()
|
||||
.createHtmlTextBalloonBuilder(
|
||||
"Wow, nice vim skills!", VimIcons.IDEAVIM,
|
||||
MessageType.INFO.titleForeground, MessageType.INFO.popupBackground,
|
||||
null
|
||||
).createBalloon()
|
||||
balloon.show(
|
||||
JBPopupFactory.getInstance().guessBestPopupLocation((editor as IjVimEditor).editor),
|
||||
Balloon.Position.below
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val nf: List<String> = injector.options(editor).nrformats
|
||||
val alpha = nf.contains("alpha")
|
||||
val hex = nf.contains("hex")
|
||||
|
@@ -89,17 +89,14 @@ public class FileGroup extends VimFileBase {
|
||||
|
||||
@Nullable VirtualFile findFile(@NotNull String filename, @NotNull Project project) {
|
||||
VirtualFile found;
|
||||
// Vim supports both ~/ and ~\ (tested on Mac and Windows). On Windows, it supports forward- and back-slashes, but
|
||||
// it only supports forward slash on Unix (tested on Mac)
|
||||
// VFS works with both directory separators (tested on Mac and Windows)
|
||||
if (filename.startsWith("~/") || filename.startsWith("~\\")) {
|
||||
String relativePath = filename.substring(2);
|
||||
if (filename.length() > 2 && filename.charAt(0) == '~' && filename.charAt(1) == File.separatorChar) {
|
||||
String homefile = filename.substring(2);
|
||||
String dir = System.getProperty("user.home");
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("home dir file");
|
||||
logger.debug("looking for " + relativePath + " in " + dir);
|
||||
logger.debug("looking for " + homefile + " in " + dir);
|
||||
}
|
||||
found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(dir, relativePath));
|
||||
found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(dir, homefile));
|
||||
}
|
||||
else {
|
||||
found = LocalFileSystem.getInstance().findFileByIoFile(new File(filename));
|
||||
|
@@ -29,15 +29,13 @@ 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)
|
||||
public var colorfulmodewidget: Boolean by optionProperty(IjOptions.colorfulmodewidget)
|
||||
|
||||
// Temporary options to control work-in-progress behaviour
|
||||
public var octopushandler: Boolean by optionProperty(IjOptions.octopushandler)
|
||||
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
||||
public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
|
||||
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
|
||||
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
|
||||
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -80,16 +80,14 @@ public object IjOptions {
|
||||
"lookupkeys",
|
||||
"<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
|
||||
)
|
||||
public val octopushandler: ToggleOption = addOption(ToggleOption("octopushandler", GLOBAL, "octopushandler", false))
|
||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true))
|
||||
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
||||
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
|
||||
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true))
|
||||
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
||||
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 colorfulmodewidget: ToggleOption = addOption(ToggleOption("colorfulmodewidget", GLOBAL, "colorfulmodewidget", false, 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>
|
||||
private fun <T : Option<out VimDataType>> addOption(option: T) = option.also { Options.addOption(option) }
|
||||
}
|
||||
}
|
@@ -26,7 +26,6 @@ import com.maddyhome.idea.vim.EventFacade;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.action.ComplicatedKeysAction;
|
||||
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
|
||||
import com.maddyhome.idea.vim.action.change.LazyVimCommand;
|
||||
import com.maddyhome.idea.vim.api.*;
|
||||
import com.maddyhome.idea.vim.command.MappingMode;
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel;
|
||||
@@ -209,25 +208,6 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
|
||||
registerRequiredShortcut(Collections.singletonList(keyStroke), owner);
|
||||
}
|
||||
|
||||
public void registerCommandAction(@NotNull LazyVimCommand command) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
initIdentityChecker();
|
||||
for (List<KeyStroke> keys : command.getKeys()) {
|
||||
checkCommand(command.getModes(), command, keys);
|
||||
}
|
||||
}
|
||||
|
||||
for (List<KeyStroke> keyStrokes : command.getKeys()) {
|
||||
registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE);
|
||||
|
||||
for (MappingMode mappingMode : command.getModes()) {
|
||||
Node<VimActionsInitiator> node = getKeyRoot(mappingMode);
|
||||
NodesKt.addLeafs(node, keyStrokes, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void registerCommandAction(@NotNull VimActionsInitiator actionHolder) {
|
||||
IjVimActionsInitiator holder = (IjVimActionsInitiator)actionHolder;
|
||||
|
||||
@@ -274,9 +254,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 &&
|
||||
!(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
|
||||
!(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
|
||||
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
|
||||
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
|
||||
}
|
||||
}
|
||||
|
@@ -1,68 +0,0 @@
|
||||
package com.maddyhome.idea.vim.group
|
||||
|
||||
import com.intellij.codeInsight.daemon.ReferenceImporter
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ReadAction
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
|
||||
import java.util.function.BooleanSupplier
|
||||
|
||||
internal object MacroAutoImport {
|
||||
fun run(editor: Editor, dataContext: DataContext) {
|
||||
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
|
||||
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
|
||||
|
||||
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
|
||||
return
|
||||
}
|
||||
|
||||
val importers = ReferenceImporter.EP_NAME.extensionList
|
||||
if (importers.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
|
||||
val fixes = mutableListOf<BooleanSupplier>()
|
||||
|
||||
file.accept(object : PsiRecursiveElementWalkingVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
for (reference in element.references) {
|
||||
if (reference.resolve() != null) {
|
||||
continue
|
||||
}
|
||||
for (importer in importers) {
|
||||
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
|
||||
?.let(fixes::add)
|
||||
}
|
||||
}
|
||||
super.visitElement(element)
|
||||
}
|
||||
})
|
||||
|
||||
return@nonBlocking fixes
|
||||
}.executeSynchronously()
|
||||
|
||||
ApplicationManager.getApplication().invokeAndWait {
|
||||
WriteCommandAction.writeCommandAction(project)
|
||||
.withName("Auto Import")
|
||||
.withGroupId("IdeaVimAutoImportAfterMacro")
|
||||
.shouldRecordActionForActiveDocument(true)
|
||||
.run<RuntimeException> {
|
||||
fixes.forEach { it.asBoolean }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -7,8 +7,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.diagnostic.logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
@@ -21,7 +19,6 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
||||
import com.maddyhome.idea.vim.macro.VimMacroBase
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
|
||||
/**
|
||||
* Used to handle playback of macros
|
||||
@@ -64,36 +61,22 @@ internal class MacroGroup : VimMacroBase() {
|
||||
try {
|
||||
myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
|
||||
val runnable = runnable@{
|
||||
try {
|
||||
// Handle one keystroke then queue up the next key
|
||||
for (i in 0 until total) {
|
||||
// Handle one keystroke then queue up the next key
|
||||
for (i in 0 until total) {
|
||||
myPotemkinProgress.fraction = (i + 1).toDouble() / total
|
||||
while (keyStack.hasStroke()) {
|
||||
val key = keyStack.feedStroke()
|
||||
try {
|
||||
myPotemkinProgress.fraction = (i + 1).toDouble() / total
|
||||
while (keyStack.hasStroke()) {
|
||||
val key = keyStack.feedStroke()
|
||||
try {
|
||||
myPotemkinProgress.checkCanceled()
|
||||
} catch (e: ProcessCanceledException) {
|
||||
return@runnable
|
||||
}
|
||||
ProgressManager.getInstance().executeNonCancelableSection {
|
||||
// Prevent autocompletion during macros.
|
||||
// See https://github.com/JetBrains/ideavim/pull/772 for details
|
||||
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
|
||||
getInstance().handleKey(editor, key, context)
|
||||
}
|
||||
if (injector.messages.isError()) return@runnable
|
||||
}
|
||||
} finally {
|
||||
keyStack.resetFirst()
|
||||
myPotemkinProgress.checkCanceled()
|
||||
} catch (e: ProcessCanceledException) {
|
||||
return@runnable
|
||||
}
|
||||
ProgressManager.getInstance().executeNonCancelableSection { getInstance().handleKey(editor, key, context) }
|
||||
if (injector.messages.isError()) return@runnable
|
||||
}
|
||||
} finally {
|
||||
keyStack.removeFirst()
|
||||
}
|
||||
if (!isInternalMacro) {
|
||||
MacroAutoImport.run(editor.ij, context.ij)
|
||||
keyStack.resetFirst()
|
||||
}
|
||||
keyStack.removeFirst()
|
||||
}
|
||||
|
||||
if (isInternalMacro) {
|
||||
|
@@ -28,7 +28,7 @@ import java.util.Set;
|
||||
@Deprecated
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "2.3")
|
||||
public class MarkGroup {
|
||||
public List<Jump> jumps = VimInjectorKt.injector.getJumpService().getJumps("");
|
||||
public List<Jump> jumps = VimInjectorKt.injector.getJumpService().getJumps();
|
||||
|
||||
public void saveJumpLocation(@NotNull Editor editor) {
|
||||
VimInjectorKt.injector.getJumpService().saveJumpLocation(new IjVimEditor(editor));
|
||||
@@ -54,7 +54,7 @@ public class MarkGroup {
|
||||
|
||||
@Nullable
|
||||
public Jump getJump(int count) {
|
||||
return VimInjectorKt.injector.getJumpService().getJump("", count);
|
||||
return VimInjectorKt.injector.getJumpService().getJump(count);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -115,7 +115,7 @@ public class MarkGroup {
|
||||
}
|
||||
|
||||
public int getJumpSpot() {
|
||||
return VimInjectorKt.injector.getJumpService().getJumpSpot("");
|
||||
return VimInjectorKt.injector.getJumpService().getJumpSpot();
|
||||
}
|
||||
|
||||
public void updateMarkFromDelete(@Nullable VimEditor editor,
|
||||
@@ -133,6 +133,6 @@ public class MarkGroup {
|
||||
}
|
||||
|
||||
public void dropLastJump() {
|
||||
VimInjectorKt.injector.getJumpService().dropLastJump("");
|
||||
VimInjectorKt.injector.getJumpService().dropLastJump();
|
||||
}
|
||||
}
|
||||
|
@@ -33,8 +33,6 @@ 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
|
||||
@@ -48,7 +46,9 @@ 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
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
@@ -72,8 +72,6 @@ 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
|
||||
@@ -165,8 +163,8 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
|
||||
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 spot = jumpService.getJumpSpot()
|
||||
val (line, col, fileName) = jumpService.getJump(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)
|
||||
@@ -461,13 +459,11 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
val fileEditor = event.oldEditor
|
||||
if (fileEditor is TextEditor) {
|
||||
val editor = fileEditor.editor
|
||||
if (!editor.isDisposed) {
|
||||
ExOutputModel.getInstance(editor).clear()
|
||||
editor.vim.let { vimEditor ->
|
||||
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
|
||||
vimEditor.exitVisualMode()
|
||||
KeyHandler.getInstance().reset(vimEditor)
|
||||
}
|
||||
ExOutputModel.getInstance(editor).clear()
|
||||
editor.vim.let { vimEditor ->
|
||||
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
|
||||
vimEditor.exitVisualMode()
|
||||
KeyHandler.getInstance().reset(vimEditor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -21,11 +21,8 @@ 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.diagnostic.logger
|
||||
import com.intellij.openapi.ide.CopyPasteManager
|
||||
import com.intellij.openapi.keymap.KeymapUtil
|
||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||
import com.intellij.openapi.keymap.impl.ui.KeymapPanel
|
||||
import com.intellij.openapi.options.ShowSettingsUtil
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
@@ -35,7 +32,6 @@ 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.handler.KeyMapIssue
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||
import com.maddyhome.idea.vim.key.ShortcutOwner
|
||||
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
|
||||
@@ -184,77 +180,6 @@ internal class NotificationService(private val project: Project?) {
|
||||
ActionIdNotifier.notifyActionId(id, project)
|
||||
}
|
||||
|
||||
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
|
||||
val keymapManager = KeymapManagerEx.getInstanceEx()
|
||||
val keymap = keymapManager.activeKeymap
|
||||
val message = buildString {
|
||||
appendLine("Current IDE keymap (${keymap.name}) has issues:<br/>")
|
||||
issues.forEach {
|
||||
when (it) {
|
||||
is KeyMapIssue.AddShortcut -> {
|
||||
appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
|
||||
}
|
||||
is KeyMapIssue.RemoveShortcut -> {
|
||||
appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val notification = IDEAVIM_STICKY_GROUP.createNotification(
|
||||
IDEAVIM_NOTIFICATION_TITLE,
|
||||
message,
|
||||
NotificationType.ERROR,
|
||||
)
|
||||
notification.subtitle = "IDE keymap misconfigured"
|
||||
notification.addAction(object : DumbAwareAction("Fix Keymap") {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
issues.forEach {
|
||||
when (it) {
|
||||
is KeyMapIssue.AddShortcut -> {
|
||||
keymap.addShortcut(it.actionId, KeyboardShortcut(it.keyStroke, null))
|
||||
}
|
||||
|
||||
is KeyMapIssue.RemoveShortcut -> {
|
||||
keymap.removeShortcut(it.actionId, it.shortcut)
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.info("Shortcuts updated $issues")
|
||||
notification.expire()
|
||||
requiredShortcutsAssigned()
|
||||
}
|
||||
})
|
||||
notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
|
||||
notification.hideBalloon()
|
||||
}
|
||||
})
|
||||
notification.addAction(object : DumbAwareAction("Ignore") {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
LOG.info("Ignored to update shortcuts $issues")
|
||||
notification.hideBalloon()
|
||||
}
|
||||
})
|
||||
notification.notify(project)
|
||||
}
|
||||
|
||||
private fun requiredShortcutsAssigned() {
|
||||
val notification = Notification(
|
||||
IDEAVIM_NOTIFICATION_ID,
|
||||
IDEAVIM_NOTIFICATION_TITLE,
|
||||
"Keymap fixed",
|
||||
NotificationType.INFORMATION,
|
||||
)
|
||||
notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
|
||||
notification.hideBalloon()
|
||||
}
|
||||
})
|
||||
notification.notify(project)
|
||||
}
|
||||
|
||||
object ActionIdNotifier {
|
||||
private var notification: Notification? = null
|
||||
private const val NO_ID = "<i>Cannot detect action id</i>"
|
||||
@@ -389,8 +314,6 @@ internal class NotificationService(private val project: Project?) {
|
||||
const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
|
||||
const val ideajoinExamplesUrl = "https://jb.gg/f9zji9"
|
||||
|
||||
private val LOG = logger<NotificationService>()
|
||||
|
||||
private fun createIdeaVimRcManually(message: String, project: Project?) {
|
||||
val notification =
|
||||
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)
|
||||
|
@@ -40,6 +40,10 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
||||
override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
|
||||
override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
|
||||
|
||||
private fun updateFallbackWindow(fallbackWindow: VimEditor, targetEditor: VimEditor) {
|
||||
copyPerWindowGlobalValues(fallbackWindow, targetEditor)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
||||
// Vim only has one window, and it's not possible to close it. This means that editing a new file will always
|
||||
@@ -54,8 +58,6 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
||||
// Unfortunately, we can't reliably know if a closing editor is the selected editor. Instead, we rely on selection
|
||||
// change events. If an editor is losing selection and there is no new selection, we can assume this means that
|
||||
// the last editor has been closed, and use the closed editor to update the fallback window
|
||||
//
|
||||
// XXX: event.oldEditor will must probably return a disposed editor. So, it should be treated with care
|
||||
if (event.newEditor == null) {
|
||||
(event.oldEditor as? TextEditor)?.editor?.let {
|
||||
(VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
|
||||
@@ -66,7 +68,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
||||
}
|
||||
|
||||
internal class IjOptionConstants {
|
||||
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
|
||||
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate")
|
||||
companion object {
|
||||
|
||||
const val idearefactormode_keep = "keep"
|
||||
|
293
src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.java
Normal file
293
src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.java
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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.group;
|
||||
|
||||
import com.intellij.execution.ExecutionException;
|
||||
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.execution.process.ProcessOutput;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressIndicatorProvider;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.util.execution.ParametersListUtil;
|
||||
import com.intellij.util.text.CharSequenceReader;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext;
|
||||
import com.maddyhome.idea.vim.api.VimEditor;
|
||||
import com.maddyhome.idea.vim.api.VimInjectorKt;
|
||||
import com.maddyhome.idea.vim.api.VimProcessGroupBase;
|
||||
import com.maddyhome.idea.vim.command.Command;
|
||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||
import com.maddyhome.idea.vim.ex.ExException;
|
||||
import com.maddyhome.idea.vim.ex.InvalidCommandException;
|
||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
|
||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions;
|
||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
||||
|
||||
|
||||
public class ProcessGroup extends VimProcessGroupBase {
|
||||
public String getLastCommand() {
|
||||
return lastCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSearchCommand(@NotNull VimEditor editor, ExecutionContext context, int count, char leader) {
|
||||
if (((IjVimEditor)editor).getEditor().isOneLineMode()) // Don't allow searching in one line editors
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String initText = "";
|
||||
String label = String.valueOf(leader);
|
||||
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.activate(((IjVimEditor)editor).getEditor(), ((DataContext)context.getContext()), label, initText, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String endSearchCommand() {
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.deactivate(true);
|
||||
|
||||
return panel.getText();
|
||||
}
|
||||
|
||||
public void startExCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
|
||||
// Don't allow ex commands in one line editors
|
||||
if (editor.isOneLineMode()) return;
|
||||
|
||||
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd);
|
||||
injector.getMarkService().setVisualSelectionMarks(editor);
|
||||
VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, cmd.getCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processExKey(@NotNull VimEditor editor, @NotNull KeyStroke stroke) {
|
||||
// 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.
|
||||
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
if (panel.isActive()) {
|
||||
UiHelper.requestFocus(panel.getEntry());
|
||||
panel.handleKey(stroke);
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
|
||||
KeyHandler.getInstance().reset(editor);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean processExEntry(final @NotNull VimEditor editor, final @NotNull ExecutionContext context) {
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.deactivate(true);
|
||||
boolean res = true;
|
||||
try {
|
||||
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
|
||||
|
||||
logger.debug("processing command");
|
||||
|
||||
String text = panel.getText();
|
||||
|
||||
if (!panel.getLabel().equals(":")) {
|
||||
// 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 new InvalidCommandException("Expected ':' command. Got '" + panel.getLabel() + "'", text);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) logger.debug("swing=" + SwingUtilities.isEventDispatchThread());
|
||||
|
||||
int repeat = 1;
|
||||
if (text.contains("raction ")) {
|
||||
text = text.replace("raction ", "action ");
|
||||
repeat = panel.getCount();
|
||||
}
|
||||
|
||||
for (int i = 0; i < repeat; i++) {
|
||||
VimInjectorKt.getInjector().getVimscriptExecutor().execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext.INSTANCE);
|
||||
}
|
||||
}
|
||||
catch (ExException e) {
|
||||
VimPlugin.showMessage(e.getMessage());
|
||||
VimPlugin.indicateError();
|
||||
res = false;
|
||||
}
|
||||
catch (Exception bad) {
|
||||
ProcessGroup.logger.error(bad);
|
||||
VimPlugin.indicateError();
|
||||
res = false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// commands executed from map command / macro should not be added to history
|
||||
private boolean skipHistory(VimEditor editor) {
|
||||
return VimStateMachine.Companion.getInstance(editor).getMappingState().isExecutingMap() || injector.getMacro().isExecutingMacro();
|
||||
}
|
||||
|
||||
public void cancelExEntry(final @NotNull VimEditor editor, boolean resetCaret) {
|
||||
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
|
||||
KeyHandler.getInstance().reset(editor);
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.deactivate(true, resetCaret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startFilterCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
|
||||
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd) + "!";
|
||||
VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
|
||||
}
|
||||
|
||||
private @NotNull String getRange(Editor editor, @NotNull Command cmd) {
|
||||
String initText = "";
|
||||
if (VimStateMachine.Companion.getInstance(new IjVimEditor(editor)).getMode() instanceof Mode.VISUAL) {
|
||||
initText = "'<,'>";
|
||||
}
|
||||
else if (cmd.getRawCount() > 0) {
|
||||
if (cmd.getCount() == 1) {
|
||||
initText = ".";
|
||||
}
|
||||
else {
|
||||
initText = ".,.+" + (cmd.getCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return initText;
|
||||
}
|
||||
|
||||
public @Nullable String executeCommand(@NotNull VimEditor editor, @NotNull String command, @Nullable CharSequence input, @Nullable String currentDirectoryPath)
|
||||
throws ExecutionException, ProcessCanceledException {
|
||||
|
||||
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
|
||||
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
|
||||
// not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
|
||||
// better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
|
||||
// Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
|
||||
return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
||||
|
||||
final String shell = globalOptions(injector).getShell();
|
||||
final String shellcmdflag = globalOptions(injector).getShellcmdflag();
|
||||
final String shellxescape = globalOptions(injector).getShellxescape();
|
||||
final String shellxquote = globalOptions(injector).getShellxquote();
|
||||
|
||||
// For Win32. See :help 'shellxescape'
|
||||
final String escapedCommand = shellxquote.equals("(")
|
||||
? doEscape(command, shellxescape, "^")
|
||||
: command;
|
||||
// Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
|
||||
final String quotedCommand = shellxquote.equals("(")
|
||||
? "(" + escapedCommand + ")"
|
||||
: (shellxquote.equals("\"(")
|
||||
? "\"(" + escapedCommand + ")\""
|
||||
: shellxquote + escapedCommand + shellxquote);
|
||||
|
||||
final ArrayList<String> commands = new ArrayList<>();
|
||||
commands.add(shell);
|
||||
if (!shellcmdflag.isEmpty()) {
|
||||
// Note that Vim also does a simple whitespace split for multiple parameters
|
||||
commands.addAll(ParametersListUtil.parse(shellcmdflag));
|
||||
}
|
||||
commands.add(quotedCommand);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand));
|
||||
}
|
||||
|
||||
final GeneralCommandLine commandLine = new GeneralCommandLine(commands);
|
||||
if (currentDirectoryPath != null) {
|
||||
commandLine.setWorkDirectory(currentDirectoryPath);
|
||||
}
|
||||
final CapturingProcessHandler handler = new CapturingProcessHandler(commandLine);
|
||||
if (input != null) {
|
||||
handler.addProcessListener(new ProcessAdapter() {
|
||||
@Override
|
||||
public void startNotified(@NotNull ProcessEvent event) {
|
||||
try {
|
||||
final CharSequenceReader charSequenceReader = new CharSequenceReader(input);
|
||||
final BufferedWriter outputStreamWriter = new BufferedWriter(new OutputStreamWriter(handler.getProcessInput()));
|
||||
copy(charSequenceReader, outputStreamWriter);
|
||||
outputStreamWriter.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getInstance().getProgressIndicator();
|
||||
final ProcessOutput output = handler.runProcessWithProgressIndicator(progressIndicator);
|
||||
|
||||
lastCommand = command;
|
||||
|
||||
if (output.isCancelled()) {
|
||||
// TODO: Vim will use whatever text has already been written to stdout
|
||||
// For whatever reason, we're not getting any here, so just throw an exception
|
||||
throw new ProcessCanceledException();
|
||||
}
|
||||
|
||||
final Integer exitCode = handler.getExitCode();
|
||||
if (exitCode != null && exitCode != 0) {
|
||||
VimPlugin.showMessage("shell returned " + exitCode);
|
||||
VimPlugin.indicateError();
|
||||
}
|
||||
|
||||
// Get stderr; stdout and strip colors, which are not handles properly.
|
||||
return (output.getStderr() + output.getStdout()).replaceAll("\u001B\\[[;\\d]*m", "");
|
||||
}, "IdeaVim - !" + command, true, ((IjVimEditor) editor).getEditor().getProject());
|
||||
}
|
||||
|
||||
private String doEscape(String original, String charsToEscape, String escapeChar) {
|
||||
String result = original;
|
||||
for (char c : charsToEscape.toCharArray()) {
|
||||
result = result.replace("" + c, escapeChar + c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Java 10 has a transferTo method we could use instead
|
||||
private void copy(@NotNull Reader from, @NotNull Writer to) throws IOException {
|
||||
char[] buf = new char[2048];
|
||||
int cnt;
|
||||
while ((cnt = from.read(buf)) != -1) {
|
||||
to.write(buf, 0, cnt);
|
||||
}
|
||||
}
|
||||
|
||||
private String lastCommand;
|
||||
|
||||
private static final Logger logger = Logger.getInstance(ProcessGroup.class.getName());
|
||||
}
|
@@ -1,281 +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 com.maddyhome.idea.vim.group
|
||||
|
||||
import com.intellij.execution.ExecutionException
|
||||
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
|
||||
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.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
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.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
|
||||
private set
|
||||
|
||||
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
|
||||
|
||||
val currentMode = editor.vimStateMachine.mode
|
||||
check(currentMode is ReturnableFromCmd) {
|
||||
"Cannot enable cmd mode from current mode $currentMode"
|
||||
}
|
||||
|
||||
val initText = getRange(editor, cmd)
|
||||
injector.markService.setVisualSelectionMarks(editor)
|
||||
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): 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) {
|
||||
requestFocus(panel.entry)
|
||||
panel.handleKey(stroke)
|
||||
|
||||
return true
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
editor.vimStateMachine.mode = NORMAL()
|
||||
getInstance().reset(editor)
|
||||
val panel = ExEntryPanel.getInstance()
|
||||
panel.deactivate(true, resetCaret)
|
||||
}
|
||||
|
||||
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)
|
||||
public override fun executeCommand(
|
||||
editor: VimEditor,
|
||||
command: String,
|
||||
input: CharSequence?,
|
||||
currentDirectoryPath: String?
|
||||
): String? {
|
||||
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
|
||||
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
|
||||
// not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
|
||||
// better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
|
||||
// Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
|
||||
|
||||
return ProgressManager.getInstance().runProcessWithProgressSynchronously<String, ExecutionException>(
|
||||
{
|
||||
val shell = injector.globalOptions().shell
|
||||
val shellcmdflag = injector.globalOptions().shellcmdflag
|
||||
val shellxescape = injector.globalOptions().shellxescape
|
||||
val shellxquote = injector.globalOptions().shellxquote
|
||||
|
||||
// For Win32. See :help 'shellxescape'
|
||||
val escapedCommand = if (shellxquote == "(") doEscape(command, shellxescape, "^")
|
||||
else command
|
||||
// Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
|
||||
val quotedCommand = if (shellxquote == "(") "($escapedCommand)"
|
||||
else (if (shellxquote == "\"(") "\"($escapedCommand)\""
|
||||
else shellxquote + escapedCommand + shellxquote)
|
||||
|
||||
val commands = ArrayList<String>()
|
||||
commands.add(shell)
|
||||
if (shellcmdflag.isNotEmpty()) {
|
||||
// Note that Vim also does a simple whitespace split for multiple parameters
|
||||
commands.addAll(ParametersListUtil.parse(shellcmdflag))
|
||||
}
|
||||
commands.add(quotedCommand)
|
||||
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand))
|
||||
}
|
||||
|
||||
val commandLine = GeneralCommandLine(commands)
|
||||
if (currentDirectoryPath != null) {
|
||||
commandLine.setWorkDirectory(currentDirectoryPath)
|
||||
}
|
||||
val handler = CapturingProcessHandler(commandLine)
|
||||
if (input != null) {
|
||||
handler.addProcessListener(object : ProcessAdapter() {
|
||||
override fun startNotified(event: ProcessEvent) {
|
||||
try {
|
||||
val charSequenceReader = CharSequenceReader(input)
|
||||
val outputStreamWriter = BufferedWriter(OutputStreamWriter(handler.processInput))
|
||||
copy(charSequenceReader, outputStreamWriter)
|
||||
outputStreamWriter.close()
|
||||
} catch (e: IOException) {
|
||||
logger.error(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
|
||||
val output = handler.runProcessWithProgressIndicator(progressIndicator)
|
||||
|
||||
lastCommand = command
|
||||
|
||||
if (output.isCancelled) {
|
||||
// TODO: Vim will use whatever text has already been written to stdout
|
||||
// For whatever reason, we're not getting any here, so just throw an exception
|
||||
throw ProcessCanceledException()
|
||||
}
|
||||
|
||||
val exitCode = handler.exitCode
|
||||
if (exitCode != null && exitCode != 0) {
|
||||
VimPlugin.showMessage("shell returned $exitCode")
|
||||
VimPlugin.indicateError()
|
||||
}
|
||||
(output.stderr + output.stdout).replace("\u001B\\[[;\\d]*m".toRegex(), "")
|
||||
}, "IdeaVim - !$command", true, editor.ij.project
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun doEscape(original: String, charsToEscape: String, escapeChar: String): String {
|
||||
var result = original
|
||||
for (c in charsToEscape.toCharArray()) {
|
||||
result = result.replace("" + c, escapeChar + c)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// TODO: Java 10 has a transferTo method we could use instead
|
||||
@Throws(IOException::class)
|
||||
private fun copy(from: Reader, to: Writer) {
|
||||
val buf = CharArray(2048)
|
||||
var cnt: Int
|
||||
while ((from.read(buf).also { cnt = it }) != -1) {
|
||||
to.write(buf, 0, cnt)
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
private val logger = logger<ProcessGroup>()
|
||||
}
|
||||
}
|
@@ -192,8 +192,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
||||
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
||||
* @param direction The direction to search
|
||||
*/
|
||||
@Override
|
||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
|
||||
@TestOnly
|
||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
|
||||
@NotNull String patternOffset, Direction direction) {
|
||||
setLastUsedPattern(pattern, RE_SEARCH, true);
|
||||
lastIgnoreSmartCase = false;
|
||||
|
@@ -15,7 +15,6 @@ import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory
|
||||
import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.PlaceInfo
|
||||
import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.RecentPlacesListener
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimJumpServiceBase
|
||||
@@ -41,72 +40,60 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
|
||||
}
|
||||
}
|
||||
|
||||
// We do not delete old project records.
|
||||
// Rationale: It's more likely that users will want to review their old projects and access their jump history
|
||||
// (e.g., recent files), than for the 100 jumps (max number of records) to consume enough space to be noticeable.
|
||||
override fun getState(): Element {
|
||||
val projectsElem = Element("projects")
|
||||
for ((project, jumps) in projectToJumps) {
|
||||
val projectElement = Element("project").setAttribute("id", project)
|
||||
for (jump in jumps) {
|
||||
val jumpElem = Element("jump")
|
||||
jumpElem.setAttribute("line", jump.line.toString())
|
||||
jumpElem.setAttribute("column", jump.col.toString())
|
||||
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
|
||||
projectElement.addContent(jumpElem)
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("saved jump = $jump")
|
||||
}
|
||||
val jumpsElem = Element("jumps")
|
||||
for (jump in jumps) {
|
||||
val jumpElem = Element("jump")
|
||||
jumpElem.setAttribute("line", jump.line.toString())
|
||||
jumpElem.setAttribute("column", jump.col.toString())
|
||||
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
|
||||
jumpsElem.addContent(jumpElem)
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("saved jump = $jump")
|
||||
}
|
||||
projectsElem.addContent(projectElement)
|
||||
}
|
||||
return projectsElem
|
||||
return jumpsElem
|
||||
}
|
||||
|
||||
override fun loadState(state: Element) {
|
||||
val projectElements = state.getChildren("project")
|
||||
for (projectElement in projectElements) {
|
||||
val jumps = mutableListOf<Jump>()
|
||||
val jumpElements = projectElement.getChildren("jump")
|
||||
for (jumpElement in jumpElements) {
|
||||
val jump = Jump(
|
||||
Integer.parseInt(jumpElement.getAttributeValue("line")),
|
||||
Integer.parseInt(jumpElement.getAttributeValue("column")),
|
||||
jumpElement.getAttributeValue("filename"),
|
||||
)
|
||||
jumps.add(jump)
|
||||
}
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("jumps=$jumps")
|
||||
}
|
||||
val projectId = projectElement.getAttributeValue("id")
|
||||
projectToJumps[projectId] = jumps
|
||||
val jumpList = state.getChildren("jump")
|
||||
for (jumpElement in jumpList) {
|
||||
val jump = Jump(
|
||||
Integer.parseInt(jumpElement.getAttributeValue("line")),
|
||||
Integer.parseInt(jumpElement.getAttributeValue("column")),
|
||||
jumpElement.getAttributeValue("filename"),
|
||||
)
|
||||
jumps.add(jump)
|
||||
}
|
||||
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("jumps=$jumps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class JumpsListener(val project: Project) : RecentPlacesListener {
|
||||
internal class JumpsListener : RecentPlacesListener {
|
||||
override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) {
|
||||
if (!injector.globalIjOptions().unifyjumps) return
|
||||
|
||||
|
||||
val jumpService = injector.jumpService
|
||||
if (!isChanged) {
|
||||
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
|
||||
// we do not want jumps that were processed before
|
||||
val jump = buildJump(changePlace) ?: return
|
||||
jumpService.addJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump, true)
|
||||
jumpService.addJump(jump, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun recentPlaceRemoved(changePlace: PlaceInfo, isChanged: Boolean) {
|
||||
if (!injector.globalIjOptions().unifyjumps) return
|
||||
|
||||
|
||||
val jumpService = injector.jumpService
|
||||
if (!isChanged) {
|
||||
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
|
||||
// we do not want jumps that were processed before
|
||||
val jump = buildJump(changePlace) ?: return
|
||||
jumpService.removeJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump)
|
||||
jumpService.removeJump(jump)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -205,7 +205,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
||||
* @param event The change event
|
||||
*/
|
||||
override fun beforeDocumentChange(event: DocumentEvent) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
|
||||
if (event.oldLength == 0) return
|
||||
val doc = event.document
|
||||
@@ -221,7 +221,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
||||
* @param event The change event
|
||||
*/
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
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
|
||||
@@ -242,7 +242,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
||||
|
||||
class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
|
||||
override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (!injector.globalIjOptions().ideamarks) {
|
||||
return
|
||||
}
|
||||
@@ -255,7 +255,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
||||
}
|
||||
|
||||
override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (!injector.globalIjOptions().ideamarks) {
|
||||
return
|
||||
}
|
||||
|
@@ -27,12 +27,15 @@ import com.maddyhome.idea.vim.api.getLineEndOffset
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.setChangeMarks
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.isBlock
|
||||
import com.maddyhome.idea.vim.state.mode.isChar
|
||||
import com.maddyhome.idea.vim.state.mode.isLine
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.RWLockLabel
|
||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||
import com.maddyhome.idea.vim.ide.isClionNova
|
||||
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
@@ -45,10 +48,6 @@ import com.maddyhome.idea.vim.put.PutData
|
||||
import com.maddyhome.idea.vim.put.VimPasteProvider
|
||||
import com.maddyhome.idea.vim.put.VimPutBase
|
||||
import com.maddyhome.idea.vim.register.RegisterConstants
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.isBlock
|
||||
import com.maddyhome.idea.vim.state.mode.isChar
|
||||
import com.maddyhome.idea.vim.state.mode.isLine
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
|
||||
internal class PutGroup : VimPutBase() {
|
||||
@@ -190,7 +189,7 @@ internal class PutGroup : VimPutBase() {
|
||||
endOffset: Int,
|
||||
): Int {
|
||||
// Temp fix for VIM-2808. Should be removed after rider will fix it's issues
|
||||
if (PlatformUtils.isRider() || isClionNova()) return endOffset
|
||||
if (PlatformUtils.isRider()) return endOffset
|
||||
|
||||
val startLine = editor.offsetToBufferPosition(startOffset).line
|
||||
val endLine = editor.offsetToBufferPosition(endOffset - 1).line
|
||||
|
@@ -40,15 +40,9 @@ internal object IdeaSelectionControl {
|
||||
* This method should be in sync with [predictMode]
|
||||
*
|
||||
* Control unexpected (non vim) selection change and adjust a mode to it. The new mode is not enabled immediately,
|
||||
* but with some delay (using [VimVisualTimer]). The delay is used because some platform functionality
|
||||
* makes features by using selection. E.g. PyCharm unindent firstly select the indenting then applies delete action.
|
||||
* Such "quick" selection breaks IdeaVim behaviour.
|
||||
* but with some delay (using [VimVisualTimer])
|
||||
*
|
||||
* See [VimVisualTimer] to more info.
|
||||
*
|
||||
* XXX: This method can be split into "change calculation" and "change apply". In this way, we would be able
|
||||
* to calculate if we need to make a change or not and reduce the number of these calls.
|
||||
* If this refactoring ever is applied, please add `assertNull(VimVisualTimer.timer)` to `tearDown` of VimTestCase.
|
||||
*/
|
||||
fun controlNonVimSelectionChange(
|
||||
editor: Editor,
|
||||
@@ -56,7 +50,6 @@ internal object IdeaSelectionControl {
|
||||
) {
|
||||
VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
|
||||
|
||||
if (VimPlugin.isNotEnabled()) return@singleTask
|
||||
if (editor.isIdeaVimDisabledHere) return@singleTask
|
||||
|
||||
logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
|
||||
@@ -128,9 +121,8 @@ internal object IdeaSelectionControl {
|
||||
}
|
||||
}
|
||||
|
||||
private fun dontChangeMode(editor: Editor): Boolean {
|
||||
return editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
|
||||
}
|
||||
private fun dontChangeMode(editor: Editor): Boolean =
|
||||
editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
|
||||
|
||||
private fun chooseNonSelectionMode(editor: Editor): Mode {
|
||||
val templateActive = editor.isTemplateActive()
|
||||
|
@@ -9,10 +9,10 @@
|
||||
package com.maddyhome.idea.vim.group.visual
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
|
||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import java.awt.event.ActionEvent
|
||||
import javax.swing.Timer
|
||||
|
||||
@@ -79,11 +79,6 @@ internal object VimVisualTimer {
|
||||
}
|
||||
}
|
||||
|
||||
fun drop() {
|
||||
swingTimer?.stop()
|
||||
swingTimer = null
|
||||
}
|
||||
|
||||
inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
||||
task(mode)
|
||||
swingTimer = null
|
||||
|
@@ -12,7 +12,6 @@ import com.intellij.serviceContainer.BaseKeyedLazyInstance
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
/**
|
||||
@@ -37,8 +36,6 @@ import javax.swing.KeyStroke
|
||||
* The reason is startup performance. Using the extension points you don't even have to load classes of actions.
|
||||
* So, all actions are loaded on demand, including classes in classloader.
|
||||
*/
|
||||
@Deprecated(message = "Please use CommandOrMotion annotation")
|
||||
@ScheduledForRemoval(inVersion = "2.9.0")
|
||||
internal class ActionBeanClass : BaseKeyedLazyInstance<EditorActionHandlerBase>() {
|
||||
@Attribute("implementation")
|
||||
var implementation: String? = null
|
||||
|
@@ -1,91 +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 com.maddyhome.idea.vim.handler
|
||||
|
||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.keymap.Keymap
|
||||
import com.intellij.openapi.keymap.KeymapManagerListener
|
||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.startup.StartupActivity
|
||||
import com.intellij.util.SingleAlarm
|
||||
import com.jetbrains.rd.util.ConcurrentHashMap
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.key
|
||||
|
||||
|
||||
// We use alarm with delay to avoid many actions in case many events are fired at the same time
|
||||
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
|
||||
internal val correctorRequester = SingleAlarm({ correctCopilotKeymap() }, 1_000)
|
||||
|
||||
private val LOG = logger<CopilotKeymapCorrector>()
|
||||
|
||||
internal class CopilotKeymapCorrector : StartupActivity {
|
||||
override fun runActivity(project: Project) {
|
||||
correctorRequester.request()
|
||||
}
|
||||
}
|
||||
|
||||
internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
|
||||
override fun activeKeymapChanged(keymap: Keymap?) {
|
||||
correctorRequester.request()
|
||||
}
|
||||
|
||||
override fun shortcutChanged(keymap: Keymap, actionId: String) {
|
||||
correctorRequester.request()
|
||||
}
|
||||
|
||||
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
|
||||
correctorRequester.request()
|
||||
}
|
||||
}
|
||||
|
||||
private val copilotHideActionMap = ConcurrentHashMap<String, Unit>()
|
||||
|
||||
/**
|
||||
* See VIM-3206
|
||||
* The user expected to both copilot suggestion and the insert mode to be exited on a single esc.
|
||||
* However, for the moment, the first esc hides copilot suggestion and the second one exits insert mode.
|
||||
* To fix this, we remove the esc shortcut from the copilot action if the IdeaVim is active.
|
||||
*
|
||||
* This workaround is not the best solution, however, I don't see the better way with the current architecture of
|
||||
* actions and EditorHandlers. Firstly, I wanted to suggest to copilot to migrate to EditorActionHandler as well,
|
||||
* but this doesn't seem correct for me because in this case the user will lose an ability to change the shorcut for
|
||||
* it. It seems like copilot has a similar problem as we do - we don't want to make a handler for "Editor enter action",
|
||||
* but a handler for the esc key press. And, moreover, be able to communicate with other plugins about the ordering.
|
||||
* Before this feature is implemented, hiding the copilot suggestion on esc looks like a good workaround.
|
||||
*/
|
||||
private fun correctCopilotKeymap() {
|
||||
// This is needed to initialize the injector in case this verification is called to fast
|
||||
VimPlugin.getInstance()
|
||||
|
||||
if (injector.enabler.isEnabled()) {
|
||||
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
|
||||
val res = keymap.getShortcuts("copilot.disposeInlays")
|
||||
if (res.isEmpty()) return
|
||||
|
||||
|
||||
val escapeShortcut = res.find { it.toString() == "[pressed ESCAPE]" } ?: return
|
||||
keymap.removeShortcut("copilot.disposeInlays", escapeShortcut)
|
||||
copilotHideActionMap[keymap.name] = Unit
|
||||
LOG.info("Remove copilot escape shortcut from keymap ${keymap.name}")
|
||||
}
|
||||
else {
|
||||
copilotHideActionMap.forEach { (name, _) ->
|
||||
val keymap = KeymapManagerEx.getInstanceEx().getKeymap(name) ?: return@forEach
|
||||
val currentShortcuts = keymap.getShortcuts("copilot.disposeInlays")
|
||||
if ("[pressed ESCAPE]" !in currentShortcuts.map { it.toString() }) {
|
||||
keymap.addShortcut("copilot.disposeInlays", KeyboardShortcut(key("<esc>"), null))
|
||||
}
|
||||
LOG.info("Restore copilot escape shortcut in keymap ${keymap.name}")
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,67 +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 com.maddyhome.idea.vim.handler
|
||||
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.startup.ProjectActivity
|
||||
import com.maddyhome.idea.vim.api.key
|
||||
|
||||
/**
|
||||
* Logs the chain of handlers for esc and enter
|
||||
*
|
||||
* As we made a migration to the new way of handling esc keys (VIM-2974), we may face several issues around that
|
||||
* One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
|
||||
* the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
|
||||
*
|
||||
* This is a logger that logs the chain of handlers.
|
||||
*
|
||||
* Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing
|
||||
* otherwise, so let's use it as long as we can.
|
||||
*/
|
||||
internal class EditorHandlersChainLogger : ProjectActivity {
|
||||
@Suppress("UnresolvedPluginConfigReference")
|
||||
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
|
||||
|
||||
override suspend fun execute(project: Project) {
|
||||
val escHandlers = editorHandlers.extensionList
|
||||
.filter { it.action == "EditorEscape" }
|
||||
.joinToString("\n") { it.implementationClass }
|
||||
val enterHandlers = editorHandlers.extensionList
|
||||
.filter { it.action == "EditorEnter" }
|
||||
.joinToString("\n") { it.implementationClass }
|
||||
|
||||
LOG.info("Esc handlers chain:\n$escHandlers")
|
||||
LOG.info("Enter handlers chain:\n$enterHandlers")
|
||||
|
||||
val keymapManager = KeymapManagerEx.getInstanceEx()
|
||||
val keymap = keymapManager.activeKeymap
|
||||
val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE).joinToString()
|
||||
val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER).joinToString()
|
||||
|
||||
LOG.info("Active keymap (${keymap.name}) shortcuts for esc: $keymapShortcutsForEsc, Shortcuts for enter: $keymapShortcutsForEnter")
|
||||
|
||||
val actionsForEsc = keymap.getActionIds(key("<esc>")).joinToString("\n")
|
||||
val actionsForEnter = keymap.getActionIds(key("<enter>")).joinToString("\n")
|
||||
|
||||
LOG.info(
|
||||
"Also keymap (${keymap.name}) has " +
|
||||
"the following actions assigned to esc:\n$actionsForEsc " +
|
||||
"\nand following actions assigned to enter:\n$actionsForEnter"
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG = logger<EditorHandlersChainLogger>()
|
||||
}
|
||||
}
|
@@ -1,155 +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 com.maddyhome.idea.vim.handler
|
||||
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||
import com.intellij.openapi.actionSystem.Shortcut
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.keymap.Keymap
|
||||
import com.intellij.openapi.keymap.KeymapManagerListener
|
||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.startup.ProjectActivity
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.key
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
|
||||
internal val keyCheckRequests = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
/**
|
||||
* This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
|
||||
*/
|
||||
internal class KeymapChecker : ProjectActivity {
|
||||
override suspend fun execute(project: Project) {
|
||||
project.service<KeymapCheckerService>().start()
|
||||
keyCheckRequests.emit(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* At the moment of release 2023.3 there is a problem that starting a coroutine like this
|
||||
* right in the project activity will block this project activity in tests.
|
||||
* To avoid that, there is an intermediate service that will allow to avoid this issue.
|
||||
*
|
||||
* However, in general we should start this coroutine right in the [KeymapChecker]
|
||||
*/
|
||||
@OptIn(FlowPreview::class)
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class KeymapCheckerService(private val cs: CoroutineScope) {
|
||||
fun start() {
|
||||
cs.launch {
|
||||
keyCheckRequests
|
||||
.debounce(5_000)
|
||||
.collectLatest { verifyKeymap() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
|
||||
override fun activeKeymapChanged(keymap: Keymap?) {
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
}
|
||||
|
||||
override fun shortcutChanged(keymap: Keymap, actionId: String) {
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
}
|
||||
|
||||
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After migration to the editor action handlers, we have to make sure that the keymap has a correct configuration.
|
||||
* For example, that esc key is assigned to esc editor action
|
||||
*
|
||||
* Usually this is not a problem because this is a standard mapping, but the problem may appear in a misconfiguration
|
||||
* like it was in VIM-3204
|
||||
*/
|
||||
private fun verifyKeymap() {
|
||||
// This is needed to initialize the injector in case this verification is called to fast
|
||||
VimPlugin.getInstance()
|
||||
|
||||
if (!injector.enabler.isEnabled()) return
|
||||
|
||||
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
|
||||
val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE)
|
||||
val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER)
|
||||
|
||||
val issues = ArrayList<KeyMapIssue>()
|
||||
val correctShortcutMissing = keymapShortcutsForEsc
|
||||
.filterIsInstance<KeyboardShortcut>()
|
||||
.none { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke == null }
|
||||
|
||||
// We also check if there are any shortcuts starting from esc and with a second key. This should also be removed.
|
||||
// For example, VIM-3162 has a case when two escapes were assigned to editor escape action
|
||||
val shortcutsStartingFromEsc = keymapShortcutsForEsc
|
||||
.filterIsInstance<KeyboardShortcut>()
|
||||
.filter { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke != null }
|
||||
if (correctShortcutMissing) {
|
||||
issues += KeyMapIssue.AddShortcut(
|
||||
"esc",
|
||||
"editor escape",
|
||||
IdeActions.ACTION_EDITOR_ESCAPE,
|
||||
key("<esc>")
|
||||
)
|
||||
}
|
||||
shortcutsStartingFromEsc.forEach {
|
||||
issues += KeyMapIssue.RemoveShortcut("editor escape", IdeActions.ACTION_EDITOR_ESCAPE, it)
|
||||
}
|
||||
|
||||
|
||||
val correctEnterShortcutMissing = keymapShortcutsForEnter
|
||||
.filterIsInstance<KeyboardShortcut>()
|
||||
.none { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke == null }
|
||||
val shortcutsStartingFromEnter = keymapShortcutsForEnter
|
||||
.filterIsInstance<KeyboardShortcut>()
|
||||
.filter { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke != null }
|
||||
if (correctEnterShortcutMissing) {
|
||||
issues += KeyMapIssue.AddShortcut(
|
||||
"enter",
|
||||
"editor enter",
|
||||
IdeActions.ACTION_EDITOR_ENTER,
|
||||
key("<enter>")
|
||||
)
|
||||
}
|
||||
shortcutsStartingFromEnter.forEach {
|
||||
issues += KeyMapIssue.RemoveShortcut("editor enter", IdeActions.ACTION_EDITOR_ENTER, it)
|
||||
}
|
||||
|
||||
if (issues.isNotEmpty()) {
|
||||
VimPlugin.getNotifications(null).notifyKeymapIssues(issues)
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed interface KeyMapIssue {
|
||||
data class AddShortcut(
|
||||
val key: String,
|
||||
val action: String,
|
||||
val actionId: String,
|
||||
val keyStroke: KeyStroke,
|
||||
) : KeyMapIssue
|
||||
|
||||
data class RemoveShortcut(
|
||||
val action: String,
|
||||
val actionId: String,
|
||||
val shortcut: Shortcut,
|
||||
): KeyMapIssue
|
||||
}
|
@@ -8,67 +8,27 @@
|
||||
|
||||
package com.maddyhome.idea.vim.handler
|
||||
|
||||
import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
|
||||
import com.intellij.codeInsight.lookup.LookupManager
|
||||
import com.intellij.formatting.LineWrappingUtil
|
||||
import com.intellij.ide.DataManager
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
|
||||
import com.intellij.openapi.editor.actions.SplitLineAction
|
||||
import com.intellij.openapi.editor.impl.CaretModelImpl
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.util.Key
|
||||
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.injector
|
||||
import com.maddyhome.idea.vim.api.key
|
||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.inNormalMode
|
||||
import com.maddyhome.idea.vim.helper.isPrimaryEditor
|
||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.helper.mode
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
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
|
||||
|
||||
internal val commandContinuation = Key.create<EditorActionHandler>("commandContinuation")
|
||||
|
||||
/**
|
||||
* Handler that corrects the shape of the caret in python notebooks.
|
||||
*
|
||||
* By default, py notebooks show a thin caret after entering the cell.
|
||||
* However, we're in normal mode, so this handler fixes it.
|
||||
*/
|
||||
internal class CaretShapeEnterEditorHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
if (VimPlugin.isEnabled()) {
|
||||
invokeLater {
|
||||
editor.updateCaretsVisualAttributes()
|
||||
}
|
||||
}
|
||||
nextHandler.execute(editor, caret, dataContext)
|
||||
}
|
||||
|
||||
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
|
||||
return nextHandler.isEnabled(editor, caret, dataContext)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler doesn't work in tests for ex commands
|
||||
*/
|
||||
internal abstract class OctopusHandler(private val nextHandler: EditorActionHandler?) : EditorActionHandler() {
|
||||
internal abstract class OctopusHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||
|
||||
abstract fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?)
|
||||
open fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
|
||||
@@ -77,254 +37,64 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
|
||||
|
||||
final override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
if (isThisHandlerEnabled(editor, caret, dataContext)) {
|
||||
val executeInInvokeLater = executeInInvokeLater(editor)
|
||||
val executionHandler = {
|
||||
try {
|
||||
(dataContext as? UserDataHolder)?.putUserData(commandContinuation, nextHandler)
|
||||
executeHandler(editor, caret, dataContext)
|
||||
} finally {
|
||||
(dataContext as? UserDataHolder)?.removeUserData(commandContinuation)
|
||||
}
|
||||
}
|
||||
|
||||
if (executeInInvokeLater) {
|
||||
// This `invokeLater` is used to escape the potential `runForEachCaret` function.
|
||||
//
|
||||
// The `runForEachCaret` function is disallowed to be called recursively. However, with this new handler, we lose
|
||||
// control if we execute the code inside this function or not. See IDEA-300030 for details.
|
||||
// This means the code in IdeaVim MUST NOT call `runForEachCaret` function. While this is possible for most cases,
|
||||
// the user may make a mapping to some intellij action where the `runForEachCaret` is called. This breaks
|
||||
// the condition (see VIM-3103 for example).
|
||||
// Since we can't make sure we don't execute `runForEachCaret`, we have to "escape" out of this function. This is
|
||||
// done by scheduling the execution of our code later via the invokeLater function.
|
||||
//
|
||||
// We run this job only once for a primary caret. In the handler itself, we'll multiply the execution by the
|
||||
// number of carets. If we run this job for each caret, we may end up in the issue like VIM-3186.
|
||||
// However, I think that we may do some refactoring to run this job for each caret (if needed).
|
||||
//
|
||||
// For the moment, the known case when the caret is null - work in injected editor - VIM-3195
|
||||
if (caret == null || caret == editor.caretModel.primaryCaret) {
|
||||
ApplicationManager.getApplication().invokeLater(executionHandler)
|
||||
}
|
||||
} else {
|
||||
executionHandler()
|
||||
}
|
||||
executeHandler(editor, caret, dataContext)
|
||||
} else {
|
||||
nextHandler?.execute(editor, caret, dataContext)
|
||||
nextHandler.execute(editor, caret, dataContext)
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeInInvokeLater(editor: Editor): Boolean {
|
||||
// Currently we have a workaround for the PY console VIM-3157
|
||||
val fileName = FileDocumentManager.getInstance().getFile(editor.document)?.name
|
||||
if (
|
||||
fileName == "Python Console.py" || // This is the name in 232+
|
||||
fileName == "Python Console" // This is the name in 231
|
||||
) return false
|
||||
return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
|
||||
}
|
||||
|
||||
@Suppress("RedundantIf")
|
||||
private fun isThisHandlerEnabled(editor: Editor, caret: Caret?, dataContext: DataContext?): Boolean {
|
||||
if (VimPlugin.isNotEnabled()) return false
|
||||
if (!VimPlugin.isEnabled()) return false
|
||||
if (!isHandlerEnabled(editor, dataContext)) return false
|
||||
if (isNotActualKeyPress(dataContext)) return false
|
||||
if (dataContext?.actionStartedFromVim == true) return false
|
||||
if (!enableOctopus) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* In some cases IJ runs handlers to imitate "enter" or other key. In such cases we should not process it on the
|
||||
* IdeaVim side because the user may have mappings on enter the we'll get an unexpected behaviour.
|
||||
* This method should return true if we detect that this handler is called in such case and this is not an
|
||||
* actual keypress from the user.
|
||||
*/
|
||||
private fun isNotActualKeyPress(dataContext: DataContext?): Boolean {
|
||||
if (dataContext != null) {
|
||||
// This flag is set when the enter handlers are executed as a part of moving the comment on the new line
|
||||
val dataManager = DataManager.getInstance()
|
||||
if (dataManager.loadFromDataContext(dataContext, AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
// From VIM-3177
|
||||
val wrapLongLineDuringFormattingInProgress = dataManager
|
||||
.loadFromDataContext(dataContext, LineWrappingUtil.WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY)
|
||||
if (wrapLongLineDuringFormattingInProgress == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
// From VIM-3203
|
||||
val splitLineInProgress = dataManager.loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY)
|
||||
if (splitLineInProgress == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (dataManager.loadFromDataContext(dataContext, StartNewLineDetectorBase.Util.key) == true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (dataContext?.actionStartedFromVim == true) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
final override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
|
||||
return isThisHandlerEnabled(editor, caret, dataContext)
|
||||
|| nextHandler?.isEnabled(editor, caret, dataContext) == true
|
||||
return isThisHandlerEnabled(editor, caret, dataContext) || nextHandler.isEnabled(editor, caret, dataContext)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Known conflicts & solutions:
|
||||
* - Smart step into - set handler after
|
||||
* - Python notebooks - set handler after
|
||||
* - Python notebooks - set handler before - test needed!
|
||||
* - Ace jump - set handler after
|
||||
* - Lookup - doesn't intersect with enter anymore
|
||||
* - App code - set handler after
|
||||
* - Template - doesn't intersect with enter anymore
|
||||
* - rd.client.editor.enter - set handler before. Otherwise, rider will add new line on enter even in normal mode
|
||||
*
|
||||
* This rule is disabled due to VIM-3124
|
||||
* - before terminalEnter - not necessary, but terminalEnter causes "file is read-only" tooltip for readonly files VIM-3122
|
||||
* - `first` is set to satisfy sorting condition "before terminalEnter".
|
||||
*
|
||||
*
|
||||
* DO NOT add handlers that force to add "first" ordering. This doesn't work with jupyterCommandModeEnterKeyHandler (see VIM-3124)
|
||||
*/
|
||||
internal class VimEnterHandler(nextHandler: EditorActionHandler?) : VimKeyHandler(nextHandler) {
|
||||
internal class VimEnterHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
|
||||
override val key: String = "<CR>"
|
||||
|
||||
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
|
||||
if (!super.isHandlerEnabled(editor, dataContext)) return false
|
||||
// This is important for one-line editors, to turn off enter.
|
||||
// Some one-line editors rely on the fact that there are no enter actions registered. For example, hash search in git
|
||||
// See VIM-2974 for example where it was broken
|
||||
return !editor.isOneLineMode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Known conflicts & solutions:
|
||||
*
|
||||
* - Smart step into - set handler after
|
||||
* - Python notebooks - set handler before - yes, we have `<CR>` as "after" and `<esc>` as before. I'm not completely sure
|
||||
* why this combination is correct, but other versions don't work.
|
||||
* - Python notebooks - set handler before - test needed
|
||||
* - Ace jump - set handler after
|
||||
* - Lookup - It disappears after putting our esc before templateEscape. But I'm not sure why it works like that
|
||||
* - App code - Need to review
|
||||
* - Template - Need to review
|
||||
* - before backend.escape - to handle our handlers before Rider processing. Also, without this rule, we get problems like VIM-3146
|
||||
*/
|
||||
internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
|
||||
override val key: String = "<Esc>"
|
||||
|
||||
/**
|
||||
* Also, we need to pass esc to IDE if we're in normal mode and there is nothing to cancel
|
||||
* (e.g. we still can cancel numbers, or cancel the replace character mode)
|
||||
*/
|
||||
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
|
||||
val ideaVimSupportDialog =
|
||||
injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
|
||||
|
||||
return editor.isPrimaryEditor() ||
|
||||
EditorHelper.isFileEditor(editor) && !editor.vim.mode.inNormalMode ||
|
||||
ideaVimSupportDialog && !editor.vim.mode.inNormalMode
|
||||
return editor.mode != CommandState.Mode.COMMAND ||
|
||||
editor.vimStateMachine?.commandBuilder?.count != 0 ||
|
||||
editor.vimStateMachine?.isReplaceCharacter == true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rider (and CLion Nova) uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
|
||||
* designer to get all the esc presses, and if there is a completion close it and do not pass the execution further.
|
||||
* This doesn't work the same as in IJ.
|
||||
* In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this
|
||||
* behaviour causes a lot of complaining from users. Since the rider handler gets execution control, we don't
|
||||
* receive an event and don't exit the insert mode.
|
||||
* To fix it, this special handler exists only for rider and stands before the rider's handler. We don't execute the
|
||||
* handler from rider because the autocompletion is closed automatically anyway.
|
||||
*/
|
||||
internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
|
||||
override val key: String = "<Esc>"
|
||||
|
||||
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
|
||||
return LookupManager.getActiveLookup(editor) != null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty logger for esc presses
|
||||
*
|
||||
* As we made a migration to the new way of handling esc keys (VIM-2974), we may face several issues around that
|
||||
* One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
|
||||
* the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
|
||||
* This handler, that should stand in front of handlers change, just logs the event of pressing the key
|
||||
* and passes the execution.
|
||||
*/
|
||||
internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
LOG.info("Esc pressed")
|
||||
nextHandler.execute(editor, caret, dataContext)
|
||||
}
|
||||
|
||||
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
|
||||
return nextHandler.isEnabled(editor, caret, dataContext)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG = logger<VimEscLoggerHandler>()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround to support "Start New Line" action in normal mode.
|
||||
* IJ executes enter handler on "Start New Line". This causes an issue that IdeaVim thinks that this is just an enter key.
|
||||
* This thing should be refactored, but for now we'll use this workaround VIM-3159
|
||||
*
|
||||
* The Same thing happens with "Start New Line Before Current" action.
|
||||
*/
|
||||
internal class StartNewLineDetector(nextHandler: EditorActionHandler) : StartNewLineDetectorBase(nextHandler)
|
||||
internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandler) :
|
||||
StartNewLineDetectorBase(nextHandler)
|
||||
|
||||
internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
|
||||
nextHandler.execute(editor, caret, dataContext)
|
||||
}
|
||||
|
||||
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
|
||||
return nextHandler.isEnabled(editor, caret, dataContext)
|
||||
}
|
||||
|
||||
object Util {
|
||||
val key = Key.create<Boolean>("vim.is.start.new.line")
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG = logger<VimEscLoggerHandler>()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty logger for enter presses
|
||||
*
|
||||
* As we made a migration to the new way of handling enter keys (VIM-2974), we may face several issues around that
|
||||
* One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
|
||||
* the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
|
||||
* This handler, that should stand in front of handlers change, just logs the event of pressing the key
|
||||
* and passes the execution.
|
||||
*/
|
||||
internal class VimEnterLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
LOG.info("Enter pressed")
|
||||
nextHandler.execute(editor, caret, dataContext)
|
||||
}
|
||||
|
||||
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
|
||||
return nextHandler.isEnabled(editor, caret, dataContext)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG = logger<VimEnterLoggerHandler>()
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : OctopusHandler(nextHandler) {
|
||||
internal abstract class VimKeyHandler(nextHandler: EditorActionHandler) : OctopusHandler(nextHandler) {
|
||||
|
||||
abstract val key: String
|
||||
|
||||
@@ -341,12 +111,27 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
|
||||
}
|
||||
|
||||
internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
|
||||
// CMD line has a different processing mechanizm: the processing actions are registered
|
||||
// for the input field component. These keys are not dispatched via the octopus handler.
|
||||
if (editor.vim.mode is Mode.CMD_LINE) return false
|
||||
if (!enableOctopus) return false
|
||||
when {
|
||||
s.keyCode == KeyEvent.VK_ENTER && s.modifiers == 0 -> return true
|
||||
s.keyCode == KeyEvent.VK_ESCAPE && s.modifiers == 0 -> return true
|
||||
s.keyCode == KeyEvent.VK_ENTER -> return editor.mode in listOf(
|
||||
CommandState.Mode.COMMAND,
|
||||
CommandState.Mode.INSERT,
|
||||
CommandState.Mode.VISUAL,
|
||||
)
|
||||
s.keyCode == KeyEvent.VK_ESCAPE -> return editor.mode in listOf(
|
||||
CommandState.Mode.COMMAND,
|
||||
CommandState.Mode.INSERT,
|
||||
CommandState.Mode.VISUAL,
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Experiment: At the moment, IdeaVim intersects all shortcuts and sends the to [KeyHandler]
|
||||
* However, this doesn't seem to be a good solution as other handlers are overridden by vim.
|
||||
* If this option is enabled, vim will connect to IDE via EditorActionHandler extension point
|
||||
* what seems to be a way better solution as this is a correct way to override editor actions like enter, right, etc.
|
||||
*/
|
||||
internal val enableOctopus: Boolean
|
||||
get() = injector.globalIjOptions().octopushandler
|
||||
|
@@ -8,13 +8,11 @@
|
||||
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.CaretVisualAttributes
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
|
||||
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
|
||||
@@ -81,7 +79,6 @@ private fun Editor.guicursorMode(): GuiCursorMode {
|
||||
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
|
||||
|
||||
private fun Editor.updatePrimaryCaretVisualAttributes() {
|
||||
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
|
||||
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
|
||||
|
||||
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking
|
||||
@@ -89,7 +86,6 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
|
||||
}
|
||||
|
||||
private fun Editor.updateSecondaryCaretsVisualAttributes() {
|
||||
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
|
||||
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
|
||||
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
|
||||
this.caretModel.allCarets.forEach {
|
||||
|
@@ -36,7 +36,7 @@ public val Editor.mode: CommandState.Mode
|
||||
get() {
|
||||
val mode = this.vim.vimStateMachine.mode
|
||||
return when (mode) {
|
||||
is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
|
||||
Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
|
||||
Mode.INSERT -> CommandState.Mode.INSERT
|
||||
is Mode.NORMAL -> CommandState.Mode.COMMAND
|
||||
is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
|
||||
|
@@ -110,7 +110,7 @@ internal fun Editor.isTemplateActive(): Boolean {
|
||||
}
|
||||
|
||||
private fun vimEnabled(editor: Editor?): Boolean {
|
||||
if (VimPlugin.isNotEnabled()) return false
|
||||
if (!VimPlugin.isEnabled()) return false
|
||||
if (editor != null && editor.isIdeaVimDisabledHere) return false
|
||||
return true
|
||||
}
|
||||
|
@@ -15,12 +15,10 @@ import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.AnActionResult
|
||||
import com.intellij.openapi.actionSystem.DataContextWrapper
|
||||
import com.intellij.openapi.actionSystem.EmptyAction
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.UndoConfirmationPolicy
|
||||
import com.intellij.openapi.components.Service
|
||||
@@ -41,8 +39,6 @@ import com.maddyhome.idea.vim.newapi.IjNativeAction
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.runFromVimKey
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.awt.Component
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
@Service
|
||||
@@ -143,7 +139,7 @@ internal class IjActionExecutor : VimActionExecutor {
|
||||
manager.fireAfterActionPerformed(action, event, result!!)
|
||||
}
|
||||
if (indexError != null) {
|
||||
ActionUtil.showDumbModeWarning(project, action, event)
|
||||
ActionUtil.showDumbModeWarning(project, event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,43 +150,10 @@ internal class IjActionExecutor : VimActionExecutor {
|
||||
* @param context The context to run it in
|
||||
*/
|
||||
override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
|
||||
val action = getAction(name, context)
|
||||
val aMgr = ActionManager.getInstance()
|
||||
val action = aMgr.getAction(name)
|
||||
return action != null && executeAction(null, IjNativeAction(action), context)
|
||||
}
|
||||
|
||||
private fun getAction(name: String, context: ExecutionContext): AnAction? {
|
||||
val actionManager = ActionManager.getInstance()
|
||||
val action = actionManager.getAction(name)
|
||||
if (action !is EmptyAction) return action
|
||||
|
||||
// But if the action is an instance of EmptyAction, the fun begins
|
||||
var component: Component? = context.ij.getData(PlatformDataKeys.CONTEXT_COMPONENT) ?: return null
|
||||
while (component != null) {
|
||||
if (component !is JComponent) {
|
||||
component = component.parent
|
||||
continue
|
||||
}
|
||||
|
||||
val listOfActions = ActionUtil.getActions(component)
|
||||
if (listOfActions.isEmpty()) {
|
||||
component = component.getParent()
|
||||
continue
|
||||
}
|
||||
|
||||
fun AnAction.getId(): String? {
|
||||
return actionManager.getId(this)
|
||||
?: (shortcutSet as? ProxyShortcutSet)?.actionId
|
||||
}
|
||||
|
||||
for (action in listOfActions) {
|
||||
if (action.getId() == name) {
|
||||
return action
|
||||
}
|
||||
}
|
||||
component = component.getParent()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun executeCommand(
|
||||
editor: VimEditor?,
|
||||
|
@@ -9,7 +9,6 @@
|
||||
package com.maddyhome.idea.vim.helper;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
|
||||
import com.intellij.lang.CodeDocumentationAwareCommenter;
|
||||
import com.intellij.lang.Commenter;
|
||||
import com.intellij.lang.Language;
|
||||
@@ -17,15 +16,15 @@ import com.intellij.lang.LanguageCommenters;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
||||
import com.maddyhome.idea.vim.api.VimEditor;
|
||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||
import com.maddyhome.idea.vim.common.CharacterPosition;
|
||||
import com.maddyhome.idea.vim.common.Direction;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
@@ -33,12 +32,6 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||
import com.maddyhome.idea.vim.regexp.CharPointer;
|
||||
import com.maddyhome.idea.vim.regexp.RegExp;
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import it.unimi.dsi.fastutil.ints.IntComparator;
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSet;
|
||||
import kotlin.Pair;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -1530,42 +1523,6 @@ public class SearchHelper {
|
||||
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
|
||||
}
|
||||
|
||||
public static int findMisspelledWords(@NotNull Editor editor,
|
||||
int startOffset,
|
||||
int endOffset,
|
||||
int skipCount,
|
||||
IntComparator offsetOrdering) {
|
||||
Project project = editor.getProject();
|
||||
if (project == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
|
||||
DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
|
||||
startOffset, endOffset, highlight -> {
|
||||
if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
|
||||
int offset = highlight.getStartOffset();
|
||||
if (offset >= startOffset && offset <= endOffset) {
|
||||
offsets.add(offset);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (offsets.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (skipCount >= offsets.size()) {
|
||||
return offsets.lastInt();
|
||||
}
|
||||
else {
|
||||
IntIterator offsetIterator = offsets.iterator();
|
||||
offsetIterator.skip(skipCount);
|
||||
return offsetIterator.nextInt();
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
|
||||
List<String> pairs = options(injector, vimEditor).getMatchpairs();
|
||||
StringBuilder res = new StringBuilder();
|
||||
|
@@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.helper
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.impl.UndoManagerImpl
|
||||
import com.intellij.openapi.command.undo.UndoManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||
@@ -19,6 +20,7 @@ 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.common.ChangesListener
|
||||
import com.maddyhome.idea.vim.group.IjOptions
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
@@ -31,6 +33,15 @@ import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||
*/
|
||||
@Service
|
||||
internal class UndoRedoHelper : UndoRedoBase() {
|
||||
init {
|
||||
fun onOldUndoChanged() {
|
||||
UndoManagerImpl.ourNeverAskUser = !injector.globalIjOptions().oldundo
|
||||
}
|
||||
|
||||
injector.optionGroup.addGlobalOptionChangeListener(IjOptions.oldundo, ::onOldUndoChanged)
|
||||
onOldUndoChanged()
|
||||
}
|
||||
|
||||
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||
val ijContext = context.context as DataContext
|
||||
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
||||
@@ -44,19 +55,19 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
||||
restoreVisualMode(editor)
|
||||
} else {
|
||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||
editor.runWithChangeTracking {
|
||||
undoManager.undo(fileEditor)
|
||||
performUntilFileChanges(editor, { undoManager.isUndoAvailable(fileEditor) }, { undoManager.undo(fileEditor) })
|
||||
|
||||
// We execute undo one more time if the previous one just restored selection
|
||||
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
|
||||
undoManager.undo(fileEditor)
|
||||
editor.carets().forEach {
|
||||
val ijCaret = it.ij
|
||||
val hasSelection = ijCaret.hasSelection()
|
||||
if (hasSelection) {
|
||||
val selectionStart = ijCaret.selectionStart
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
it.ij.removeSelection()
|
||||
it.ij.moveToOffset(selectionStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
}
|
||||
}
|
||||
|
||||
scrollingModel.flushViewportChanges()
|
||||
@@ -66,10 +77,6 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun hasSelection(editor: VimEditor): Boolean {
|
||||
return editor.primaryCaret().ij.hasSelection()
|
||||
}
|
||||
|
||||
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||
val ijContext = context.context as DataContext
|
||||
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
||||
@@ -80,48 +87,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
|
||||
restoreVisualMode(editor)
|
||||
} else {
|
||||
undoManager.redo(fileEditor)
|
||||
performUntilFileChanges(editor, { undoManager.isRedoAvailable(fileEditor) }, { undoManager.redo(fileEditor) })
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
editor.carets().forEach { it.ij.removeSelection() }
|
||||
}
|
||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||
editor.runWithChangeTracking {
|
||||
undoManager.redo(fileEditor)
|
||||
|
||||
// We execute undo one more time if the previous one just restored selection
|
||||
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
|
||||
undoManager.redo(fileEditor)
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun removeSelections(editor: VimEditor) {
|
||||
editor.carets().forEach {
|
||||
val ijCaret = it.ij
|
||||
if (!ijCaret.hasSelection()) return@forEach
|
||||
private fun performUntilFileChanges(editor: VimEditor?, check: () -> Boolean, action: Runnable) {
|
||||
if (editor == null) return
|
||||
val vimDocument = editor.document
|
||||
|
||||
val selectionStart = ijCaret.selectionStart
|
||||
ijCaret.removeSelection()
|
||||
ijCaret.moveToOffset(selectionStart)
|
||||
}
|
||||
}
|
||||
|
||||
private fun VimEditor.runWithChangeTracking(block: ChangeTracker.() -> Unit) {
|
||||
val tracker = ChangeTracker(this)
|
||||
tracker.block()
|
||||
}
|
||||
|
||||
private class ChangeTracker(private val editor: VimEditor) {
|
||||
private val initialPath = editor.getPath()
|
||||
private val changeListener = object : ChangesListener {
|
||||
val changeListener = object : ChangesListener {
|
||||
var hasChanged = false
|
||||
|
||||
override fun documentChanged(change: ChangesListener.Change) {
|
||||
@@ -129,12 +109,16 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
editor.document.addChangeListener(changeListener)
|
||||
val oldPath = editor.getPath()
|
||||
vimDocument.addChangeListener(changeListener)
|
||||
while (check() && !changeListener.hasChanged && !ifFilePathChanged(editor, oldPath)) {
|
||||
action.run()
|
||||
}
|
||||
vimDocument.removeChangeListener(changeListener)
|
||||
}
|
||||
|
||||
val hasChanges: Boolean
|
||||
get() = changeListener.hasChanged || initialPath != editor.getPath()
|
||||
private fun ifFilePathChanged(editor: VimEditor, oldPath: String?): Boolean {
|
||||
return editor.getPath() != oldPath
|
||||
}
|
||||
|
||||
private fun restoreVisualMode(editor: VimEditor) {
|
||||
|
@@ -124,6 +124,10 @@ internal var Editor.vimMorePanel: ExOutputPanel? by userData()
|
||||
internal var Editor.vimExOutput: ExOutputModel? by userData()
|
||||
internal var Editor.vimTestInputModel: TestInputModel? by userData()
|
||||
|
||||
/**
|
||||
* Checks whether a keeping visual mode visual operator action is performed on editor.
|
||||
*/
|
||||
internal var Editor.vimKeepingVisualOperatorAction: Boolean by userDataOr { false }
|
||||
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
|
||||
|
||||
/**
|
||||
|
@@ -1,25 +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.ide
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
|
||||
internal val clionEP = ExtensionPointName.create<ClionNovaProvider>("IdeaVIM.clionNovaProvider")
|
||||
|
||||
internal interface ClionNovaProvider {
|
||||
fun isClionNova(): Boolean
|
||||
}
|
||||
|
||||
internal class ClionNovaProviderImpl : ClionNovaProvider {
|
||||
override fun isClionNova(): Boolean = true
|
||||
}
|
||||
|
||||
internal fun isClionNova(): Boolean {
|
||||
return clionEP.extensions.any { it.isClionNova() }
|
||||
}
|
@@ -40,7 +40,7 @@ internal object AppCodeTemplates {
|
||||
private var editor: Editor? = null
|
||||
|
||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
|
||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||
if (hostEditor != null) {
|
||||
@@ -49,7 +49,7 @@ internal object AppCodeTemplates {
|
||||
}
|
||||
|
||||
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
|
||||
if (ActionManager.getInstance().getId(action) == IdeActions.ACTION_CHOOSE_LOOKUP_ITEM) {
|
||||
val myEditor = editor
|
||||
|
@@ -56,16 +56,14 @@ internal object IdeaSpecifics {
|
||||
private val surrounderAction =
|
||||
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
||||
private var editor: Editor? = null
|
||||
private var caretOffset = -1
|
||||
private var completionPrevDocumentLength: Int? = null
|
||||
private var completionPrevDocumentOffset: Int? = null
|
||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
|
||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||
if (hostEditor != null) {
|
||||
editor = hostEditor
|
||||
caretOffset = hostEditor.caretModel.offset
|
||||
}
|
||||
|
||||
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
||||
@@ -94,58 +92,53 @@ internal object IdeaSpecifics {
|
||||
}
|
||||
|
||||
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
|
||||
val editor = editor
|
||||
if (editor != null) {
|
||||
if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
|
||||
val prevDocumentLength = completionPrevDocumentLength
|
||||
val prevDocumentOffset = completionPrevDocumentOffset
|
||||
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
|
||||
val prevDocumentLength = completionPrevDocumentLength
|
||||
val prevDocumentOffset = completionPrevDocumentOffset
|
||||
|
||||
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
||||
val register = VimPlugin.getRegister()
|
||||
val addedTextLength = editor.document.textLength - prevDocumentLength
|
||||
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
||||
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
||||
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
||||
val register = VimPlugin.getRegister()
|
||||
val addedTextLength = editor.document.textLength - prevDocumentLength
|
||||
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
||||
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
||||
|
||||
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
||||
repeat(caretShift.coerceAtLeast(0)) {
|
||||
register.recordKeyStroke(leftArrow)
|
||||
}
|
||||
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
||||
repeat(caretShift.coerceAtLeast(0)) {
|
||||
register.recordKeyStroke(leftArrow)
|
||||
}
|
||||
}
|
||||
|
||||
this.completionPrevDocumentLength = null
|
||||
this.completionPrevDocumentOffset = null
|
||||
}
|
||||
|
||||
//region Enter insert mode after surround with if
|
||||
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
||||
action.templatePresentation.text.endsWith(
|
||||
it,
|
||||
)
|
||||
}
|
||||
) {
|
||||
val commandState = editor.vim.vimStateMachine
|
||||
commandState.mode = Mode.NORMAL()
|
||||
VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
//endregion
|
||||
|
||||
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
|
||||
injector.scroll.scrollCaretIntoView(editor.vim)
|
||||
}
|
||||
this.completionPrevDocumentLength = null
|
||||
this.completionPrevDocumentOffset = null
|
||||
}
|
||||
|
||||
//region Enter insert mode after surround with if
|
||||
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
||||
action.templatePresentation.text.endsWith(
|
||||
it,
|
||||
)
|
||||
}
|
||||
) {
|
||||
editor?.let {
|
||||
val commandState = it.vim.vimStateMachine
|
||||
commandState.mode = Mode.NORMAL()
|
||||
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
|
||||
KeyHandler.getInstance().reset(it.vim)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
this.editor = null
|
||||
this.caretOffset = -1
|
||||
}
|
||||
}
|
||||
|
||||
//region Enter insert mode for surround templates without selection
|
||||
class VimTemplateManagerListener : TemplateManagerListener {
|
||||
override fun templateStarted(state: TemplateState) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
val editor = state.editor ?: return
|
||||
|
||||
state.addTemplateStateListener(object : TemplateEditingAdapter() {
|
||||
@@ -183,7 +176,7 @@ internal object IdeaSpecifics {
|
||||
//region Register shortcuts for lookup and perform partial reset
|
||||
class LookupTopicListener : LookupManagerListener {
|
||||
override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
|
||||
// Lookup opened
|
||||
if (oldLookup == null && newLookup is LookupImpl) {
|
||||
@@ -206,7 +199,7 @@ internal object IdeaSpecifics {
|
||||
//region Hide Vim search highlights when showing IntelliJ search results
|
||||
class VimFindModelListener : FindModelListener {
|
||||
override fun findNextModelChanged() {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
VimPlugin.getSearch().clearSearchHighlight()
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ internal class RiderActionListener : AnActionListener {
|
||||
|
||||
private var editor: Editor? = null
|
||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
|
||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||
if (hostEditor != null) {
|
||||
@@ -36,7 +36,7 @@ internal class RiderActionListener : AnActionListener {
|
||||
}
|
||||
|
||||
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
|
||||
//region Extend Selection for Rider
|
||||
when (ActionManager.getInstance().getId(action)) {
|
||||
|
@@ -9,7 +9,6 @@
|
||||
package com.maddyhome.idea.vim.listener
|
||||
|
||||
import com.intellij.ide.ui.UISettings
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.trace
|
||||
@@ -29,9 +28,8 @@ import com.intellij.openapi.editor.event.EditorMouseMotionListener
|
||||
import com.intellij.openapi.editor.event.SelectionEvent
|
||||
import com.intellij.openapi.editor.event.SelectionListener
|
||||
import com.intellij.openapi.editor.ex.DocumentEx
|
||||
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
|
||||
import com.intellij.openapi.editor.ex.FocusChangeListener
|
||||
import com.intellij.openapi.editor.impl.EditorComponentImpl
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||
@@ -42,11 +40,14 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
|
||||
import com.intellij.openapi.fileEditor.impl.EditorComposite
|
||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.rd.createLifetime
|
||||
import com.intellij.openapi.rd.createNestedDisposable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.removeUserData
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.ExceptionUtil
|
||||
import com.jetbrains.rd.util.lifetime.intersect
|
||||
import com.maddyhome.idea.vim.EventFacade
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimKeyListener
|
||||
@@ -61,7 +62,6 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.group.EditorGroup
|
||||
import com.maddyhome.idea.vim.group.FileGroup
|
||||
import com.maddyhome.idea.vim.group.IjOptions
|
||||
import com.maddyhome.idea.vim.group.MotionGroup
|
||||
import com.maddyhome.idea.vim.group.OptionGroup
|
||||
import com.maddyhome.idea.vim.group.ScrollGroup
|
||||
@@ -70,8 +70,6 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
|
||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
|
||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
|
||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
|
||||
import com.maddyhome.idea.vim.handler.correctorRequester
|
||||
import com.maddyhome.idea.vim.handler.keyCheckRequests
|
||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||
@@ -97,9 +95,6 @@ import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
|
||||
import com.maddyhome.idea.vim.vimDisposable
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.SwingUtilities
|
||||
@@ -134,14 +129,11 @@ internal object VimListenerManager {
|
||||
fun turnOn() {
|
||||
GlobalListeners.enable()
|
||||
EditorListeners.addAll()
|
||||
correctorRequester.request()
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
}
|
||||
|
||||
fun turnOff() {
|
||||
GlobalListeners.disable()
|
||||
EditorListeners.removeAll()
|
||||
correctorRequester.request()
|
||||
}
|
||||
|
||||
object GlobalListeners {
|
||||
@@ -159,13 +151,6 @@ internal object VimListenerManager {
|
||||
optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||
optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||
|
||||
// This code is executed after ideavimrc execution, so we trigger onGlobalOptionChanged just in case
|
||||
optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
|
||||
optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
|
||||
modeWidgetOptionListener.onGlobalOptionChanged()
|
||||
macroWidgetOptionListener.onGlobalOptionChanged()
|
||||
|
||||
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
||||
|
||||
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
|
||||
@@ -173,8 +158,6 @@ internal object VimListenerManager {
|
||||
busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
|
||||
|
||||
EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
|
||||
val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
|
||||
eventMulticaster?.addFocusChangeListener(VimFocusListener, VimPlugin.getInstance().onOffDisposable)
|
||||
}
|
||||
|
||||
fun disable() {
|
||||
@@ -185,12 +168,10 @@ internal object VimListenerManager {
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
|
||||
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object EditorListeners {
|
||||
fun addAll() {
|
||||
val initialisedEditors = mutableSetOf<Editor>()
|
||||
@@ -229,67 +210,48 @@ internal object VimListenerManager {
|
||||
}
|
||||
|
||||
fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
|
||||
// As I understand, there is no need to pass a disposable that also disposes on editor close
|
||||
// because all editor resources will be garbage collected anyway on editor close
|
||||
val disposable = editor.project?.vimDisposable ?: return
|
||||
|
||||
val listenersDisposable = Disposer.newDisposable(disposable)
|
||||
editor.putUserData(editorListenersDisposable, listenersDisposable)
|
||||
|
||||
Disposer.register(listenersDisposable) {
|
||||
if (VimListenerTestObject.enabled) {
|
||||
VimListenerTestObject.disposedCounter += 1
|
||||
}
|
||||
}
|
||||
val pluginLifetime = VimPlugin.getInstance().createLifetime()
|
||||
val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
|
||||
val disposable = editorLifetime.intersect(pluginLifetime).createNestedDisposable("MyLifetimedDisposable")
|
||||
|
||||
editor.contentComponent.addKeyListener(VimKeyListener)
|
||||
Disposer.register(listenersDisposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
|
||||
Disposer.register(disposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
|
||||
|
||||
// Initialise the local options. We MUST do this before anything has the chance to query options
|
||||
val vimEditor = editor.vim
|
||||
VimPlugin.getOptionGroup().initialiseLocalOptions(vimEditor, openingEditor, scenario)
|
||||
VimPlugin.getOptionGroup().initialiseLocalOptions(editor.vim, openingEditor, scenario)
|
||||
|
||||
val eventFacade = EventFacade.getInstance()
|
||||
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, listenersDisposable)
|
||||
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, listenersDisposable)
|
||||
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, listenersDisposable)
|
||||
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, listenersDisposable)
|
||||
eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
|
||||
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, disposable)
|
||||
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, disposable)
|
||||
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, disposable)
|
||||
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, disposable)
|
||||
eventFacade.addCaretListener(editor, EditorCaretHandler, disposable)
|
||||
|
||||
VimPlugin.getEditor().editorCreated(editor)
|
||||
|
||||
VimPlugin.getChange().editorCreated(editor, listenersDisposable)
|
||||
VimPlugin.getChange().editorCreated(editor, disposable)
|
||||
|
||||
injector.listenersNotifier.notifyEditorCreated(vimEditor)
|
||||
|
||||
Disposer.register(listenersDisposable) {
|
||||
Disposer.register(disposable) {
|
||||
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(editor: Editor, isReleased: Boolean) {
|
||||
val editorDisposable = editor.getUserData(editorListenersDisposable)
|
||||
if (editorDisposable != null) {
|
||||
Disposer.dispose(editorDisposable)
|
||||
}
|
||||
else StrictMode.fail("Editor doesn't have disposable attached. $editor")
|
||||
editor.contentComponent.removeKeyListener(VimKeyListener)
|
||||
val eventFacade = EventFacade.getInstance()
|
||||
eventFacade.removeEditorMouseListener(editor, EditorMouseHandler)
|
||||
eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler)
|
||||
eventFacade.removeEditorSelectionListener(editor, EditorSelectionHandler)
|
||||
eventFacade.removeComponentMouseListener(editor.contentComponent, ComponentMouseListener)
|
||||
eventFacade.removeCaretListener(editor, EditorCaretHandler)
|
||||
|
||||
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
|
||||
}
|
||||
}
|
||||
|
||||
private object VimFocusListener : FocusChangeListener {
|
||||
override fun focusGained(editor: Editor) {
|
||||
injector.listenersNotifier.notifyEditorFocusGained(editor.vim)
|
||||
|
||||
VimPlugin.getChange().editorReleased(editor)
|
||||
}
|
||||
|
||||
override fun focusLost(editor: Editor) {
|
||||
injector.listenersNotifier.notifyEditorFocusLost(editor.vim)
|
||||
}
|
||||
}
|
||||
|
||||
val editorListenersDisposable = Key.create<Disposable>("IdeaVim listeners disposable")
|
||||
|
||||
object VimCaretListener : CaretListener {
|
||||
override fun caretAdded(event: CaretEvent) {
|
||||
if (vimDisabled(event.editor)) return
|
||||
@@ -304,7 +266,7 @@ internal object VimListenerManager {
|
||||
|
||||
class VimFileEditorManagerListener : FileEditorManagerListener {
|
||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
|
||||
val newEditor = event.newEditor
|
||||
if (newEditor is TextEditor) {
|
||||
@@ -382,15 +344,13 @@ internal object VimListenerManager {
|
||||
}
|
||||
|
||||
override fun editorReleased(event: EditorFactoryEvent) {
|
||||
val vimEditor = event.editor.vim
|
||||
injector.listenersNotifier.notifyEditorReleased(vimEditor)
|
||||
injector.markService.editorReleased(vimEditor)
|
||||
injector.markService.editorReleased(event.editor.vim)
|
||||
}
|
||||
|
||||
override fun fileOpenedSync(
|
||||
source: FileEditorManager,
|
||||
file: VirtualFile,
|
||||
editorsWithProviders: List<FileEditorWithProvider>,
|
||||
editorsWithProviders: List<FileEditorWithProvider>
|
||||
) {
|
||||
// This callback is called once all editors are created for a file being opened. The EditorComposite has been
|
||||
// created (and the list of editors and providers is passed here) and added to an EditorWindow tab, inside a
|
||||
@@ -448,7 +408,6 @@ internal object VimListenerManager {
|
||||
*/
|
||||
override fun selectionChanged(selectionEvent: SelectionEvent) {
|
||||
if (selectionEvent.editor.isIdeaVimDisabledHere) return
|
||||
VimVisualTimer.drop()
|
||||
val editor = selectionEvent.editor
|
||||
val document = editor.document
|
||||
val ijVimEditor = IjVimEditor(editor)
|
||||
@@ -742,11 +701,6 @@ internal object VimListenerManager {
|
||||
}
|
||||
}
|
||||
|
||||
internal object VimListenerTestObject {
|
||||
var enabled: Boolean = false
|
||||
var disposedCounter = 0
|
||||
}
|
||||
|
||||
private object MouseEventsDataHolder {
|
||||
const val skipEvents = 3
|
||||
var skipNDragEvents = skipEvents
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user