1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2026-04-08 10:13:08 +02:00

Compare commits

..

31 Commits

Author SHA1 Message Date
7c4efe496d Set plugin version to chylex-54 2026-03-30 07:58:58 +02:00
19aa1f928c Fix pumvisible returning opposite result 2026-03-30 07:58:49 +02:00
b7d17bbb6e Preserve visual mode after executing IDE action 2026-03-30 07:58:49 +02:00
91546dd0d7 Make g0/g^/g$ work with soft wraps 2026-03-30 07:58:49 +02:00
89e1511860 Make gj/gk jump over soft wraps 2026-03-30 07:58:49 +02:00
507bbff1c3 Make camelCase motions adjust based on direction of visual selection 2026-03-30 07:58:49 +02:00
716956a30f Make search highlights temporary 2026-03-30 07:58:49 +02:00
dd33e39850 Do not switch to normal mode after inserting a live template 2026-03-30 07:58:49 +02:00
ebc77454ab Exit insert mode after refactoring 2026-03-29 19:45:22 +02:00
c9193cb6d4 Add action to run last macro in all opened files 2026-03-29 19:45:22 +02:00
13246c0a80 Stop macro execution after a failed search 2026-03-29 19:45:22 +02:00
b0ff57a4f5 Revert per-caret registers 2026-03-29 19:45:22 +02:00
f4e0684ca8 Apply scrolloff after executing native IDEA actions 2026-03-29 19:45:22 +02:00
3a3e7952b1 Automatically add unambiguous imports after running a macro 2026-03-29 19:45:22 +02:00
1ff6066e33 Fix(VIM-3986): Exception when pasting register contents containing new line 2026-03-29 19:45:21 +02:00
3a9abba410 Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2026-03-29 19:45:21 +02:00
510f8f948e Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2026-03-29 19:45:21 +02:00
b623bf739c Update search register when using f/t 2026-03-29 19:45:21 +02:00
c99d97b3bc Add support for count for visual and line motion surround 2026-03-29 19:45:21 +02:00
6b8eb8952f Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2026-03-29 19:45:21 +02:00
25d70ee975 Fix(VIM-696): Restore visual mode after undo/redo, and disable incompatible actions 2026-03-29 19:45:21 +02:00
cbc9637d17 Respect count with <Action> mappings 2026-03-29 19:45:21 +02:00
0d893d9961 Change matchit plugin to use HTML patterns in unrecognized files 2026-03-29 19:45:21 +02:00
4ac3a1eaaa Fix ex command panel causing Undock tool window to hide 2026-03-29 19:45:21 +02:00
86a6e9643f Reset insert mode when switching active editor 2026-03-29 19:45:21 +02:00
8b06078607 Remove notifications about configuration options 2026-03-29 19:45:20 +02:00
924455907a Remove AI 2026-03-29 19:45:20 +02:00
40367859b8 Set custom plugin version 2026-03-29 19:45:20 +02:00
45f7934d71 Revert "Fix(VIM-4108): Use default ANTLR output directory for Gradle 9+ compatibility"
This reverts commit a476583ea3.
2026-03-27 21:40:07 +01:00
0880e5f935 Revert "Upgrade Gradle wrapper to 9.2.1"
This reverts commit 517bda93
2026-03-27 21:40:07 +01:00
8af3788379 Revert "Fix(VIM-4109): Configure test source sets for Gradle 9+ compatibility"
This reverts commit 5c0d9569d9.
2026-03-27 21:40:07 +01:00
33 changed files with 107 additions and 567 deletions

View File

@@ -1,16 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Split Frontend Debugger" type="Remote" folderName="Split Mode">
<module name="ideavim" />
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="localhost" />
<option name="PORT" value="5006" />
<option name="AUTO_RESTART" value="false" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="5006" />
<option name="LOCAL" value="false" />
</RunnerSettings>
<method v="2" />
</configuration>
</component>

View File

@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start CLion with IdeaVim" type="GradleRunConfiguration" factoryName="Gradle" folderName="Platforms">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runClion" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,25 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start CLion with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle" folderName="Platforms (Split)">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runCLionSplitMode" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start IJ with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle" folderName="Split Mode"> <configuration default="false" name="Start IJ with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" /> <log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings> <ExternalSystemSettings>
<option name="executionName" /> <option name="executionName" />

View File

