mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2026-04-10 07:13:05 +02:00
Compare commits
50 Commits
customized
...
customized
| Author | SHA1 | Date | |
|---|---|---|---|
|
078ddaf3ca
|
|||
|
94a7e1d303
|
|||
|
3de7743f56
|
|||
|
8636717dea
|
|||
|
22dfdd8ca6
|
|||
|
49f9f16f0d
|
|||
|
9bfc5d72ce
|
|||
|
84c227122a
|
|||
|
1b9ff4c94a
|
|||
|
bdecbb5ef0
|
|||
|
7dfd8e6cff
|
|||
|
31e76f0fcf
|
|||
|
2aadbdc8f0
|
|||
|
627d65e528
|
|||
|
e77871796e
|
|||
|
c6e993dcbd
|
|||
|
341ba1ba1f
|
|||
|
f3d7ad55f6
|
|||
|
5480b99898
|
|||
|
5734a13ea0
|
|||
|
582e6bdcd8
|
|||
|
7414c3d3ed
|
|||
|
8fa5bec363
|
|||
|
aea54bdf81
|
|||
|
79aca4497e
|
|||
|
50976ea9da
|
|||
|
57d0ef1dd5
|
|||
|
d2f017887f
|
|||
|
cfe196ed30
|
|||
|
536942f514
|
|||
|
36e3cd1adb
|
|||
|
7c874f834a
|
|||
|
a4e963c98e
|
|||
|
|
46823abcda | ||
| d85e7dba19 | |||
|
|
a9c3277a51 | ||
|
|
6e6039c22a | ||
|
|
b49e896b41 | ||
|
|
122b066b75 | ||
|
|
cb24ac2bfa | ||
|
|
b14324a3e6 | ||
|
|
e40a839f52 | ||
|
|
a45cc0891b | ||
|
|
89bad651c0 | ||
|
|
5150dc0c9e | ||
|
|
c6c7d68876 | ||
|
|
02130a87c9 | ||
|
|
40ba977e58 | ||
|
|
21f304a560 | ||
|
|
36e8bd4663 |
16
.idea/runConfigurations/Split_Frontend_Debugger.xml
generated
Normal file
16
.idea/runConfigurations/Split_Frontend_Debugger.xml
generated
Normal 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>
|
||||
24
.idea/runConfigurations/Start_CLion_with_IdeaVim.xml
generated
Normal file
24
.idea/runConfigurations/Start_CLion_with_IdeaVim.xml
generated
Normal 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>
|
||||
25
.idea/runConfigurations/Start_CLion_with_IdeaVim__Split_Mode_.xml
generated
Normal file
25
.idea/runConfigurations/Start_CLion_with_IdeaVim__Split_Mode_.xml
generated
Normal 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>
|
||||
@@ -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" />
|
||||
|
||||
25
.idea/runConfigurations/Start_IJ_with_IdeaVim__Split_Mode_Debug_Frontend_.xml
generated
Normal file
25
.idea/runConfigurations/Start_IJ_with_IdeaVim__Split_Mode_Debug_Frontend_.xml
generated
Normal 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>
|
||||
24
.idea/runConfigurations/Start_PyCharm_with_IdeaVim.xml
generated
Normal file
24
.idea/runConfigurations/Start_PyCharm_with_IdeaVim.xml
generated
Normal 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>
|
||||
25
.idea/runConfigurations/Start_PyCharm_with_IdeaVim__Split_Mode_.xml
generated
Normal file
25
.idea/runConfigurations/Start_PyCharm_with_IdeaVim__Split_Mode_.xml
generated
Normal 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>
|
||||
24
.idea/runConfigurations/Start_Rider_with_IdeaVim.xml
generated
Normal file
24
.idea/runConfigurations/Start_Rider_with_IdeaVim.xml
generated
Normal 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>
|
||||
24
.idea/runConfigurations/Start_WebStorm_with_IdeaVim.xml
generated
Normal file
24
.idea/runConfigurations/Start_WebStorm_with_IdeaVim.xml
generated
Normal 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>
|
||||
25
.idea/runConfigurations/Start_WebStorm_with_IdeaVim__Split_Mode_.xml
generated
Normal file
25
.idea/runConfigurations/Start_WebStorm_with_IdeaVim__Split_Mode_.xml
generated
Normal 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>
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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-56
|
||||
javaVersion=21
|
||||
remoteRobotVersion=0.11.23
|
||||
antlrVersion=4.10.1
|
||||
|
||||
2
gradlew
vendored
2
gradlew
vendored
@@ -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/.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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>"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -9,7 +9,9 @@
|
||||
package com.maddyhome.idea.vim.newapi
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.maddyhome.idea.vim.api.MessageType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.wm.WindowManager
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimMessagesBase
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
@@ -23,43 +25,56 @@ internal class IjVimMessages : VimMessagesBase() {
|
||||
private var message: String? = null
|
||||
private var error = false
|
||||
private var lastBeepTimeMillis = 0L
|
||||
private var allowClearStatusBarMessage = true
|
||||
|
||||
override fun showMessage(editor: VimEditor, message: String?) {
|
||||
showMessageInternal(editor, message, MessageType.STANDARD)
|
||||
}
|
||||
|
||||
override fun showErrorMessage(editor: VimEditor, message: String?) {
|
||||
showMessageInternal(editor, message, MessageType.ERROR)
|
||||
indicateError()
|
||||
}
|
||||
|
||||
private fun showMessageInternal(editor: VimEditor, message: String?, messageType: MessageType) {
|
||||
this.message = message
|
||||
|
||||
if (message.isNullOrBlank()) {
|
||||
clearStatusBarMessage()
|
||||
return
|
||||
override fun showStatusBarMessage(editor: VimEditor?, message: String?) {
|
||||
fun setStatusBarMessage(project: Project, message: String?) {
|
||||
WindowManager.getInstance().getStatusBar(project)?.let {
|
||||
it.info = if (message.isNullOrBlank()) "" else "Vim - $message"
|
||||
}
|
||||
}
|
||||
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(editor)
|
||||
injector.outputPanel.output(editor, context, message, messageType)
|
||||
}
|
||||
this.message = message
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun showStatusBarMessage(editor: VimEditor?, message: String?) {
|
||||
if (editor != null) {
|
||||
showMessage(editor, message)
|
||||
val project = editor?.ij?.project
|
||||
if (project != null) {
|
||||
setStatusBarMessage(project, message)
|
||||
} else {
|
||||
// Legacy path for when editor is null - just store the message
|
||||
this.message = message
|
||||
// TODO: We really shouldn't set the status bar text for other projects. That's rude.
|
||||
ProjectManager.getInstance().openProjects.forEach {
|
||||
setStatusBarMessage(it, message)
|
||||
}
|
||||
}
|
||||
|
||||
// Redraw happens automatically based on changes or scrolling. If we've just set the message (e.g., searching for a
|
||||
// string, hitting the bottom and scrolling to the top), make sure we don't immediately clear it when scrolling.
|
||||
allowClearStatusBarMessage = false
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
allowClearStatusBarMessage = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStatusBarMessage(): String? = message
|
||||
|
||||
// Vim doesn't appear to have a policy about clearing the status bar, other than on "redraw". This can be forced with
|
||||
// <C-L> or the `:redraw` command, but also happens as the screen changes, e.g., when inserting or deleting lines,
|
||||
// scrolling, entering Command-line mode and probably lots more. We should manually clear the status bar when these
|
||||
// things happen.
|
||||
override fun clearStatusBarMessage() {
|
||||
if (message.isNullOrEmpty()) return
|
||||
injector.outputPanel.getCurrentOutputPanel()?.close()
|
||||
val currentMessage = message
|
||||
if (currentMessage.isNullOrEmpty()) return
|
||||
|
||||
// Don't clear the status bar message if we've only just set it
|
||||
if (!allowClearStatusBarMessage) return
|
||||
|
||||
ProjectManager.getInstance().openProjects.forEach { project ->
|
||||
WindowManager.getInstance().getStatusBar(project)?.let { statusBar ->
|
||||
// Only clear the status bar if it's showing our last message
|
||||
if (statusBar.info?.contains(currentMessage) == true) {
|
||||
statusBar.info = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
message = null
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -14,7 +14,6 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.wm.impl.IdeBackgroundUtil
|
||||
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
|
||||
import com.intellij.ui.ClientProperty
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.components.JBPanel
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.util.IJSwingUtilities
|
||||
@@ -25,6 +24,7 @@ import com.maddyhome.idea.vim.api.MessageType
|
||||
import com.maddyhome.idea.vim.api.VimOutputPanel
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.diagnostic.VimLogger
|
||||
import com.maddyhome.idea.vim.helper.requestFocus
|
||||
import com.maddyhome.idea.vim.helper.selectEditorFont
|
||||
import com.maddyhome.idea.vim.helper.vimMorePanel
|
||||
@@ -36,166 +36,121 @@ import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import java.lang.ref.WeakReference
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JRootPane
|
||||
import javax.swing.JScrollPane
|
||||
import javax.swing.JTextPane
|
||||
import javax.swing.JTextArea
|
||||
import javax.swing.KeyStroke
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.text.DefaultCaret
|
||||
import javax.swing.text.SimpleAttributeSet
|
||||
import javax.swing.text.StyleConstants
|
||||
import javax.swing.text.StyledDocument
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
/**
|
||||
* Panel that displays text in a `more` like window overlaid on the editor.
|
||||
* This panel displays text in a `more` like window and implements [VimOutputPanel].
|
||||
*/
|
||||
class OutputPanel private constructor(
|
||||
private val editor: Editor,
|
||||
) : JBPanel<OutputPanel>(), VimOutputPanel {
|
||||
class OutputPanel(editorRef: WeakReference<Editor>) : JBPanel<OutputPanel?>(), VimOutputPanel {
|
||||
private val myEditorRef: WeakReference<Editor> = editorRef
|
||||
val editor: Editor? get() = myEditorRef.get()
|
||||
|
||||
private val textPane = JTextPane()
|
||||
private val resizeAdapter: ComponentAdapter
|
||||
private var defaultForeground: Color? = null
|
||||
val myLabel: JLabel = JLabel("more")
|
||||
private val myText = JTextArea()
|
||||
private val myScrollPane: JScrollPane =
|
||||
JBScrollPane(myText, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
|
||||
private val myAdapter: ComponentAdapter
|
||||
private var myLineHeight = 0
|
||||
|
||||
private var glassPane: JComponent? = null
|
||||
private var originalLayout: LayoutManager? = null
|
||||
private var wasOpaque = false
|
||||
private var myOldGlass: JComponent? = null
|
||||
private var myOldLayout: LayoutManager? = null
|
||||
private var myWasOpaque = false
|
||||
|
||||
var active: Boolean = false
|
||||
private val segments = mutableListOf<TextLine>()
|
||||
var myActive: Boolean = false
|
||||
|
||||
private val labelComponent: JLabel = JLabel("more")
|
||||
private val scrollPane: JScrollPane =
|
||||
JBScrollPane(textPane, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
|
||||
private var cachedLineHeight = 0
|
||||
private var isSingleLine = false
|
||||
val isActive: Boolean
|
||||
get() = myActive
|
||||
|
||||
init {
|
||||
textPane.isEditable = false
|
||||
textPane.caret = object : DefaultCaret() {
|
||||
override fun setVisible(v: Boolean) {
|
||||
super.setVisible(false)
|
||||
}
|
||||
}
|
||||
textPane.highlighter = null
|
||||
// Create a text editor for the text and a label for the prompt
|
||||
val layout = BorderLayout(0, 0)
|
||||
setLayout(layout)
|
||||
add(myScrollPane, BorderLayout.CENTER)
|
||||
add(myLabel, BorderLayout.SOUTH)
|
||||
|
||||
resizeAdapter = object : ComponentAdapter() {
|
||||
// Set the text area read only, and support wrap
|
||||
myText.isEditable = false
|
||||
myText.setLineWrap(true)
|
||||
|
||||
myAdapter = object : ComponentAdapter() {
|
||||
override fun componentResized(e: ComponentEvent?) {
|
||||
positionPanel()
|
||||
}
|
||||
}
|
||||
|
||||
// Suppress the fancy frame background used in the Islands theme
|
||||
ClientProperty.putRecursive(this, IdeBackgroundUtil.NO_BACKGROUND, true)
|
||||
putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, editor.component)
|
||||
// Setup some listeners to handle keystrokes
|
||||
val moreKeyListener = MoreKeyListener()
|
||||
addKeyListener(moreKeyListener)
|
||||
myText.addKeyListener(moreKeyListener)
|
||||
|
||||
// Initialize panel
|
||||
setLayout(BorderLayout(0, 0))
|
||||
add(scrollPane, BorderLayout.CENTER)
|
||||
add(labelComponent, BorderLayout.SOUTH)
|
||||
|
||||
val keyListener = OutputPanelKeyListener()
|
||||
addKeyListener(keyListener)
|
||||
textPane.addKeyListener(keyListener)
|
||||
// Suppress the fancy frame background used in the Islands theme, which comes from a custom Graphics implementation
|
||||
// applied to the IdeRoot, and used to paint all children, including this panel. This client property is checked by
|
||||
// JBPanel.getComponentGraphics to give us the original Graphics, opting out of the fancy painting.
|
||||
ClientProperty.putRecursive<Boolean?>(this, IdeBackgroundUtil.NO_BACKGROUND, true)
|
||||
editor?.let { putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, it.component) }
|
||||
|
||||
updateUI()
|
||||
}
|
||||
|
||||
// Called automatically when the LAF is changed and the component is visible, and manually by the LAF listener handler
|
||||
override fun updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
setBorder(ExPanelBorder())
|
||||
|
||||
// Swing uses a bad pattern of calling updateUI() from the constructor. At this moment, all these variables are null
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (textPane != null && labelComponent != null && scrollPane != null) {
|
||||
if (myText != null && myLabel != null && myScrollPane != null) {
|
||||
setFontForElements()
|
||||
textPane.setBorder(null)
|
||||
scrollPane.setBorder(null)
|
||||
labelComponent.setForeground(textPane.getForeground())
|
||||
myText.setBorder(null)
|
||||
myScrollPane.setBorder(null)
|
||||
myLabel.setForeground(myText.getForeground())
|
||||
|
||||
// Make sure the panel is positioned correctly in case we're changing font size
|
||||
positionPanel()
|
||||
}
|
||||
}
|
||||
|
||||
override var text: String
|
||||
get() = textPane.getText() ?: ""
|
||||
get() = myText.text
|
||||
set(value) {
|
||||
// ExOutputPanel will strip a trailing newline. We'll do it now so that tests have the same behaviour.
|
||||
val newValue = value.removeSuffix("\n")
|
||||
segments.clear()
|
||||
if (newValue.isEmpty()) return
|
||||
segments.add(TextLine(newValue, null))
|
||||
myText.text = newValue
|
||||
val ed = editor
|
||||
if (ed != null) {
|
||||
myText.setFont(selectEditorFont(ed, newValue))
|
||||
}
|
||||
myText.setCaretPosition(0)
|
||||
if (newValue.isNotEmpty()) {
|
||||
activate()
|
||||
}
|
||||
}
|
||||
|
||||
override var label: String
|
||||
get() = labelComponent.text
|
||||
get() = myLabel.text ?: ""
|
||||
set(value) {
|
||||
labelComponent.text = value
|
||||
myLabel.text = value
|
||||
val ed = editor
|
||||
if (ed != null) {
|
||||
myLabel.setFont(selectEditorFont(ed, value))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets styled text with multiple segments, each potentially having a different color.
|
||||
*/
|
||||
fun setStyledText(lines: List<TextLine>) {
|
||||
val doc = textPane.styledDocument
|
||||
doc.remove(0, doc.length)
|
||||
|
||||
if (defaultForeground == null) {
|
||||
defaultForeground = textPane.foreground
|
||||
}
|
||||
|
||||
if (lines.size > 1) {
|
||||
setMultiLineText(lines, doc)
|
||||
} else {
|
||||
doc.insertString(doc.length, lines[0].text.removeSuffix("\n"), getLineColor(lines[0]))
|
||||
}
|
||||
|
||||
val fullText = doc.getText(0, doc.length)
|
||||
textPane.setFont(selectEditorFont(editor, fullText))
|
||||
textPane.setCaretPosition(0)
|
||||
if (fullText.isNotEmpty()) {
|
||||
activate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMultiLineText(
|
||||
lines: List<TextLine>,
|
||||
doc: StyledDocument,
|
||||
) {
|
||||
for ((index, line) in lines.withIndex()) {
|
||||
val text = line.text.removeSuffix("\n")
|
||||
val attrs = getLineColor(line)
|
||||
val separator = if (index < lines.size - 1) "\n" else ""
|
||||
doc.insertString(doc.length, text + separator, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLineColor(segment: TextLine): SimpleAttributeSet {
|
||||
val attrs = SimpleAttributeSet()
|
||||
val color = segment.color ?: defaultForeground
|
||||
if (color != null) {
|
||||
StyleConstants.setForeground(attrs, color)
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
override fun addText(text: String, isNewLine: Boolean, messageType: MessageType) {
|
||||
val color = when (messageType) {
|
||||
MessageType.ERROR -> JBColor.RED
|
||||
MessageType.STANDARD -> null
|
||||
}
|
||||
segments.add(TextLine(text, color))
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
val currentPanel = injector.outputPanel.getCurrentOutputPanel()
|
||||
if (currentPanel != null && currentPanel != this) currentPanel.close()
|
||||
|
||||
setStyledText(segments)
|
||||
if (!active) {
|
||||
activate()
|
||||
if (this.text.isNotEmpty() && isNewLine) {
|
||||
this.text += "\n$text"
|
||||
} else {
|
||||
this.text += text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,15 +159,20 @@ class OutputPanel private constructor(
|
||||
}
|
||||
|
||||
override fun clearText() {
|
||||
segments.clear()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
text = ""
|
||||
}
|
||||
|
||||
override fun handleKey(key: KeyStroke) {
|
||||
override fun show() {
|
||||
editor ?: return
|
||||
val currentPanel = injector.outputPanel.getCurrentOutputPanel()
|
||||
if (currentPanel != null && currentPanel != this) currentPanel.close()
|
||||
|
||||
if (!myActive) {
|
||||
activate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleKey(key: KeyStroke) {
|
||||
if (isAtEnd) {
|
||||
close(key)
|
||||
return
|
||||
@@ -237,262 +197,214 @@ class OutputPanel private constructor(
|
||||
|
||||
override fun getForeground(): Color? {
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (textPane == null) {
|
||||
if (myText == null) {
|
||||
// Swing uses a bad pattern of calling getForeground() from the constructor. At this moment, `myText` is null.
|
||||
return super.getForeground()
|
||||
}
|
||||
return textPane.getForeground()
|
||||
return myText.getForeground()
|
||||
}
|
||||
|
||||
override fun getBackground(): Color? {
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (textPane == null) {
|
||||
if (myText == null) {
|
||||
// Swing uses a bad pattern of calling getBackground() from the constructor. At this moment, `myText` is null.
|
||||
return super.getBackground()
|
||||
}
|
||||
return textPane.getBackground()
|
||||
return myText.getBackground()
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off the output panel and optionally puts the focus back to the original component.
|
||||
* Turns off the ex entry field and optionally puts the focus back to the original component
|
||||
*/
|
||||
fun deactivate(refocusOwningEditor: Boolean) {
|
||||
if (!active) return
|
||||
active = false
|
||||
clearText()
|
||||
textPane.text = ""
|
||||
if (refocusOwningEditor) {
|
||||
requestFocus(editor.contentComponent)
|
||||
if (!myActive) return
|
||||
myActive = false
|
||||
myText.text = ""
|
||||
val ed = editor
|
||||
if (refocusOwningEditor && ed != null) {
|
||||
requestFocus(ed.contentComponent)
|
||||
}
|
||||
if (glassPane != null) {
|
||||
glassPane!!.removeComponentListener(resizeAdapter)
|
||||
glassPane!!.isVisible = false
|
||||
glassPane!!.remove(this)
|
||||
glassPane!!.setOpaque(wasOpaque)
|
||||
glassPane!!.setLayout(originalLayout)
|
||||
if (myOldGlass != null) {
|
||||
myOldGlass!!.removeComponentListener(myAdapter)
|
||||
myOldGlass!!.isVisible = false
|
||||
myOldGlass!!.remove(this)
|
||||
myOldGlass!!.setOpaque(myWasOpaque)
|
||||
myOldGlass!!.setLayout(myOldLayout)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on the output panel for the given editor.
|
||||
* Turns on the more window for the given editor
|
||||
*/
|
||||
fun activate() {
|
||||
disableOldGlass()
|
||||
val ed = editor ?: return
|
||||
val root = SwingUtilities.getRootPane(ed.contentComponent)
|
||||
deactivateOldGlass(root)
|
||||
|
||||
setFontForElements()
|
||||
positionPanel()
|
||||
|
||||
if (glassPane != null) {
|
||||
glassPane!!.isVisible = true
|
||||
if (myOldGlass != null) {
|
||||
myOldGlass!!.isVisible = true
|
||||
}
|
||||
|
||||
active = true
|
||||
requestFocus(textPane)
|
||||
myActive = true
|
||||
requestFocus(myText)
|
||||
}
|
||||
|
||||
private fun disableOldGlass() {
|
||||
val root = SwingUtilities.getRootPane(editor.contentComponent) ?: return
|
||||
glassPane = root.getGlassPane() as JComponent?
|
||||
if (glassPane == null) {
|
||||
private fun deactivateOldGlass(root: JRootPane?) {
|
||||
if (root == null) return
|
||||
myOldGlass = root.getGlassPane() as JComponent?
|
||||
if (myOldGlass != null) {
|
||||
myOldLayout = myOldGlass!!.layout
|
||||
myWasOpaque = myOldGlass!!.isOpaque
|
||||
myOldGlass!!.setLayout(null)
|
||||
myOldGlass!!.setOpaque(false)
|
||||
myOldGlass!!.add(this)
|
||||
myOldGlass!!.addComponentListener(myAdapter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setFontForElements() {
|
||||
val ed = editor ?: return
|
||||
myText.setFont(selectEditorFont(ed, myText.getText()))
|
||||
myLabel.setFont(selectEditorFont(ed, myLabel.text))
|
||||
}
|
||||
|
||||
override fun scrollLine() {
|
||||
scrollOffset(myLineHeight)
|
||||
}
|
||||
|
||||
override fun scrollPage() {
|
||||
scrollOffset(myScrollPane.getVerticalScrollBar().visibleAmount)
|
||||
}
|
||||
|
||||
override fun scrollHalfPage() {
|
||||
val sa = myScrollPane.getVerticalScrollBar().visibleAmount / 2.0
|
||||
val offset = ceil(sa / myLineHeight) * myLineHeight
|
||||
scrollOffset(offset.toInt())
|
||||
}
|
||||
|
||||
fun onBadKey() {
|
||||
val ed = editor ?: return
|
||||
myLabel.setText(injector.messages.message("message.ex.output.more.prompt.full"))
|
||||
myLabel.setFont(selectEditorFont(ed, myLabel.text))
|
||||
}
|
||||
|
||||
private fun scrollOffset(more: Int) {
|
||||
val ed = editor ?: return
|
||||
val `val` = myScrollPane.getVerticalScrollBar().value
|
||||
myScrollPane.getVerticalScrollBar().setValue(`val` + more)
|
||||
myScrollPane.getHorizontalScrollBar().setValue(0)
|
||||
if (isAtEnd) {
|
||||
myLabel.setText(injector.messages.message("message.ex.output.end.prompt"))
|
||||
} else {
|
||||
myLabel.setText(injector.messages.message("message.ex.output.more.prompt"))
|
||||
}
|
||||
myLabel.setFont(selectEditorFont(ed, myLabel.text))
|
||||
}
|
||||
|
||||
val isAtEnd: Boolean
|
||||
get() {
|
||||
val isSingleLine = myText.getLineCount() == 1
|
||||
if (isSingleLine) return true
|
||||
val scrollBar = myScrollPane.getVerticalScrollBar()
|
||||
val value = scrollBar.value
|
||||
if (!scrollBar.isVisible) {
|
||||
return true
|
||||
}
|
||||
return value >= scrollBar.maximum - scrollBar.visibleAmount ||
|
||||
scrollBar.maximum <= scrollBar.visibleAmount
|
||||
}
|
||||
|
||||
private fun positionPanel() {
|
||||
val ed = editor ?: return
|
||||
val contentComponent = ed.contentComponent
|
||||
val scroll = SwingUtilities.getAncestorOfClass(JScrollPane::class.java, contentComponent)
|
||||
val rootPane = SwingUtilities.getRootPane(contentComponent)
|
||||
if (scroll == null || rootPane == null) {
|
||||
// These might be null if we're invoked during component initialisation and before it's been added to the tree
|
||||
return
|
||||
}
|
||||
originalLayout = glassPane!!.layout
|
||||
wasOpaque = glassPane!!.isOpaque
|
||||
glassPane!!.setLayout(null)
|
||||
glassPane!!.setOpaque(false)
|
||||
glassPane!!.add(this)
|
||||
glassPane!!.addComponentListener(resizeAdapter)
|
||||
|
||||
size = scroll.size
|
||||
|
||||
myLineHeight = myText.getFontMetrics(myText.getFont()).height
|
||||
val count: Int = countLines(myText.getText())
|
||||
val visLines = size.height / myLineHeight - 1
|
||||
val lines = min(count, visLines)
|
||||
setSize(
|
||||
size.width,
|
||||
lines * myLineHeight + myLabel.getPreferredSize().height + border.getBorderInsets(this).top * 2
|
||||
)
|
||||
|
||||
val height = size.height
|
||||
val bounds = scroll.bounds
|
||||
bounds.translate(0, scroll.getHeight() - height)
|
||||
bounds.height = height
|
||||
val pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.location, rootPane.getGlassPane())
|
||||
bounds.location = pos
|
||||
setBounds(bounds)
|
||||
|
||||
myScrollPane.getVerticalScrollBar().setValue(0)
|
||||
if (!injector.globalOptions().more) {
|
||||
// FIX
|
||||
scrollOffset(100000)
|
||||
} else {
|
||||
scrollOffset(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun close(key: KeyStroke? = null) {
|
||||
val ed = editor ?: return
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
deactivate(true)
|
||||
val project = ed.project
|
||||
if (project != null && key != null && key.keyChar != '\n') {
|
||||
val keys: MutableList<KeyStroke> = ArrayList(1)
|
||||
keys.add(key)
|
||||
if (LOG.isTrace()) {
|
||||
LOG.trace(
|
||||
"Adding new keys to keyStack as part of playback. State before adding keys: " +
|
||||
getInstance().keyStack.dump()
|
||||
)
|
||||
}
|
||||
getInstance().keyStack.addKeys(keys)
|
||||
val context: ExecutionContext =
|
||||
injector.executionContextManager.getEditorExecutionContext(IjVimEditor(ed))
|
||||
VimPlugin.getMacro().playbackKeys(IjVimEditor(ed), context, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
close(null)
|
||||
}
|
||||
|
||||
fun close(key: KeyStroke?) {
|
||||
val passKeyBack = isSingleLine
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
deactivate(true)
|
||||
val project = editor.project
|
||||
// For single line messages, pass any key back to the editor (including Enter)
|
||||
// For multi-line messages, don't pass Enter back (it was used to dismiss)
|
||||
if (project != null && key != null && (passKeyBack || key.keyChar != '\n')) {
|
||||
val keys: MutableList<KeyStroke> = ArrayList(1)
|
||||
keys.add(key)
|
||||
getInstance().keyStack.addKeys(keys)
|
||||
val context: ExecutionContext =
|
||||
injector.executionContextManager.getEditorExecutionContext(IjVimEditor(editor))
|
||||
VimPlugin.getMacro().playbackKeys(IjVimEditor(editor), context, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setFontForElements() {
|
||||
textPane.setFont(selectEditorFont(editor, textPane.getText()))
|
||||
labelComponent.setFont(selectEditorFont(editor, labelComponent.text))
|
||||
}
|
||||
|
||||
private fun positionPanel() {
|
||||
val scroll = positionPanelStart() ?: return
|
||||
val lineHeight = textPane.getFontMetrics(textPane.getFont()).height
|
||||
val count = countLines(textPane.getText())
|
||||
val visLines = size.height / lineHeight - 1
|
||||
val lines = min(count, visLines)
|
||||
|
||||
// Simple output: single line that fits entirely - no label needed
|
||||
isSingleLine = count == 1 && count <= visLines
|
||||
labelComponent.isVisible = !isSingleLine
|
||||
|
||||
val extraHeight = if (isSingleLine) 0 else labelComponent.getPreferredSize().height
|
||||
setSize(
|
||||
size.width,
|
||||
lines * lineHeight + extraHeight + border.getBorderInsets(this).top * 2
|
||||
)
|
||||
|
||||
finishPositioning(scroll)
|
||||
|
||||
// Force layout so that viewport sizes are valid before checking scroll state
|
||||
validate()
|
||||
|
||||
// onPositioned
|
||||
cachedLineHeight = lineHeight
|
||||
scrollPane.getVerticalScrollBar().setValue(0)
|
||||
if (!isSingleLine) {
|
||||
if (!injector.globalOptions().more) {
|
||||
scrollOffset(100000)
|
||||
} else {
|
||||
scrollOffset(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun positionPanelStart(): JScrollPane? {
|
||||
val contentComponent = editor.contentComponent
|
||||
val scroll = SwingUtilities.getAncestorOfClass(JScrollPane::class.java, contentComponent) as? JScrollPane
|
||||
val rootPane = SwingUtilities.getRootPane(contentComponent)
|
||||
if (scroll == null || rootPane == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
size = scroll.size
|
||||
return scroll
|
||||
}
|
||||
|
||||
private fun finishPositioning(scroll: JScrollPane) {
|
||||
val rootPane = SwingUtilities.getRootPane(editor.contentComponent)
|
||||
val bounds = scroll.bounds
|
||||
bounds.translate(0, scroll.getHeight() - size.height)
|
||||
bounds.height = size.height
|
||||
val pos = SwingUtilities.convertPoint(scroll.getParent(), bounds.location, rootPane.getGlassPane())
|
||||
bounds.location = pos
|
||||
setBounds(bounds)
|
||||
}
|
||||
|
||||
private fun countLines(text: String): Int {
|
||||
if (text.isEmpty()) {
|
||||
return 1
|
||||
}
|
||||
|
||||
var count = 0
|
||||
var pos = -1
|
||||
while ((text.indexOf('\n', pos + 1).also { pos = it }) != -1) {
|
||||
count++
|
||||
}
|
||||
|
||||
if (text[text.length - 1] != '\n') {
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
override fun scrollLine() {
|
||||
scrollOffset(cachedLineHeight)
|
||||
}
|
||||
|
||||
override fun scrollPage() {
|
||||
scrollOffset(scrollPane.getVerticalScrollBar().visibleAmount)
|
||||
}
|
||||
|
||||
override fun scrollHalfPage() {
|
||||
val sa = scrollPane.getVerticalScrollBar().visibleAmount / 2.0
|
||||
val offset = ceil(sa / cachedLineHeight) * cachedLineHeight
|
||||
scrollOffset(offset.toInt())
|
||||
}
|
||||
|
||||
fun onBadKey() {
|
||||
labelComponent.setText(injector.messages.message("message.ex.output.more.prompt.full"))
|
||||
labelComponent.setFont(selectEditorFont(editor, labelComponent.text))
|
||||
}
|
||||
|
||||
private fun scrollOffset(more: Int) {
|
||||
scrollPane.validate()
|
||||
val scrollBar = scrollPane.getVerticalScrollBar()
|
||||
val value = scrollBar.value
|
||||
scrollBar.setValue(value + more)
|
||||
scrollPane.getHorizontalScrollBar().setValue(0)
|
||||
|
||||
// Check if we're at the end or if content fits entirely (nothing to scroll)
|
||||
if (isAtEnd) {
|
||||
labelComponent.setText(injector.messages.message("message.ex.output.end.prompt"))
|
||||
} else {
|
||||
labelComponent.setText(injector.messages.message("message.ex.output.more.prompt"))
|
||||
}
|
||||
labelComponent.setFont(selectEditorFont(editor, labelComponent.text))
|
||||
}
|
||||
|
||||
val isAtEnd: Boolean
|
||||
get() {
|
||||
if (isSingleLine) return true
|
||||
val contentHeight = textPane.preferredSize.height
|
||||
val viewportHeight = scrollPane.viewport.height
|
||||
if (contentHeight <= viewportHeight) return true
|
||||
val scrollBar = scrollPane.getVerticalScrollBar()
|
||||
return scrollBar.value >= scrollBar.maximum - scrollBar.visibleAmount
|
||||
}
|
||||
|
||||
private inner class OutputPanelKeyListener : KeyAdapter() {
|
||||
private class MoreKeyListener : KeyAdapter() {
|
||||
/**
|
||||
* Invoked when a key has been pressed.
|
||||
*/
|
||||
override fun keyTyped(e: KeyEvent) {
|
||||
val currentPanel: VimOutputPanel = injector.outputPanel.getCurrentOutputPanel() ?: return
|
||||
|
||||
val keyChar = e.keyChar
|
||||
val keyCode = e.getKeyCode()
|
||||
val keyChar = e.getKeyChar()
|
||||
val modifiers = e.modifiersEx
|
||||
val keyStroke = KeyStroke.getKeyStroke(keyChar, modifiers)
|
||||
val keyStroke = if (keyChar == KeyEvent.CHAR_UNDEFINED)
|
||||
KeyStroke.getKeyStroke(keyCode, modifiers)
|
||||
else
|
||||
KeyStroke.getKeyStroke(keyChar, modifiers)
|
||||
currentPanel.handleKey(keyStroke)
|
||||
}
|
||||
|
||||
override fun keyPressed(e: KeyEvent) {
|
||||
if (!e.isActionKey && e.keyCode != KeyEvent.VK_ENTER) return
|
||||
val currentPanel = injector.outputPanel.getCurrentOutputPanel() as? OutputPanel ?: return
|
||||
|
||||
val keyCode = e.keyCode
|
||||
val modifiers = e.modifiersEx
|
||||
val keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers)
|
||||
|
||||
if (isSingleLine) {
|
||||
currentPanel.close(keyStroke)
|
||||
e.consume()
|
||||
return
|
||||
}
|
||||
|
||||
// Multi-line mode: arrow keys scroll, down/right at end closes
|
||||
when (keyCode) {
|
||||
KeyEvent.VK_ENTER -> {
|
||||
if (currentPanel.isAtEnd) currentPanel.close() else currentPanel.scrollLine()
|
||||
e.consume()
|
||||
}
|
||||
|
||||
KeyEvent.VK_DOWN -> if (currentPanel.isAtEnd) currentPanel.close(keyStroke) else currentPanel.scrollLine()
|
||||
KeyEvent.VK_RIGHT -> if (currentPanel.isAtEnd) currentPanel.close(keyStroke) else currentPanel.scrollLine()
|
||||
KeyEvent.VK_UP -> currentPanel.scrollOffset(-cachedLineHeight)
|
||||
KeyEvent.VK_LEFT -> currentPanel.scrollOffset(-cachedLineHeight)
|
||||
KeyEvent.VK_PAGE_DOWN -> if (currentPanel.isAtEnd) currentPanel.close(keyStroke) else currentPanel.scrollPage()
|
||||
KeyEvent.VK_PAGE_UP -> currentPanel.scrollOffset(-scrollPane.verticalScrollBar.visibleAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LafListener : LafManagerListener {
|
||||
override fun lookAndFeelChanged(source: LafManager) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
// This listener is only invoked for local scenarios, and we only need to update local editor UI. This will invoke
|
||||
// updateUI on the output pane and it's child components
|
||||
for (vimEditor in injector.editorGroup.getEditors()) {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
if (!isPanelActive(editor)) continue
|
||||
@@ -502,24 +414,41 @@ class OutputPanel private constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG: VimLogger = injector.getLogger<OutputPanel>(OutputPanel::class.java)
|
||||
|
||||
fun getNullablePanel(editor: Editor): OutputPanel? {
|
||||
return editor.vimMorePanel as OutputPanel?
|
||||
return editor.vimMorePanel as? OutputPanel
|
||||
}
|
||||
|
||||
fun isPanelActive(editor: Editor): Boolean {
|
||||
return getNullablePanel(editor) != null
|
||||
return getNullablePanel(editor)?.myActive ?: false
|
||||
}
|
||||
|
||||
fun getInstance(editor: Editor): OutputPanel {
|
||||
var panel: OutputPanel? = getNullablePanel(editor)
|
||||
if (panel == null) {
|
||||
panel = OutputPanel(editor)
|
||||
panel = OutputPanel(WeakReference(editor))
|
||||
editor.vimMorePanel = panel
|
||||
}
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun countLines(text: String): Int {
|
||||
if (text.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
|
||||
var count = 0
|
||||
var pos = -1
|
||||
while ((text.indexOf('\n', pos + 1).also { pos = it }) != -1) {
|
||||
count++
|
||||
}
|
||||
|
||||
if (text[text.length - 1] != '\n') {
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class TextLine(val text: String, val color: Color?)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -22,11 +22,11 @@ class IjOutputPanelService : VimOutputPanelServiceBase() {
|
||||
private var activeOutputPanel: WeakReference<VimOutputPanel>? = null
|
||||
|
||||
override fun getCurrentOutputPanel(): VimOutputPanel? {
|
||||
return activeOutputPanel?.get()?.takeIf { (it as OutputPanel).active }
|
||||
return activeOutputPanel?.get()?.takeIf { (it as OutputPanel).isActive }
|
||||
}
|
||||
|
||||
override fun create(editor: VimEditor, context: ExecutionContext): VimOutputPanel {
|
||||
val panel = OutputPanel.getInstance(editor.ij)
|
||||
val panel = OutputPanel(WeakReference(editor.ij))
|
||||
activeOutputPanel = WeakReference(panel)
|
||||
return panel
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.maddyhome.idea.vim.vimscript.model.functions.handlers
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.impl.PresentationFactory
|
||||
import com.intellij.openapi.actionSystem.impl.Utils
|
||||
import com.intellij.openapi.keymap.impl.ActionProcessor
|
||||
import com.intellij.vim.annotations.VimscriptFunction
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.asVimInt
|
||||
import com.maddyhome.idea.vim.vimscript.model.functions.BuiltinFunctionHandler
|
||||
import java.awt.event.KeyEvent
|
||||
|
||||
@VimscriptFunction(name = "isactionenabled")
|
||||
internal class IsActionEnabled : BuiltinFunctionHandler<VimInt>() {
|
||||
override fun doFunction(
|
||||
arguments: Arguments,
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
vimContext: VimLContext,
|
||||
): VimInt {
|
||||
val action = ActionManager.getInstance().getAction(arguments.getString(0).value)
|
||||
if (action == null) {
|
||||
return false.asVimInt()
|
||||
}
|
||||
|
||||
val presentationFactory = PresentationFactory()
|
||||
val wrappedContext = Utils.createAsyncDataContext(context.ij)
|
||||
val actionProcessor = object : ActionProcessor() {}
|
||||
val inputEventAdjusted = KeyEvent(editor.ij.contentComponent, KeyEvent.KEY_PRESSED, 0L, 0, KeyEvent.VK_UNDEFINED, '\u0000')
|
||||
|
||||
val updateEvent = Utils.runUpdateSessionForInputEvent(listOf(action), inputEventAdjusted, wrappedContext, "IdeaVim", actionProcessor, presentationFactory) { _, updater, events ->
|
||||
val presentation = updater(action)
|
||||
events[presentation]
|
||||
}
|
||||
|
||||
val result = updateEvent != null && updateEvent.presentation.isEnabled
|
||||
return result.asVimInt()
|
||||
}
|
||||
}
|
||||
17
src/main/resources/IdeaVIM.ideavim-frontend-split.xml
Normal file
17
src/main/resources/IdeaVIM.ideavim-frontend-split.xml
Normal 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>
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler",
|
||||
"isactionenabled": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.IsActionEnabled",
|
||||
"pumvisible": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.PopupMenuVisibleFunctionHandler"
|
||||
}
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -134,7 +134,7 @@ class CmdCommandTest : VimTestCase() {
|
||||
VimPlugin.getCommand().resetAliases()
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("command! -range Error echo <args>"))
|
||||
assertPluginError(true)
|
||||
assertPluginError(false)
|
||||
kotlin.test.assertEquals("'-range' is not supported by `command`", injector.messages.getStatusBarMessage())
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ class CmdCommandTest : VimTestCase() {
|
||||
VimPlugin.getCommand().resetAliases()
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("command! -complete=color Error echo <args>"))
|
||||
assertPluginError(true)
|
||||
assertPluginError(false)
|
||||
kotlin.test.assertEquals("'-complete' is not supported by `command`", injector.messages.getStatusBarMessage())
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -24,6 +24,7 @@ class ExecuteCommandTest : VimTestCase() {
|
||||
fun `test execute with range`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("1,2execute 'echo 42'"))
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ class HistoryCommandTest : VimTestCase() {
|
||||
fun `test history with 'history' option set to 0 shows nothing`() {
|
||||
enterCommand("set history=0")
|
||||
enterCommand("history")
|
||||
assertNoExOutput()
|
||||
assertPluginError(false)
|
||||
assertPluginErrorMessage("'history' option is zero")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2025 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
|
||||
@@ -43,6 +43,7 @@ class AndFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test and function with list causes error`() {
|
||||
enterCommand("echo and([1, 2, 3], [2, 3, 4])")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E745: Using a List as a Number")
|
||||
}
|
||||
@@ -50,6 +51,7 @@ class AndFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test and function with dict causes error`() {
|
||||
enterCommand("echo and({1: 2, 3: 4}, {3: 4, 5: 6})")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
||||
}
|
||||
@@ -57,6 +59,7 @@ class AndFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test and function with float causes error`() {
|
||||
enterCommand("echo and(1.5, 2.5)")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E805: Using a Float as a Number")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2025 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
|
||||
@@ -28,6 +28,7 @@ class InvertFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test invert function with list causes error`() {
|
||||
enterCommand("echo invert([1, 2, 3])")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E745: Using a List as a Number")
|
||||
}
|
||||
@@ -35,6 +36,7 @@ class InvertFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test invert function with dict causes error`() {
|
||||
enterCommand("echo invert({1: 2, 3: 4})")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
||||
}
|
||||
@@ -42,6 +44,7 @@ class InvertFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test invert function with float causes error`() {
|
||||
enterCommand("echo invert(1.5)")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E805: Using a Float as a Number")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2025 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
|
||||
@@ -43,6 +43,7 @@ class OrFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test or function with list causes error`() {
|
||||
enterCommand("echo or([1, 2, 3], [2, 3, 4])")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E745: Using a List as a Number")
|
||||
}
|
||||
@@ -50,6 +51,7 @@ class OrFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test or function with dict causes error`() {
|
||||
enterCommand("echo or({1: 2, 3: 4}, {3: 4, 5: 6})")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
||||
}
|
||||
@@ -57,6 +59,7 @@ class OrFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test or function with float causes error`() {
|
||||
enterCommand("echo or(1.5, 2.5)")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E805: Using a Float as a Number")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2025 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
|
||||
@@ -43,6 +43,7 @@ class XorFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test xor function with list causes error`() {
|
||||
enterCommand("echo xor([1, 2, 3], [2, 3, 4])")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E745: Using a List as a Number")
|
||||
}
|
||||
@@ -50,6 +51,7 @@ class XorFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test xor function with dict causes error`() {
|
||||
enterCommand("echo xor({1: 2, 3: 4}, {3: 4, 5: 6})")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E728: Using a Dictionary as a Number")
|
||||
}
|
||||
@@ -57,6 +59,7 @@ class XorFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test xor function with float causes error`() {
|
||||
enterCommand("echo xor(1.5, 2.5)")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E805: Using a Float as a Number")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2025 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
|
||||
@@ -33,6 +33,7 @@ class ToLowerFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test tolower with list causes error`() {
|
||||
enterCommand("echo tolower([1, 2, 3])")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E730: Using a List as a String")
|
||||
}
|
||||
@@ -40,6 +41,7 @@ class ToLowerFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test tolower with dict causes error`() {
|
||||
enterCommand("echo tolower({1: 2, 3: 4})")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E731: Using a Dictionary as a String")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2025 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
|
||||
@@ -33,6 +33,7 @@ class ToUpperFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test toupper with list causes error`() {
|
||||
enterCommand("echo toupper([1, 2, 3])")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E730: Using a List as a String")
|
||||
}
|
||||
@@ -40,6 +41,7 @@ class ToUpperFunctionTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test toupper with dict causes error`() {
|
||||
enterCommand("echo toupper({1: 2, 3: 4})")
|
||||
assertNoExOutput()
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E731: Using a Dictionary as a String")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -207,12 +207,7 @@ class FunctionDeclarationTest : VimTestCase() {
|
||||
typeText(commandToKeys("echo F1()"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessage("E121: Undefined variable: x")
|
||||
assertExOutput(
|
||||
"""
|
||||
E121: Undefined variable: x
|
||||
0
|
||||
""".trimIndent()
|
||||
)
|
||||
assertExOutput("0")
|
||||
|
||||
typeText(commandToKeys("delf! F1"))
|
||||
typeText(commandToKeys("delf! F2"))
|
||||
|
||||
@@ -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
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -154,12 +154,7 @@ class TryCatchTest : VimTestCase() {
|
||||
),
|
||||
)
|
||||
assertPluginError(true)
|
||||
assertExOutput(
|
||||
"""
|
||||
finally block
|
||||
my exception
|
||||
""".trimIndent()
|
||||
)
|
||||
assertExOutput("finally block")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -223,7 +223,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
) {
|
||||
enterCommand("set nowrapscan")
|
||||
}
|
||||
assertPluginError(true)
|
||||
assertPluginError(false)
|
||||
assertPluginErrorMessage("E385: Search hit BOTTOM without match for: one")
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
three
|
||||
""".trimIndent()
|
||||
)
|
||||
assertPluginError(true)
|
||||
assertPluginError(false)
|
||||
assertPluginErrorMessage("E486: Pattern not found: banana")
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
) {
|
||||
enterCommand("set nowrapscan")
|
||||
}
|
||||
assertPluginError(true)
|
||||
assertPluginError(false)
|
||||
assertPluginErrorMessage("E384: Search hit TOP without match for: three")
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
three
|
||||
""".trimIndent()
|
||||
)
|
||||
assertPluginError(true)
|
||||
assertPluginError(false)
|
||||
assertPluginErrorMessage("E486: Pattern not found: banana")
|
||||
}
|
||||
|
||||
@@ -615,7 +615,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
)
|
||||
enterCommand("set nowrapscan")
|
||||
typeText("10", "/", searchCommand("one"))
|
||||
assertPluginError(true)
|
||||
assertPluginError(false)
|
||||
assertPluginErrorMessage("E385: Search hit BOTTOM without match for: one")
|
||||
assertPosition(2, 0)
|
||||
}
|
||||
@@ -679,7 +679,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
)
|
||||
enterCommand("set nowrapscan")
|
||||
typeText("12", "?one<CR>")
|
||||
assertPluginError(true)
|
||||
assertPluginError(false)
|
||||
assertPluginErrorMessage("E384: Search hit TOP without match for: one")
|
||||
assertPosition(8, 0)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -12,25 +12,7 @@ import com.maddyhome.idea.vim.helper.EngineMessageHelper
|
||||
import org.jetbrains.annotations.PropertyKey
|
||||
|
||||
interface VimMessages {
|
||||
/**
|
||||
* Displays an informational message to the user.
|
||||
* The message panel closes on any keystroke and passes the key through to the editor.
|
||||
*/
|
||||
fun showMessage(editor: VimEditor, message: String?)
|
||||
|
||||
/**
|
||||
* Displays an error message to the user (typically in red).
|
||||
* The message panel closes on any keystroke and passes the key through to the editor.
|
||||
*/
|
||||
fun showErrorMessage(editor: VimEditor, message: String?)
|
||||
|
||||
/**
|
||||
* Legacy method for displaying messages.
|
||||
* @deprecated Use [showMessage] or [showErrorMessage] instead.
|
||||
*/
|
||||
@Deprecated("Use showMessage or showErrorMessage instead", ReplaceWith("showMessage(editor, message)"))
|
||||
fun showStatusBarMessage(editor: VimEditor?, message: String?)
|
||||
|
||||
fun getStatusBarMessage(): String?
|
||||
fun clearStatusBarMessage()
|
||||
fun indicateError()
|
||||
@@ -46,4 +28,13 @@ interface VimMessages {
|
||||
fun message(@PropertyKey(resourceBundle = EngineMessageHelper.BUNDLE) key: String, vararg params: Any): String
|
||||
|
||||
fun updateStatusBar(editor: VimEditor)
|
||||
|
||||
fun showMessage(editor: VimEditor, message: String) {
|
||||
showStatusBarMessage(editor, message)
|
||||
}
|
||||
|
||||
fun showErrorMessage(editor: VimEditor, message: String?) {
|
||||
showStatusBarMessage(editor, message)
|
||||
indicateError()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -29,8 +29,7 @@ interface VimOutputPanel {
|
||||
* Note: The full text content is not updated in the display until [show] is invoked.
|
||||
*
|
||||
* @param text The text to append.
|
||||
* @param isNewLine Whether to start the appended text on a new line.
|
||||
* @param messageType The type of message, used to determine text styling.
|
||||
* @param isNewLine Whether to start the appended text on a new line. Defaults to true.
|
||||
*/
|
||||
fun addText(text: String, isNewLine: Boolean = true, messageType: MessageType = MessageType.STANDARD)
|
||||
|
||||
@@ -52,4 +51,4 @@ interface VimOutputPanel {
|
||||
|
||||
fun setContent(text: String)
|
||||
fun clearText()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -26,12 +26,8 @@ interface VimOutputPanelService {
|
||||
fun getCurrentOutputPanel(): VimOutputPanel?
|
||||
|
||||
/**
|
||||
* Appends text to the existing output panel or creates a new one with the given text and message type.
|
||||
* Appends text to the existing output panel or creates a new one with the given text.
|
||||
* Basic method that should be sufficient in most cases.
|
||||
*/
|
||||
fun output(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
text: String,
|
||||
messageType: MessageType = MessageType.STANDARD,
|
||||
)
|
||||
}
|
||||
fun output(editor: VimEditor, context: ExecutionContext, text: String)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2026 The IdeaVim authors
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
@@ -13,9 +13,9 @@ abstract class VimOutputPanelServiceBase : VimOutputPanelService {
|
||||
return getCurrentOutputPanel() ?: create(editor, context)
|
||||
}
|
||||
|
||||
override fun output(editor: VimEditor, context: ExecutionContext, text: String, messageType: MessageType) {
|
||||
override fun output(editor: VimEditor, context: ExecutionContext, text: String) {
|
||||
val panel = getOrCreate(editor, context)
|
||||
panel.addText(text, true, messageType)
|
||||
panel.addText(text)
|
||||
panel.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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>",
|
||||
|
||||
Reference in New Issue
Block a user