1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2026-04-07 07:46:51 +02:00

Compare commits

..

48 Commits

Author SHA1 Message Date
2d79870c0f Set plugin version to chylex-55 2026-04-06 20:43:01 +02:00
551c6286ab Fix Ex commands not working 2026-04-06 20:43:01 +02:00
e17e93d143 Preserve visual mode after executing IDE action 2026-04-06 20:43:01 +02:00
18722c240c Make g0/g^/g$ work with soft wraps 2026-04-06 20:43:01 +02:00
945fdf3fe9 Make gj/gk jump over soft wraps 2026-04-06 20:43:01 +02:00
40e9d6ff7a Make camelCase motions adjust based on direction of visual selection 2026-04-06 20:43:00 +02:00
6bce1110e5 Make search highlights temporary 2026-04-06 20:43:00 +02:00
d8876b525a Do not switch to normal mode after inserting a live template 2026-04-06 20:43:00 +02:00
069815326a Exit insert mode after refactoring 2026-04-06 20:43:00 +02:00
755bf21d35 Add action to run last macro in all opened files 2026-04-06 20:43:00 +02:00
565ce9f9e4 Stop macro execution after a failed search 2026-04-06 20:43:00 +02:00
8cb78e26f8 Revert per-caret registers 2026-04-06 20:43:00 +02:00
430bdc2a82 Apply scrolloff after executing native IDEA actions 2026-04-06 20:43:00 +02:00
fe55e3e6eb Automatically add unambiguous imports after running a macro 2026-04-06 20:43:00 +02:00
b7ac7acaf5 Fix(VIM-3986): Exception when pasting register contents containing new line 2026-04-06 20:43:00 +02:00
8cd0e2c266 Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2026-04-06 20:43:00 +02:00
902c005826 Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2026-04-06 20:43:00 +02:00
b09ded236f Update search register when using f/t 2026-04-06 20:43:00 +02:00
221b5474c9 Add support for count for visual and line motion surround 2026-04-06 20:43:00 +02:00
2a91e67f39 Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2026-04-06 20:43:00 +02:00
e2bd6a2828 Fix(VIM-696): Restore visual mode after undo/redo, and disable incompatible actions 2026-04-06 20:43:00 +02:00
52ff8012cc Respect count with <Action> mappings 2026-04-06 20:42:59 +02:00
0bac02e40e Change matchit plugin to use HTML patterns in unrecognized files 2026-04-06 20:42:59 +02:00
33740616da Fix ex command panel causing Undock tool window to hide 2026-04-06 20:42:59 +02:00
57d0ef1dd5 Reset insert mode when switching active editor 2026-04-06 20:42:59 +02:00
d2f017887f Remove notifications about configuration options 2026-04-06 20:42:59 +02:00
cfe196ed30 Remove AI 2026-04-06 20:42:59 +02:00
536942f514 Set custom plugin version 2026-04-06 20:42:59 +02:00
36e3cd1adb Revert "Fix(VIM-4108): Use default ANTLR output directory for Gradle 9+ compatibility"
This reverts commit a476583ea3.
2026-04-06 19:58:55 +02:00
7c874f834a Revert "Upgrade Gradle wrapper to 9.2.1"
This reverts commit 517bda93
2026-04-06 19:58:52 +02:00
a4e963c98e Revert "Fix(VIM-4109): Configure test source sets for Gradle 9+ compatibility"
This reverts commit 5c0d9569d9.
2026-04-06 19:58:46 +02:00
1grzyb1
46823abcda Fix timing in jump navigation split tests 2026-04-03 12:25:19 +02:00
d85e7dba19 Fix pumvisible returning opposite result
The implementation was broken in ed50fa28f5, which inverted the result but did not invert the condition.
2026-04-03 12:17:47 +02:00
1grzyb1
a9c3277a51 Reset KeyHandler in rider esc lookup 2026-04-03 11:28:24 +02:00
1grzyb1
6e6039c22a Enable lookup listener only in rider/clion 2026-04-03 11:28:24 +02:00
1grzyb1
b49e896b41 Return VimCaret fields back to IjVimCaret
There was compatibility issue with multicursor due to change of return type
2026-04-03 11:14:54 +02:00
1grzyb1
122b066b75 Return KeyGroup from getKey
There was compatibility issue with multicursor due to change of return type
2026-04-03 11:14:54 +02:00
1grzyb1
cb24ac2bfa Restore public fields in IjVimEditor
Some fields where moved to factory and it resulted in compatybility issues with multicursor plugin
2026-04-03 11:14:54 +02:00
1grzyb1
b14324a3e6 Catching initialization exceptions
When external plugin couldn't be initilized and throw exception it resulted in broken ideavim state
2026-04-03 08:37:20 +02:00
1grzyb1
e40a839f52 Fix Escape not exiting insert mode after Ctrl+Space completion in Rider
Octopus is disabled for Rider (VIM-3815), and Rider's LookupSummaryInfo popup causes the popup manager to consume Escape before IdeaVim's action handlers can process it, so we now listen for explicit lookup cancellation via LookupListener to exit insert mode.
2026-04-02 12:27:25 +02:00
1grzyb1
a45cc0891b Don't extend octopus handler in VimEscForRiderHandler
Octopus is disabled for Rider so VimEscForRiderHandler couldn't properly handle esc
2026-04-02 11:02:08 +02:00
1grzyb1
89bad651c0 Add missing frontend module decriptor 2026-04-02 11:02:04 +02:00
dependabot[bot]
5150dc0c9e Bump io.ktor:ktor-client-content-negotiation from 3.4.1 to 3.4.2
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.4.1...3.4.2)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-content-negotiation
  dependency-version: 3.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-01 19:54:28 +00:00