@@ -1,25 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start IJ with IdeaVim (Split Mode Debug Frontend)" type="GradleRunConfiguration" factoryName="Gradle" folderName="Split Mode">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runIdeSplitModeDebugFrontend" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start PyCharm with IdeaVim" type="GradleRunConfiguration" factoryName="Gradle" folderName="Platforms">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runPycharm" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,25 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start PyCharm with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle" folderName="Platforms (Split)">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runPycharmSplitMode" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start Rider with IdeaVim" type="GradleRunConfiguration" factoryName="Gradle" folderName="Platforms">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runRider" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start WebStorm with IdeaVim" type="GradleRunConfiguration" factoryName="Gradle" folderName="Platforms">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runWebstorm" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,25 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start WebStorm with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle" folderName="Platforms (Split)">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runWebstormSplitMode" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@@ -6,7 +6,6 @@
* https://opensource.org/licenses/MIT. * https://opensource.org/licenses/MIT.
*/ */
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
import org.jetbrains.intellij.platform.gradle.TestFrameworkType import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
@@ -27,11 +26,11 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.6.0.202603022253-r") classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.6.0.202603022253-r")
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:3.4.2") classpath("io.ktor:ktor-client-core:3.4.1")
classpath("io.ktor:ktor-client-cio:3.4.2") classpath("io.ktor:ktor-client-cio:3.4.1")
classpath("io.ktor:ktor-client-auth:3.4.2") classpath("io.ktor:ktor-client-auth:3.4.1")
classpath("io.ktor:ktor-client-content-negotiation:3.4.2") classpath("io.ktor:ktor-client-content-negotiation:3.4.1")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.4.2") classpath("io.ktor:ktor-serialization-kotlinx-json:3.4.1")
// This comes from the changelog plugin // This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1") // classpath("org.jetbrains:markdown:0.3.1")
@@ -113,7 +112,7 @@ dependencies {
testFramework(TestFrameworkType.Platform) testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5) testFramework(TestFrameworkType.JUnit5)
compatiblePlugin("com.intellij.classic.ui") plugin("com.intellij.classic.ui", "261.22158.185")
pluginModule(runtimeOnly(project(":modules:ideavim-common"))) pluginModule(runtimeOnly(project(":modules:ideavim-common")))
pluginModule(runtimeOnly(project(":modules:ideavim-frontend"))) pluginModule(runtimeOnly(project(":modules:ideavim-frontend")))
@@ -226,30 +225,6 @@ tasks {
// localPath = file("/Users/{user}/Applications/WebStorm.app") // localPath = file("/Users/{user}/Applications/WebStorm.app")
// } // }
val runPycharm by intellijPlatformTesting.runIde.registering {
type = IntelliJPlatformType.PyCharmProfessional
version = "2025.3.2"
task {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
}
}
val runWebstorm by intellijPlatformTesting.runIde.registering {
type = IntelliJPlatformType.WebStorm
version = "2025.3.2"
task {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
}
}
val runClion by intellijPlatformTesting.runIde.registering {
type = IntelliJPlatformType.CLion
version = "2025.3.2"
task {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
}
}
val runIdeForUiTests by intellijPlatformTesting.runIde.registering { val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
task { task {
jvmArgumentProviders += CommandLineArgumentProvider { jvmArgumentProviders += CommandLineArgumentProvider {
@@ -272,55 +247,6 @@ tasks {
val runIdeSplitMode by intellijPlatformTesting.runIde.registering { val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
splitMode = true splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
}
val runWebstormSplitMode by intellijPlatformTesting.runIde.registering {
type = IntelliJPlatformType.WebStorm
version = "2025.3.2"
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
}
val runRider by intellijPlatformTesting.runIde.registering {
type = IntelliJPlatformType.Rider
version = "2026.1"
task {
systemProperty("idea.log.debug.categories", "com.maddyhome.idea.vim.handler.EditorHandlersChainLogger")
}
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
}
val runCLionSplitMode by intellijPlatformTesting.runIde.registering {
type = IntelliJPlatformType.CLion
version = "2025.3.2"
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
}
val runPycharmSplitMode by intellijPlatformTesting.runIde.registering {
type = IntelliJPlatformType.PyCharmProfessional
version = "2025.3.2"
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
} }
// Run split mode with a JDWP debug agent on the frontend (JetBrains Client) process. // Run split mode with a JDWP debug agent on the frontend (JetBrains Client) process.
@@ -329,11 +255,6 @@ tasks {
splitMode = true splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
prepareSandboxTask { prepareSandboxTask {
val sandboxDir = project.layout.buildDirectory.dir("idea-sandbox").map { it.asFile } val sandboxDir = project.layout.buildDirectory.dir("idea-sandbox").map { it.asFile }
doLast { doLast {
@@ -365,12 +286,6 @@ tasks {
val testIdeSplitMode by intellijPlatformTesting.testIde.registering { val testIdeSplitMode by intellijPlatformTesting.testIde.registering {
splitMode = true splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
task { task {
useJUnitPlatform() useJUnitPlatform()
} }

View File

@@ -20,7 +20,7 @@ ideaVersion=2026.1
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IU ideaType=IU
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-55 version=chylex-54
javaVersion=21 javaVersion=21
remoteRobotVersion=0.11.23 remoteRobotVersion=0.11.23
antlrVersion=4.10.1 antlrVersion=4.10.1

2
gradlew vendored
View File

@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.

View File

@@ -165,27 +165,25 @@ internal class FileRemoteApiImpl : FileRemoteApi {
// ======================== Private helpers ======================== // ======================== Private helpers ========================
private fun findFile(filename: String, project: Project): VirtualFile? { private fun findFile(filename: String, project: Project): VirtualFile? {
var found: VirtualFile?
if (filename.startsWith("~/") || filename.startsWith("~\\")) { if (filename.startsWith("~/") || filename.startsWith("~\\")) {
val relativePath = filename.substring(2) val relativePath = filename.substring(2)
val dir = System.getProperty("user.home") val dir = System.getProperty("user.home")
logger.debug { "home dir file" } logger.debug { "home dir file" }
logger.debug { "looking for $relativePath in $dir" } logger.debug { "looking for $relativePath in $dir" }
return LocalFileSystem.getInstance().refreshAndFindFileByNioFile(Path(dir, relativePath)) found = LocalFileSystem.getInstance().refreshAndFindFileByNioFile(Path(dir, relativePath))
} else {
found = VirtualFileManager.getInstance().findFileByNioPath(Path(filename))
if (found == null) {
found = findByNameInContentRoots(filename, project)
if (found == null) {
found = findByNameInProject(filename, project)
}
}
} }
val basePath = project.basePath return found
if (basePath != null) {
val baseDir = LocalFileSystem.getInstance().refreshAndFindFileByNioFile(Path(basePath))
baseDir?.findFileByRelativePath(filename)?.let { return it }
}
VirtualFileManager.getInstance().findFileByNioPath(Path(filename))?.let { return it }
findByNameInContentRoots(filename, project)?.let { return it }
findByNameInProject(filename, project)?.let { return it }
return null
} }
private fun buildFileInfoMessage(editor: Editor, project: Project, fullPath: Boolean): String { private fun buildFileInfoMessage(editor: Editor, project: Project, fullPath: Boolean): String {

View File

@@ -8,7 +8,7 @@
<idea-plugin> <idea-plugin>
<dependencies> <dependencies>
<plugin id="com.intellij.modules.rider"/> <module name="com.intellij.modules.rider"/>
</dependencies> </dependencies>
<projectListeners> <projectListeners>
<listener class="com.maddyhome.idea.vim.listener.RiderActionListener" <listener class="com.maddyhome.idea.vim.listener.RiderActionListener"

View File

@@ -26,11 +26,11 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:6.0.3") testImplementation("org.junit.jupiter:junit-jupiter:6.0.3")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("io.ktor:ktor-client-core:3.4.2") implementation("io.ktor:ktor-client-core:3.4.1")
implementation("io.ktor:ktor-client-cio:3.4.2") implementation("io.ktor:ktor-client-cio:3.4.1")
implementation("io.ktor:ktor-client-content-negotiation:3.4.2") implementation("io.ktor:ktor-client-content-negotiation:3.4.1")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.4.2") implementation("io.ktor:ktor-serialization-kotlinx-json:3.4.1")
implementation("io.ktor:ktor-client-auth:3.4.2") implementation("io.ktor:ktor-client-auth:3.4.1")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // This is needed for jgit to connect to ssh

View File

@@ -22,7 +22,6 @@ import com.intellij.openapi.util.Disposer;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.config.VimState; import com.maddyhome.idea.vim.config.VimState;
import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator; import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator;
import com.maddyhome.idea.vim.group.KeyGroup;
import com.maddyhome.idea.vim.group.VimNotifications; import com.maddyhome.idea.vim.group.VimNotifications;
import com.maddyhome.idea.vim.group.VimWindowGroup; import com.maddyhome.idea.vim.group.VimWindowGroup;
import com.maddyhome.idea.vim.history.VimHistory; import com.maddyhome.idea.vim.history.VimHistory;
@@ -131,12 +130,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return VimInjectorKt.getInjector().getHistoryGroup(); return VimInjectorKt.getInjector().getHistoryGroup();
} }
public static @NotNull KeyGroup getKey() { public static @NotNull VimKeyGroup getKey() {
return ((KeyGroup)VimInjectorKt.getInjector().getKeyGroup()); return VimInjectorKt.getInjector().getKeyGroup();
} }
public static @Nullable KeyGroup getKeyIfCreated() { public static @Nullable VimKeyGroup getKeyIfCreated() {
return ApplicationManager.getApplication().getServiceIfCreated(KeyGroup.class); return ApplicationManager.getApplication().getServiceIfCreated(VimKeyGroup.class);
} }
public static @NotNull VimWindowGroup getWindow() { public static @NotNull VimWindowGroup getWindow() {
@@ -338,7 +337,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
} }
} }
if (element.getChild("shortcut-conflicts") != null) { if (element.getChild("shortcut-conflicts") != null) {
getKey().loadShortcutConflictsData(element); ((VimKeyGroupBase)getKey()).loadShortcutConflictsData(element);
} }
if (element.getChild("editor") != null) { if (element.getChild("editor") != null) {
getEditor().loadEditorStateData(element); getEditor().loadEditorStateData(element);

View File

@@ -11,7 +11,6 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.ExtensionPointListener import com.intellij.openapi.extensions.ExtensionPointListener
import com.intellij.openapi.extensions.PluginDescriptor import com.intellij.openapi.extensions.PluginDescriptor
import com.intellij.vim.api.VimInitApi
import com.maddyhome.idea.vim.api.VimExtensionRegistrator import com.maddyhome.idea.vim.api.VimExtensionRegistrator
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setToggleOption import com.maddyhome.idea.vim.api.setToggleOption
@@ -21,6 +20,7 @@ import com.maddyhome.idea.vim.key.MappingOwner.Plugin.Companion.remove
import com.maddyhome.idea.vim.options.OptionAccessScope import com.maddyhome.idea.vim.options.OptionAccessScope
import com.maddyhome.idea.vim.options.OptionDeclaredScope import com.maddyhome.idea.vim.options.OptionDeclaredScope
import com.maddyhome.idea.vim.options.ToggleOption import com.maddyhome.idea.vim.options.ToggleOption
import com.intellij.vim.api.VimInitApi
import com.maddyhome.idea.vim.statistic.ExtensionTracking import com.maddyhome.idea.vim.statistic.ExtensionTracking
import com.maddyhome.idea.vim.thinapi.VimApiImpl import com.maddyhome.idea.vim.thinapi.VimApiImpl
@@ -106,13 +106,9 @@ class VimExtensionRegistrar : VimExtensionRegistrator {
override fun enableDelayedExtensions() { override fun enableDelayedExtensions() {
delayedExtensionEnabling.forEach { delayedExtensionEnabling.forEach {
val name = it.name ?: it.instance.name val name = it.name ?: it.instance.name
try { val initApi = createVimApi(name)
val initApi = createVimApi(name) it.instance.init(initApi)
it.instance.init(initApi) logger.info("IdeaVim extension '$name' initialized")
logger.info("IdeaVim extension '$name' initialized")
} catch (e: Throwable) {
logger.error("Failed to initialize IdeaVim extension '$name'", e)
}
} }
delayedExtensionEnabling.clear() delayedExtensionEnabling.clear()
} }

View File

@@ -241,17 +241,13 @@ internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(n
/** /**
* Rider (and CLion Nova) uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially * Rider (and CLion Nova) uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
* designed to get all the esc presses, and if there is a completion close it and do not pass the execution further. * 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. * 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 * 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 * 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. * 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 * 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. * handler from rider because the autocompletion is closed automatically anyway.
*
* NOTE: This handler only works when octopus is enabled (non-Rider IDEs). For Rider, where octopus is disabled
* (VIM-3815) and Escape is consumed by the popup manager before the EditorEscape chain fires, the fix is in
* [com.maddyhome.idea.vim.listener.IdeaSpecifics.LookupTopicListener] via a LookupListener.
*/ */
internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) { internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
override val key: String = "<Esc>" override val key: String = "<Esc>"

View File

@@ -50,8 +50,6 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.helper.exitSelectMode import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.ide.isClionNova
import com.maddyhome.idea.vim.ide.isRider
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@@ -352,16 +350,6 @@ internal object IdeaSpecifics {
if (newLookup.editor.isIdeaVimDisabledHere) return if (newLookup.editor.isIdeaVimDisabledHere) return
(VimPlugin.getKey() as VimKeyGroupBase).registerShortcutsForLookup(newLookup) (VimPlugin.getKey() as VimKeyGroupBase).registerShortcutsForLookup(newLookup)
// In Rider/CLion Nova, octopus is disabled (VIM-3815) and Escape is consumed by the popup manager
// (due to LookupSummaryInfo popup) before the action system runs, so IdeaVim never sees it.
// Listen for explicit lookup cancellation (Escape) to exit insert mode.
// Note: we check isRider/isClionNova specifically, not !isOctopusEnabled(), because
// JetBrains Client (split mode) also has octopus disabled but doesn't need this workaround,
// and isCanceledExplicitly can be true for non-Escape keys (e.g. space) in that environment.
if (isRider() || isClionNova()) {
newLookup.addLookupListener(RiderEscLookupListener(newLookup.editor))
}
} }
// Lookup closed // Lookup closed
@@ -373,20 +361,6 @@ internal object IdeaSpecifics {
} }
} }
} }
/**
* In Rider/CLion Nova, octopus is disabled (VIM-3815) and Escape is consumed by the popup manager
* (due to LookupSummaryInfo parameter info popup) before the action system runs, so IdeaVim never sees it.
* This listener exits insert mode when the lookup is explicitly cancelled (Escape).
*/
private class RiderEscLookupListener(private val editor: Editor) : com.intellij.codeInsight.lookup.LookupListener {
override fun lookupCanceled(event: com.intellij.codeInsight.lookup.LookupEvent) {
if (event.isCanceledExplicitly && editor.vim.mode is Mode.INSERT) {
editor.vim.exitInsertMode(injector.executionContextManager.getEditorExecutionContext(editor.vim))
KeyHandler.getInstance().reset(editor.vim)
}
}
}
//endregion //endregion
//region Hide Vim search highlights when showing IntelliJ search results //region Hide Vim search highlights when showing IntelliJ search results

View File

@@ -12,7 +12,6 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.VisualPosition
import com.maddyhome.idea.vim.api.BufferPosition import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.LocalMarkStorage import com.maddyhome.idea.vim.api.LocalMarkStorage
import com.maddyhome.idea.vim.api.SelectionInfo import com.maddyhome.idea.vim.api.SelectionInfo
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
@@ -199,12 +198,3 @@ class IjVimCaret(val caret: Caret) : VimCaretBase() {
override fun hashCode(): Int = this.caret.hashCode() override fun hashCode(): Int = this.caret.hashCode()
} }
val Caret.vim: VimCaret
get() = VimEditorFactory.getInstance().createVimCaret(this)
val VimCaret.ij: Caret
get() = VimEditorFactory.getInstance().extractCaret(this)
val ImmutableVimCaret.ij: Caret
get() = VimEditorFactory.getInstance().extractCaret(this)

View File

@@ -671,12 +671,3 @@ class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase() {
} }
} }
val Editor.vim: VimEditor
get() = VimEditorFactory.getInstance().createVimEditor(this)
val VimEditor.ij: Editor
get() = VimEditorFactory.getInstance().extractEditor(this)
val com.intellij.openapi.util.TextRange.vim: TextRange
get() = TextRange(this.startOffset, this.endOffset)

View File

@@ -14,6 +14,7 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.TextRange
interface VimEditorFactory { interface VimEditorFactory {
fun createVimEditor(editor: Editor): VimEditor fun createVimEditor(editor: Editor): VimEditor
@@ -26,3 +27,21 @@ interface VimEditorFactory {
fun getInstance(): VimEditorFactory = service() fun getInstance(): VimEditorFactory = service()
} }
} }
val Editor.vim: VimEditor
get() = VimEditorFactory.getInstance().createVimEditor(this)
val VimEditor.ij: Editor
get() = VimEditorFactory.getInstance().extractEditor(this)
val Caret.vim: VimCaret
get() = VimEditorFactory.getInstance().createVimCaret(this)
val VimCaret.ij: Caret
get() = VimEditorFactory.getInstance().extractCaret(this)
val ImmutableVimCaret.ij: Caret
get() = VimEditorFactory.getInstance().extractCaret(this)
val com.intellij.openapi.util.TextRange.vim: TextRange
get() = TextRange(this.startOffset, this.endOffset)

View File

@@ -194,7 +194,7 @@ class ExTextField internal constructor(private val myParentPanel: ExEntryPanel)
// handler adds all non-control characters to the text field. We want to add all characters, so if we have an // handler adds all non-control characters to the text field. We want to add all characters, so if we have an
// actual character, just add it. Anything else, we'll pass to the super class like before (even though it's unclear // actual character, just add it. Anything else, we'll pass to the super class like before (even though it's unclear
// what it will do with the keystroke) // what it will do with the keystroke)
if (stroke.keyChar != KeyEvent.CHAR_UNDEFINED && !isKeyCharEnterOrEscape(stroke.keyChar)) { if (stroke.keyChar != KeyEvent.CHAR_UNDEFINED) {
replaceSelection(stroke.keyChar.toString()) replaceSelection(stroke.keyChar.toString())
} else { } else {
val event = KeyEvent( val event = KeyEvent(

View File

@@ -1,17 +0,0 @@
<!--
~ Copyright 2003-2026 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.
-->
<!-- Split-mode (Remote Dev) specific registrations.
This module only loads when intellij.platform.frontend.split is available,
which provides access to intellij.rd.client and its extension points. -->
<idea-plugin package="com.maddyhome.idea.vim">
<dependencies>
<module name="intellij.platform.frontend.split"/>
<module name="IdeaVIM.ideavim-frontend"/>
</dependencies>
</idea-plugin>

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2003-2026 The IdeaVim authors * Copyright 2003-2023 The IdeaVim authors
* *
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at * license that can be found in the LICENSE.txt file or at
@@ -10,12 +10,9 @@ package org.jetbrains.plugins.ideavim.action.motion.search
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class SearchEntryFwdActionTest : VimTestCase() { class SearchEntryFwdActionTest : VimTestCase() {
@Test @Test
@@ -113,23 +110,6 @@ class SearchEntryFwdActionTest : VimTestCase() {
) )
} }
@Test
fun `test escape after search not found closes panel without inserting escape char`() {
configureByText("lorem ipsum dolor sit amet")
typeText("/notfound")
val panel = ExEntryPanel.getOrCreatePanelInstance()
assertTrue(panel.isActive)
typeText("<Esc>")
assertFalse(panel.isActive)
assertMode(Mode.NORMAL())
// The panel text should not contain ^[ (escape character written as text)
assertFalse(panel.text.contains("\u001B"), "Panel text should not contain escape character")
assertFalse(panel.text.contains("^["), "Panel text should not contain ^[ literal")
}
@Disabled("Ctrl-o doesn't work yet in select mode") @Disabled("Ctrl-o doesn't work yet in select mode")
@Test @Test
fun `search in one time from select mode`() { fun `search in one time from select mode`() {

View File

@@ -22,11 +22,11 @@ class JumpNavigationSplitTest : IdeaVimStarterTestBase() {
openFile(longFile("Jump1")) openFile(longFile("Jump1"))
typeVim("G") typeVim("G")
pause(500) pause(300)
assertCaretAfter(40, "G should go to end of file") assertCaretAfter(40, "G should go to end of file")
ctrlO() ctrlO()
pause(500) pause(300)
assertCaretBefore(10, "Ctrl-O should jump back to start") assertCaretBefore(10, "Ctrl-O should jump back to start")
} }
@@ -35,7 +35,7 @@ class JumpNavigationSplitTest : IdeaVimStarterTestBase() {
openFile(longFile("Jump2")) openFile(longFile("Jump2"))
typeVim("gg") typeVim("gg")
pause(500) pause(300)
typeVim("/Line 30\n") typeVim("/Line 30\n")
pause() pause()

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2003-2026 The IdeaVim authors * Copyright 2003-2023 The IdeaVim authors
* *
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at * license that can be found in the LICENSE.txt file or at
@@ -13,22 +13,14 @@ import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.isCommandLineActionChar
import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.KeyHandlerState
import javax.swing.KeyStroke import javax.swing.KeyStroke
/** @CommandOrMotion(keys = ["<C-K>"], modes = [Mode.INSERT, Mode.CMD_LINE])
* Insert mode: insert a digraph character via `<C-K>`
*
* The converted digraph character is re-injected through the key handler so that it is processed as typed input in
* Insert mode (handled by the change group).
*/
@CommandOrMotion(keys = ["<C-K>"], modes = [Mode.INSERT])
class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() { class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT override val type: Command.Type = Command.Type.INSERT
@@ -37,6 +29,22 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
// We're waiting for it to complete and give us a CHARACTER // We're waiting for it to complete and give us a CHARACTER
override val argumentType: Argument.Type = Argument.Type.DIGRAPH override val argumentType: Argument.Type = Argument.Type.DIGRAPH
/**
* Perform additional initialisation when starting to wait for an argument
*
* IdeaVim has two ways of handling digraphs/literals. Actions such as `r` or `f` can accept a digraph, which really
* means it accepts a character, but the user can use `<C-K>`/`<C-V>` to type a digraph or literal and convert it into
* a character. Unfortunately, there is no mode that can be used to register an "insert digraph/literal" action for
* these keys while replace or find is active. So the key handler hard codes these keys and will check for them when
* an action expects a digraph (and like Vim, these keys cannot be mapped). Once the state machine has matched a
* character, the expected argument is reset to [Argument.Type.CHARACTER] and the character is passed through the key
* handler again, potentially mapped, and then attached as an argument to the current command, which is now complete
* and executed.
*
* In Insert and Command-line mode, the `<C-K>` and `<C-V>` keys are actions that will wait for a character argument,
* and then insert it. Commands are only executed once complete, so we use [onStartWaitingForArgument] to start the
* digraph state machine. This also gives us a repeatable command and captures the keys for `'showcmd'`.
*/
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
val result = keyState.digraphSequence.startDigraphSequence() val result = keyState.digraphSequence.startDigraphSequence()
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter) KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
@@ -48,6 +56,7 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
cmd: Command, cmd: Command,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
// The converted digraph character has been captured as an argument, push it back through key handler
val argument = cmd.argument as? Argument.Character ?: return false val argument = cmd.argument as? Argument.Character ?: return false
val keyStroke = KeyStroke.getKeyStroke(argument.character) val keyStroke = KeyStroke.getKeyStroke(argument.character)
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
@@ -55,37 +64,3 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
return true return true
} }
} }
/**
* Command-line mode: insert a digraph character via `<C-K>`
*
* Control characters like Escape or Enter are inserted directly into the command line to avoid being matched as
* commands. Other characters use [VimCommandLine.handleKey] so that overwrite mode is handled correctly.
*/
@CommandOrMotion(keys = ["<C-K>"], modes = [Mode.CMD_LINE])
class CmdLineCompletedDigraphAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
val result = keyState.digraphSequence.startDigraphSequence()
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
}
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
val argument = cmd.argument as? Argument.Character ?: return false
val commandLine = injector.commandLine.getActiveCommandLine() ?: return false
val ch = argument.character
if (ch.isCommandLineActionChar()) {
commandLine.insertText(commandLine.caret.offset, ch.toString())
} else {
commandLine.handleKey(KeyStroke.getKeyStroke(ch))
}
return true
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2003-2026 The IdeaVim authors * Copyright 2003-2023 The IdeaVim authors
* *
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at * license that can be found in the LICENSE.txt file or at
@@ -13,22 +13,14 @@ import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.isCommandLineActionChar
import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.KeyHandlerState
import javax.swing.KeyStroke import javax.swing.KeyStroke
/** @CommandOrMotion(keys = ["<C-V>", "<C-Q>"], modes = [Mode.INSERT, Mode.CMD_LINE])
* Insert mode: insert a literal character via `<C-V>` / `<C-Q>`
*
* The converted literal character is re-injected through the key handler so that it is processed as typed input in
* Insert mode (handled by the change group).
*/
@CommandOrMotion(keys = ["<C-V>", "<C-Q>"], modes = [Mode.INSERT])
class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() { class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT override val type: Command.Type = Command.Type.INSERT
@@ -37,6 +29,22 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
// We're waiting for it to complete and give us a CHARACTER // We're waiting for it to complete and give us a CHARACTER
override val argumentType: Argument.Type = Argument.Type.DIGRAPH override val argumentType: Argument.Type = Argument.Type.DIGRAPH
/**
* Perform additional initialisation when starting to wait for an argument
*
* IdeaVim has two ways of handling digraphs/literals. Actions such as `r` or `f` can accept a digraph, which really
* means it accepts a character, but the user can use `<C-K>`/`<C-V>` to type a digraph or literal and convert it into
* a character. Unfortunately, there is no mode that can be used to register an "insert digraph/literal" action for
* these keys while replace or find is active. So the key handler hard codes these keys and will check for them when
* an action expects a digraph (and like Vim, these keys cannot be mapped). Once the state machine has matched a
* character, the expected argument is reset to [Argument.Type.CHARACTER] and the character is passed through the key
* handler again, potentially mapped, and then attached as an argument to the current command, which is now complete
* and executed.
*
* In Insert and Command-line mode, the `<C-K>` and `<C-V>` keys are actions that will wait for a character argument,
* and then insert it. Commands are only executed once complete, so we use [onStartWaitingForArgument] to start the
* digraph state machine. This also gives us a repeatable command and captures the keys for `'showcmd'`.
*/
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
val result = keyState.digraphSequence.startLiteralSequence() val result = keyState.digraphSequence.startLiteralSequence()
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter) KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
@@ -48,6 +56,7 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
cmd: Command, cmd: Command,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
// The converted literal character has been captured as an argument, push it back through key handler
val argument = cmd.argument as? Argument.Character ?: return false val argument = cmd.argument as? Argument.Character ?: return false
val keyStroke = KeyStroke.getKeyStroke(argument.character) val keyStroke = KeyStroke.getKeyStroke(argument.character)
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
@@ -55,39 +64,3 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
return true return true
} }
} }
/**
* Command-line mode: insert a literal character via `<C-V>` / `<C-Q>`
*
* Control characters like Escape or Enter are inserted directly into the command line to avoid being matched as
* commands (e.g., LeaveCommandLineAction). Other characters use [VimCommandLine.handleKey] so that overwrite mode
* is handled correctly.
*/
@CommandOrMotion(keys = ["<C-V>", "<C-Q>"], modes = [Mode.CMD_LINE])
class CmdLineCompletedLiteralAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
val result = keyState.digraphSequence.startLiteralSequence()
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
}
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
val argument = cmd.argument as? Argument.Character ?: return false
val commandLine = injector.commandLine.getActiveCommandLine() ?: return false
val ch = argument.character
if (ch.isCommandLineActionChar()) {
// Insert directly to avoid these being matched as commands by the key handler
commandLine.insertText(commandLine.caret.offset, ch.toString())
} else {
commandLine.handleKey(KeyStroke.getKeyStroke(ch))
}
return true
}
}

