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

Compare commits

..

15 Commits

Author SHA1 Message Date
4ca05ba6df Set plugin version to chylex-19 2023-11-15 01:18:20 +01:00
114ac40990 Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2023-11-15 00:47:52 +01:00
2829109eb7 Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2023-11-14 23:41:00 +01:00
f1323139b4 Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2023-11-10 02:34:50 +01:00
df4aa59310 Add support for count for visual and line motion surround 2023-11-09 09:18:04 +01:00
7a3bb5b2d7 Fix(VIM-2933): Reloading/sourcing .ideavimrc does not initialize new plugins 2023-10-28 11:41:44 +02:00
c4b05957fc Reset insert mode when switching active editor 2023-10-28 11:40:27 +02:00
db83b89931 Prevent IdeaVIM from stealing key binding that confirms in-place refactoring 2023-10-28 11:40:27 +02:00
ce27c3e5ba Add operating system type to has() function 2023-10-28 11:40:27 +02:00
31358bc983 Remove update checker 2023-10-28 11:40:27 +02:00
f7e1c9c837 [VIM-696] Restore visual mode after undo/redo, and disable incompatible actions 2023-10-28 11:40:27 +02:00
c46109caa3 Change matchit plugin to use HTML patterns in unrecognized files 2023-10-28 11:40:27 +02:00
9f7ca83306 Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2023-10-28 11:40:27 +02:00
b5761f20d2 Add support for repeatable actions with ':raction' 2023-10-28 11:40:26 +02:00
305c6d2bf9 Set custom plugin version 2023-10-28 11:40:26 +02:00
447 changed files with 2328 additions and 7346 deletions

View File

@@ -14,7 +14,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- name: Fetch origin repo

View File

@@ -12,7 +12,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- uses: actions/checkout@v3

View File

@@ -12,7 +12,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- uses: actions/checkout@v2

View File

@@ -12,7 +12,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- uses: actions/checkout@v2

View File

@@ -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"

View File

@@ -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:

View File

@@ -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:

View File

@@ -14,7 +14,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- name: Fetch origin repo

View File

@@ -14,7 +14,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- name: Fetch origin repo

View File

@@ -15,7 +15,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- uses: actions/checkout@v3

View File

@@ -15,7 +15,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- uses: actions/checkout@v3

View File

@@ -12,7 +12,6 @@ jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps:
- uses: actions/checkout@v2

3
.gitignore vendored
View File

@@ -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

View File

@@ -1,6 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright 2003-&amp;#36;today.year The IdeaVim authors&#10;&#10;Use of this source code is governed by an MIT-style&#10;license that can be found in the LICENSE.txt file or at&#10;https://opensource.org/licenses/MIT." />
<option name="notice" value="Copyright 2003-2023 The IdeaVim authors&#10;&#10;Use of this source code is governed by an MIT-style&#10;license that can be found in the LICENSE.txt file or at&#10;https://opensource.org/licenses/MIT." />
<option name="myName" value="IdeaVim" />
</copyright>
</component>

View File

@@ -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"
}

View File

@@ -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 {

View File

@@ -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

View File

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

View File

@@ -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"
}
}
}

View File

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

View File

@@ -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"
}

View File

@@ -483,14 +483,6 @@ Contributors:
[![icon][github]](https://github.com/ludwig-jb)
&nbsp;
ludwig-jb
* [![icon][mail]](mailto:pvydmuch@gmail.com)
[![icon][github]](https://github.com/pWydmuch)
&nbsp;
pWydmuch
* [![icon][mail]](mailto:leonid989@gmail.com)
[![icon][github]](https://github.com/Infonautica)
&nbsp;
Leonid Danilov
Previous contributors:

View File

@@ -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

View File

@@ -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`.

View File

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

View File

@@ -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'),
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,2 @@
com.intellij.vim.providers.CommandOrMotionProcessorProvider
com.intellij.vim.providers.ExCommandProcessorProvider
com.intellij.vim.providers.VimscriptFunctionProcessorProvider
com.intellij.vim.providers.ExCommandProcessorProvider

View File

@@ -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 {

View File

@@ -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]]

View File

@@ -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>

View File

@@ -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:
```

View File

@@ -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=

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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

View 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);
}
}

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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,
}

View File

@@ -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,

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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 {

View File

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

View File

@@ -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

View File

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

View File

@@ -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")

View File

@@ -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));

View File

@@ -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)
}
/**

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

@@ -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"

View 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());
}

View File

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

View File

@@ -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;

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

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

View File

@@ -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?,

View File

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

View File

@@ -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) {

View File

@@ -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()
/**

View File

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

View File

@@ -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

View File

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

View File

@@ -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)) {

View File

@@ -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