dependabot[bot]
c6c7d68876 Bump gradle-wrapper from 9.4.0 to 9.4.1
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.4.0 to 9.4.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.4.0...v9.4.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-01 19:51:49 +00:00
1grzyb1
02130a87c9 Exit search with proper defocus and handle escape 2026-04-01 11:48:43 +02:00
1grzyb1
40ba977e58 Add run configurations for all platforms
To simplify running ideavim with different platforms, this commit introduce run configurations for each platform both in monolith and split mode
2026-04-01 09:43:37 +02:00
1grzyb1
21f304a560 VIM-4135 fix loading rider module 2026-04-01 09:32:32 +02:00
1grzyb1
36e8bd4663 VIM-4016 Fix :edit when project has no source roots
When project couldn't properlly indexed and didn't have source roots it couldn't find file using edit command. So I've modified it to search using absolute paths in project
2026-03-31 09:48:34 +02:00
33 changed files with 567 additions and 107 deletions

View File

@@ -0,0 +1,16 @@
<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

@@ -0,0 +1,24 @@
<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

@@ -0,0 +1,25 @@
<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">
<configuration default="false" name="Start IJ with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="Start IJ with IdeaVim (Split Mode)" 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" />

View File

@@ -0,0 +1,25 @@
<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

@@ -0,0 +1,24 @@
<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

@@ -0,0 +1,25 @@
<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

@@ -0,0 +1,24 @@
<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

@@ -0,0 +1,24 @@
<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