View File

@@ -160,11 +160,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
return TextRange(start, end) return TextRange(start, end)
} }
override fun findWordAtOrFollowingCursor( override fun findWordAtOrFollowingCursor(editor: VimEditor, caret: ImmutableVimCaret, isBigWord: Boolean): TextRange? {
editor: VimEditor,
caret: ImmutableVimCaret,
isBigWord: Boolean,
): TextRange? {
val offset = caret.offset val offset = caret.offset
return findWordAtOrFollowingCursor(editor, offset, isBigWord) return findWordAtOrFollowingCursor(editor, offset, isBigWord)
} }
@@ -172,7 +168,6 @@ abstract class VimSearchHelperBase : VimSearchHelper {
override fun findFilenameAtOrFollowingCursor(editor: VimEditor, caret: ImmutableVimCaret): TextRange? { override fun findFilenameAtOrFollowingCursor(editor: VimEditor, caret: ImmutableVimCaret): TextRange? {
return findFilenameAtOrFollowingCursor(editor, caret.offset) return findFilenameAtOrFollowingCursor(editor, caret.offset)
} }
override fun findFilenameAtOrFollowingCursor(editor: VimEditor, offset: Int): TextRange? { override fun findFilenameAtOrFollowingCursor(editor: VimEditor, offset: Int): TextRange? {
val text = editor.text() val text = editor.text()
if (text.isEmpty()) return null if (text.isEmpty()) return null
@@ -305,9 +300,6 @@ abstract class VimSearchHelperBase : VimSearchHelper {
} }
if (result is VimMatchResult.Failure) { if (result is VimMatchResult.Failure) {
if (!showMessages) {
return null
}
if (wrap) { if (wrap) {
injector.messages.showErrorMessage(editor, injector.messages.message("E486", pattern)) injector.messages.showErrorMessage(editor, injector.messages.message("E486", pattern))
} else if (dir === Direction.FORWARDS) { } else if (dir === Direction.FORWARDS) {
@@ -315,6 +307,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
} else { } else {
injector.messages.showErrorMessage(editor, injector.messages.message("E384", pattern)) injector.messages.showErrorMessage(editor, injector.messages.message("E384", pattern))
} }
return null
} }
// When trying to find the end position for a match, we're allowed to match the current position. But if we do that // When trying to find the end position for a match, we're allowed to match the current position. But if we do that
@@ -341,10 +334,6 @@ abstract class VimSearchHelperBase : VimSearchHelper {
} }
} }
if (result is VimMatchResult.Failure) {
return null
}
return (result as VimMatchResult.Success).range return (result as VimMatchResult.Success).range
} }
@@ -1779,8 +1768,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
if (isOuter && shouldEndOnWhitespace && start > 0 if (isOuter && shouldEndOnWhitespace && start > 0
&& !isWhitespace(editor, chars[end], isBig) && !isWhitespace(editor, chars[end], isBig)
&& !isWhitespace(editor, chars[start], isBig) && !isWhitespace(editor, chars[start], isBig)) {
) {
// Outer word objects normally include following whitespace. But if there's no following whitespace to include, // Outer word objects normally include following whitespace. But if there's no following whitespace to include,
// we should extend the range to include preceding whitespace. However, Vim doesn't select whitespace at the // we should extend the range to include preceding whitespace. However, Vim doesn't select whitespace at the

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2003-2026 The IdeaVim authors * Copyright 2003-2023 The IdeaVim authors
* *
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at * license that can be found in the LICENSE.txt file or at
@@ -18,13 +18,3 @@ fun KeyStroke.isCloseKeyStroke(): Boolean {
keyCode == KeyEvent.VK_C && modifiers and InputEvent.CTRL_DOWN_MASK != 0 || keyCode == KeyEvent.VK_C && modifiers and InputEvent.CTRL_DOWN_MASK != 0 ||
keyCode == '['.code && modifiers and InputEvent.CTRL_DOWN_MASK != 0 keyCode == '['.code && modifiers and InputEvent.CTRL_DOWN_MASK != 0
} }
/**
* Returns true if this character would be matched as a command-line action (close or execute) rather than text input
* when re-injected through the key handler in CMD_LINE mode.
*
* Escape closes the command line, Enter/CR executes it.
*/
fun Char.isCommandLineActionChar(): Boolean {
return this == '\u001B' || this == '\n' || this == '\r'
}

View File

@@ -83,7 +83,7 @@ sealed class Command(
if (Flag.SAVE_SELECTION !in argFlags.flags) { if (Flag.SAVE_SELECTION !in argFlags.flags) {
// Editor.inBlockSelection is not available, because we're not in Visual mode anymore. Check if the primary caret // Editor.inBlockSelection is not available, because we're not in Visual mode anymore. Check if the primary caret
// currently has a selection and if (when we still in Visual) it was a block selection. // currently has a selection and if (when we still in Visual) it was a block selection.
injector.application.runWriteAction { injector.application.runReadAction {
if (editor.primaryCaret().hasSelection() && editor.primaryCaret().lastSelectionInfo.selectionType.isBlock) { if (editor.primaryCaret().hasSelection() && editor.primaryCaret().lastSelectionInfo.selectionType.isBlock) {
editor.removeSecondaryCarets() editor.removeSecondaryCarets()
} }

View File

@@ -299,15 +299,10 @@
"class": "com.maddyhome.idea.vim.action.motion.updown.MotionDownAction", "class": "com.maddyhome.idea.vim.action.motion.updown.MotionDownAction",
"modes": "NXO" "modes": "NXO"
}, },
{
"keys": "<C-K>",
"class": "com.maddyhome.idea.vim.action.change.insert.CmdLineCompletedDigraphAction",
"modes": "C"
},
{ {
"keys": "<C-K>", "keys": "<C-K>",
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction", "class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction",
"modes": "I" "modes": "IC"
}, },
{ {
"keys": "<C-Left>", "keys": "<C-Left>",
@@ -414,15 +409,10 @@
"class": "com.maddyhome.idea.vim.action.window.tabs.PreviousTabAction", "class": "com.maddyhome.idea.vim.action.window.tabs.PreviousTabAction",
"modes": "NXO" "modes": "NXO"
}, },
{
"keys": "<C-Q>",
"class": "com.maddyhome.idea.vim.action.change.insert.CmdLineCompletedLiteralAction",
"modes": "C"
},
{ {
"keys": "<C-Q>", "keys": "<C-Q>",
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction", "class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction",
"modes": "I" "modes": "IC"
}, },
{ {
"keys": "<C-R>", "keys": "<C-R>",
@@ -569,15 +559,10 @@
"class": "com.maddyhome.idea.vim.action.motion.scroll.CtrlUpAction", "class": "com.maddyhome.idea.vim.action.motion.scroll.CtrlUpAction",
"modes": "N" "modes": "N"
}, },
{
"keys": "<C-V>",
"class": "com.maddyhome.idea.vim.action.change.insert.CmdLineCompletedLiteralAction",
"modes": "C"
},
{ {
"keys": "<C-V>", "keys": "<C-V>",
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction", "class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction",
"modes": "I" "modes": "IC"
}, },
{ {
"keys": "<C-W>", "keys": "<C-W>",