@@ -0,0 +1,25 @@
<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,6 +6,7 @@
* 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.tasks.aware.SplitModeAware
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
@@ -26,11 +27,11 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.6.0.202603022253-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:3.4.1")
classpath("io.ktor:ktor-client-cio:3.4.1")
classpath("io.ktor:ktor-client-auth:3.4.1")
classpath("io.ktor:ktor-client-content-negotiation:3.4.1")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.4.1")
classpath("io.ktor:ktor-client-core:3.4.2")
classpath("io.ktor:ktor-client-cio:3.4.2")
classpath("io.ktor:ktor-client-auth:3.4.2")
classpath("io.ktor:ktor-client-content-negotiation:3.4.2")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.4.2")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@@ -112,7 +113,7 @@ dependencies {
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
plugin("com.intellij.classic.ui", "261.22158.185")
compatiblePlugin("com.intellij.classic.ui")
pluginModule(runtimeOnly(project(":modules:ideavim-common")))
pluginModule(runtimeOnly(project(":modules:ideavim-frontend")))
@@ -225,6 +226,30 @@ tasks {
// 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 {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
@@ -247,6 +272,55 @@ tasks {
val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
splitMode = true
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.
@@ -255,6 +329,11 @@ tasks {
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
prepareSandboxTask {
val sandboxDir = project.layout.buildDirectory.dir("idea-sandbox").map { it.asFile }
doLast {
@@ -286,6 +365,12 @@ tasks {
val testIdeSplitMode by intellijPlatformTesting.testIde.registering {
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
plugins {
plugin("AceJump", "3.8.22")
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
}
task {
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
ideaType=IU
instrumentPluginCode=true
version=chylex-54
version=chylex-55
javaVersion=21
remoteRobotVersion=0.11.23
antlrVersion=4.10.1

2
gradlew vendored
View File

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

View File

@@ -165,25 +165,27 @@ internal class FileRemoteApiImpl : FileRemoteApi {
// ======================== Private helpers ========================
private fun findFile(filename: String, project: Project): VirtualFile? {
var found: VirtualFile?
if (filename.startsWith("~/") || filename.startsWith("~\\")) {
val relativePath = filename.substring(2)
val dir = System.getProperty("user.home")
logger.debug { "home dir file" }
logger.debug { "looking for $relativePath in $dir" }
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)
}
}
return LocalFileSystem.getInstance().refreshAndFindFileByNioFile(Path(dir, relativePath))
}
return found
val basePath = project.basePath
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 {

View File

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

View File

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

View File

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

View File

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

View File

@@ -241,13 +241,17 @@ internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(n
/**
* 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.
* designed 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.
*
* 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) {
override val key: String = "<Esc>"

View File

@@ -50,6 +50,8 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode
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.initInjector
import com.maddyhome.idea.vim.newapi.vim
@@ -350,6 +352,16 @@ internal object IdeaSpecifics {
if (newLookup.editor.isIdeaVimDisabledHere) return
(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
@@ -361,6 +373,20 @@ 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
//region Hide Vim search highlights when showing IntelliJ search results

View File

@@ -12,6 +12,7 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition
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.SelectionInfo
import com.maddyhome.idea.vim.api.VimCaret
@@ -198,3 +199,12 @@ class IjVimCaret(val caret: Caret) : VimCaretBase() {
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,3 +671,12 @@ 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,7 +14,6 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.TextRange
interface VimEditorFactory {
fun createVimEditor(editor: Editor): VimEditor
@@ -27,21 +26,3 @@ interface VimEditorFactory {
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
// 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)
if (stroke.keyChar != KeyEvent.CHAR_UNDEFINED) {
if (stroke.keyChar != KeyEvent.CHAR_UNDEFINED && !isKeyCharEnterOrEscape(stroke.keyChar)) {
replaceSelection(stroke.keyChar.toString())
} else {
val event = KeyEvent(

View File

@@ -0,0 +1,17 @@
<!--
~ 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-2023 The IdeaVim authors
* 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
@@ -10,9 +10,12 @@ package org.jetbrains.plugins.ideavim.action.motion.search
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class SearchEntryFwdActionTest : VimTestCase() {
@Test
@@ -110,6 +113,23 @@ 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")
@Test
fun `search in one time from select mode`() {

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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
@@ -13,14 +13,22 @@ import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.isCommandLineActionChar
import com.maddyhome.idea.vim.state.KeyHandlerState
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() {
override val type: Command.Type = Command.Type.INSERT
@@ -29,22 +37,6 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
// We're waiting for it to complete and give us a CHARACTER
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) {
val result = keyState.digraphSequence.startDigraphSequence()
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
@@ -56,7 +48,6 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
cmd: Command,
operatorArguments: OperatorArguments,
): 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 keyStroke = KeyStroke.getKeyStroke(argument.character)
val keyHandler = KeyHandler.getInstance()
@@ -64,3 +55,37 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
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-2023 The IdeaVim authors
* 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
@@ -13,14 +13,22 @@ import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.isCommandLineActionChar
import com.maddyhome.idea.vim.state.KeyHandlerState
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() {
override val type: Command.Type = Command.Type.INSERT
@@ -29,22 +37,6 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
// We're waiting for it to complete and give us a CHARACTER
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) {
val result = keyState.digraphSequence.startLiteralSequence()
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
@@ -56,7 +48,6 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
cmd: Command,
operatorArguments: OperatorArguments,
): 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 keyStroke = KeyStroke.getKeyStroke(argument.character)
val keyHandler = KeyHandler.getInstance()
@@ -64,3 +55,39 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
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,7 +160,11 @@ abstract class VimSearchHelperBase : VimSearchHelper {
return TextRange(start, end)
}
override fun findWordAtOrFollowingCursor(editor: VimEditor, caret: ImmutableVimCaret, isBigWord: Boolean): TextRange? {
override fun findWordAtOrFollowingCursor(
editor: VimEditor,
caret: ImmutableVimCaret,
isBigWord: Boolean,
): TextRange? {
val offset = caret.offset
return findWordAtOrFollowingCursor(editor, offset, isBigWord)
}
@@ -168,6 +172,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
override fun findFilenameAtOrFollowingCursor(editor: VimEditor, caret: ImmutableVimCaret): TextRange? {
return findFilenameAtOrFollowingCursor(editor, caret.offset)
}
override fun findFilenameAtOrFollowingCursor(editor: VimEditor, offset: Int): TextRange? {
val text = editor.text()
if (text.isEmpty()) return null
@@ -300,6 +305,9 @@ abstract class VimSearchHelperBase : VimSearchHelper {
}
if (result is VimMatchResult.Failure) {
if (!showMessages) {
return null
}
if (wrap) {
injector.messages.showErrorMessage(editor, injector.messages.message("E486", pattern))
} else if (dir === Direction.FORWARDS) {
@@ -307,7 +315,6 @@ abstract class VimSearchHelperBase : VimSearchHelper {
} else {
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
@@ -334,6 +341,10 @@ abstract class VimSearchHelperBase : VimSearchHelper {
}
}
if (result is VimMatchResult.Failure) {
return null
}
return (result as VimMatchResult.Success).range
}
@@ -1768,7 +1779,8 @@ abstract class VimSearchHelperBase : VimSearchHelper {
if (isOuter && shouldEndOnWhitespace && start > 0
&& !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,
// 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-2023 The IdeaVim authors
* 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
@@ -18,3 +18,13 @@ fun KeyStroke.isCloseKeyStroke(): Boolean {
keyCode == KeyEvent.VK_C && 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) {
// 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.
injector.application.runReadAction {
injector.application.runWriteAction {
if (editor.primaryCaret().hasSelection() && editor.primaryCaret().lastSelectionInfo.selectionType.isBlock) {
editor.removeSecondaryCarets()
}

View File

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