mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2026-05-01 02:50:39 +02:00
Compare commits
78 Commits
customized
...
customized
| Author | SHA1 | Date | |
|---|---|---|---|
|
a565dab624
|
|||
|
97bbe7d996
|
|||
|
cdc525d62b
|
|||
|
62cb93b8fd
|
|||
|
412dbfffb1
|
|||
|
fcbd4f3ecd
|
|||
|
efea120fc4
|
|||
|
93a3ef99a2
|
|||
|
7e0825250c
|
|||
|
9b8413a4d4
|
|||
|
5938e20aa9
|
|||
|
bf282dbb8b
|
|||
|
f729d69ebd
|
|||
|
a6648469b2
|
|||
|
42d5a14b97
|
|||
|
ef9f204069
|
|||
|
dcaa3e081d
|
|||
|
480c891e0e
|
|||
|
def269e35f
|
|||
|
0fcbbdf0ce
|
|||
|
59f267e723
|
|||
|
e69921d4d3
|
|||
|
4ae3a9f426
|
|||
|
d44afe5284
|
|||
|
4612f5ce68
|
|||
|
3292bc65fd
|
|||
|
04e67e622a
|
|||
|
b51714e9f9
|
|||
|
f8d3e9d98e
|
|||
|
b152819d2b
|
|||
|
fe90c24a46
|
|||
|
8b636f9dde
|
|||
|
376bf98dee
|
|||
|
a4c70083aa
|
|||
|
|
9f33320e66 | ||
|
|
5dc5aaa0d8 | ||
|
|
0d8d215946 | ||
|
|
c9234b82f5 | ||
|
|
b9bd523648 | ||
|
|
4f5b793642 | ||
|
|
2fdf52d305 | ||
|
|
56103c990b | ||
|
|
1e489e2c14 | ||
|
|
06d877415d | ||
|
|
c946ecde86 | ||
|
|
00e1d8173e | ||
|
|
cff4afa050 | ||
|
|
706ac76b5e | ||
|
|
98934ff7bf | ||
|
|
c26658d27d | ||
|
|
f6fb0fbea6 | ||
|
|
74cf6fbee8 | ||
|
|
b22089f50f | ||
|
|
dcb15c826c | ||
|
|
4ddc30055c | ||
|
|
20b46279ad | ||
|
|
3653e7f193 | ||
|
|
fd3222dd76 | ||
|
|
11ca10d10a | ||
|
|
fa33b264ba | ||
|
|
46a48b03a1 | ||
|
|
46823abcda | ||
| d85e7dba19 | |||
|
|
a9c3277a51 | ||
|
|
6e6039c22a | ||
|
|
b49e896b41 | ||
|
|
122b066b75 | ||
|
|
cb24ac2bfa | ||
|
|
b14324a3e6 | ||
|
|
e40a839f52 | ||
|
|
a45cc0891b | ||
|
|
89bad651c0 | ||
|
|
5150dc0c9e | ||
|
|
c6c7d68876 | ||
|
|
02130a87c9 | ||
|
|
40ba977e58 | ||
|
|
21f304a560 | ||
|
|
36e8bd4663 |
50
.github/workflows/runSplitModeTests.yml
vendored
50
.github/workflows/runSplitModeTests.yml
vendored
@@ -1,50 +0,0 @@
|
|||||||
name: Run Split Mode Tests
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
jobs:
|
|
||||||
test-linux:
|
|
||||||
if: github.repository == 'JetBrains/ideavim'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Free up disk space
|
|
||||||
run: |
|
|
||||||
echo "Disk space before cleanup:"
|
|
||||||
df -h
|
|
||||||
sudo rm -rf /usr/share/dotnet
|
|
||||||
sudo rm -rf /usr/local/lib/android
|
|
||||||
sudo rm -rf /opt/ghc
|
|
||||||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
|
||||||
sudo docker image prune --all --force
|
|
||||||
echo "Disk space after cleanup:"
|
|
||||||
df -h
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: zulu
|
|
||||||
java-version: 21
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
with:
|
|
||||||
cache-read-only: false
|
|
||||||
- name: Start Xvfb
|
|
||||||
run: |
|
|
||||||
Xvfb :99 -screen 0 1920x1080x24 &
|
|
||||||
echo "DISPLAY=:99" >> $GITHUB_ENV
|
|
||||||
- name: Run split mode tests
|
|
||||||
run: gradle :tests:split-mode-tests:testSplitMode --console=plain
|
|
||||||
- name: Upload reports
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: split-mode-reports
|
|
||||||
path: |
|
|
||||||
tests/split-mode-tests/build/reports
|
|
||||||
out/ide-tests/tests/**/log
|
|
||||||
out/ide-tests/tests/**/frontend/log
|
|
||||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -33,5 +33,6 @@
|
|||||||
</option>
|
</option>
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="parallelModelFetch" value="true" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
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">
|
<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" />
|
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
|
||||||
<ExternalSystemSettings>
|
<ExternalSystemSettings>
|
||||||
<option name="executionName" />
|
<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>
|
||||||
10
.teamcity/_Self/Project.kt
vendored
10
.teamcity/_Self/Project.kt
vendored
@@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package _Self
|
package _Self
|
||||||
|
|
||||||
import _Self.buildTypes.Compatibility
|
import _Self.buildTypes.Compatibility
|
||||||
@@ -6,6 +14,7 @@ import _Self.buildTypes.Nvim
|
|||||||
import _Self.buildTypes.PluginVerifier
|
import _Self.buildTypes.PluginVerifier
|
||||||
import _Self.buildTypes.PropertyBased
|
import _Self.buildTypes.PropertyBased
|
||||||
import _Self.buildTypes.RandomOrderTests
|
import _Self.buildTypes.RandomOrderTests
|
||||||
|
import _Self.buildTypes.SplitModeTests
|
||||||
|
|
||||||
import _Self.buildTypes.TestingBuildType
|
import _Self.buildTypes.TestingBuildType
|
||||||
import _Self.buildTypes.TypeScriptTest
|
import _Self.buildTypes.TypeScriptTest
|
||||||
@@ -30,6 +39,7 @@ object Project : Project({
|
|||||||
buildType(PropertyBased)
|
buildType(PropertyBased)
|
||||||
buildType(LongRunning)
|
buildType(LongRunning)
|
||||||
buildType(RandomOrderTests)
|
buildType(RandomOrderTests)
|
||||||
|
buildType(SplitModeTests)
|
||||||
|
|
||||||
buildType(Nvim)
|
buildType(Nvim)
|
||||||
buildType(PluginVerifier)
|
buildType(PluginVerifier)
|
||||||
|
|||||||
12
.teamcity/_Self/buildTypes/Compatibility.kt
vendored
12
.teamcity/_Self/buildTypes/Compatibility.kt
vendored
@@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package _Self.buildTypes
|
package _Self.buildTypes
|
||||||
|
|
||||||
import _Self.AgentSize
|
import _Self.AgentSize
|
||||||
@@ -11,6 +19,10 @@ object Compatibility : IdeaVimBuildType({
|
|||||||
id("IdeaVimCompatibility")
|
id("IdeaVimCompatibility")
|
||||||
name = "IdeaVim compatibility with external plugins"
|
name = "IdeaVim compatibility with external plugins"
|
||||||
|
|
||||||
|
failureConditions {
|
||||||
|
executionTimeoutMin = 180
|
||||||
|
}
|
||||||
|
|
||||||
vcs {
|
vcs {
|
||||||
root(DslContext.settingsRoot)
|
root(DslContext.settingsRoot)
|
||||||
branchFilter = "+:<default>"
|
branchFilter = "+:<default>"
|
||||||
|
|||||||
12
.teamcity/_Self/buildTypes/RandomOrderTests.kt
vendored
12
.teamcity/_Self/buildTypes/RandomOrderTests.kt
vendored
@@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package _Self.buildTypes
|
package _Self.buildTypes
|
||||||
|
|
||||||
import _Self.AgentSize
|
import _Self.AgentSize
|
||||||
@@ -26,7 +34,7 @@ object RandomOrderTests : IdeaVimBuildType({
|
|||||||
gradle {
|
gradle {
|
||||||
clearConditions()
|
clearConditions()
|
||||||
tasks = """
|
tasks = """
|
||||||
test
|
clean test
|
||||||
-x :tests:property-tests:test
|
-x :tests:property-tests:test
|
||||||
-x :tests:long-running-tests:test
|
-x :tests:long-running-tests:test
|
||||||
-Djunit.jupiter.execution.order.random.seed=default
|
-Djunit.jupiter.execution.order.random.seed=default
|
||||||
@@ -34,7 +42,7 @@ object RandomOrderTests : IdeaVimBuildType({
|
|||||||
""".trimIndent().replace("\n", " ")
|
""".trimIndent().replace("\n", " ")
|
||||||
buildFile = ""
|
buildFile = ""
|
||||||
enableStacktrace = true
|
enableStacktrace = true
|
||||||
gradleParams = "--build-cache --configuration-cache"
|
gradleParams = "--no-build-cache --configuration-cache"
|
||||||
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
|
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
.teamcity/_Self/buildTypes/SplitModeTests.kt
vendored
Normal file
63
.teamcity/_Self/buildTypes/SplitModeTests.kt
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package _Self.buildTypes
|
||||||
|
|
||||||
|
import _Self.AgentSize
|
||||||
|
import _Self.IdeaVimBuildType
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs
|
||||||
|
|
||||||
|
object SplitModeTests : IdeaVimBuildType({
|
||||||
|
name = "Split mode tests"
|
||||||
|
description = "Tests for IdeaVim in Remote Development split mode (backend + frontend)"
|
||||||
|
|
||||||
|
artifactRules = """
|
||||||
|
+:tests/split-mode-tests/build/reports => split-mode-tests/build/reports
|
||||||
|
+:out/ide-tests/tests/**/log => out/ide-tests/log
|
||||||
|
+:out/ide-tests/tests/**/frontend/log => out/ide-tests/frontend-log
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
params {
|
||||||
|
param("env.ORG_GRADLE_PROJECT_downloadIdeaSources", "false")
|
||||||
|
param("env.ORG_GRADLE_PROJECT_instrumentPluginCode", "false")
|
||||||
|
param("env.DISPLAY", ":99")
|
||||||
|
}
|
||||||
|
|
||||||
|
vcs {
|
||||||
|
root(DslContext.settingsRoot)
|
||||||
|
branchFilter = "+:<default>"
|
||||||
|
|
||||||
|
checkoutMode = CheckoutMode.AUTO
|
||||||
|
}
|
||||||
|
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
name = "Start Xvfb and run split mode tests"
|
||||||
|
scriptContent = """
|
||||||
|
Xvfb :99 -screen 0 1920x1080x24 &
|
||||||
|
sleep 2
|
||||||
|
./gradlew :tests:split-mode-tests:testSplitMode --console=plain --build-cache --configuration-cache --stacktrace
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers {
|
||||||
|
vcs {
|
||||||
|
branchFilter = "+:<default>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requirements {
|
||||||
|
// Use a larger agent for split-mode tests — they launch two full IDE instances
|
||||||
|
equals("teamcity.agent.hardware.cpuCount", AgentSize.XLARGE)
|
||||||
|
equals("teamcity.agent.os.family", "Linux")
|
||||||
|
}
|
||||||
|
})
|
||||||
12
.teamcity/_Self/buildTypes/TestingBuildType.kt
vendored
12
.teamcity/_Self/buildTypes/TestingBuildType.kt
vendored
@@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
@file:Suppress("ClassName")
|
@file:Suppress("ClassName")
|
||||||
|
|
||||||
package _Self.buildTypes
|
package _Self.buildTypes
|
||||||
@@ -41,10 +49,10 @@ open class TestingBuildType(
|
|||||||
steps {
|
steps {
|
||||||
gradle {
|
gradle {
|
||||||
clearConditions()
|
clearConditions()
|
||||||
tasks = "test -x :tests:property-tests:test -x :tests:long-running-tests:test"
|
tasks = "clean test -x :tests:property-tests:test -x :tests:long-running-tests:test"
|
||||||
buildFile = ""
|
buildFile = ""
|
||||||
enableStacktrace = true
|
enableStacktrace = true
|
||||||
gradleParams = "--build-cache --configuration-cache"
|
gradleParams = "--no-build-cache --configuration-cache"
|
||||||
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
|
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -542,6 +542,10 @@ Contributors:
|
|||||||
[![icon][github]](https://github.com/1grzyb1)
|
[![icon][github]](https://github.com/1grzyb1)
|
||||||
|
|
||||||
1grzyb1
|
1grzyb1
|
||||||
|
* [![icon][mail]](mailto:yury@digitalby.me)
|
||||||
|
[![icon][github]](https://github.com/digitalby)
|
||||||
|
|
||||||
|
digitalby
|
||||||
|
|
||||||
Contributors with JetBrains IP:
|
Contributors with JetBrains IP:
|
||||||
|
|
||||||
|
|||||||
23
CHANGES.md
23
CHANGES.md
@@ -36,14 +36,37 @@ usual beta standards.
|
|||||||
* [VIM-566](https://youtrack.jetbrains.com/issue/VIM-566) Added support for `:set foldlevel` option - control fold visibility level
|
* [VIM-566](https://youtrack.jetbrains.com/issue/VIM-566) Added support for `:set foldlevel` option - control fold visibility level
|
||||||
|
|
||||||
### Fixes:
|
### Fixes:
|
||||||
|
* [VIM-4135](https://youtrack.jetbrains.com/issue/VIM-4135) Fixed IdeaVim not loading in Rider
|
||||||
|
* [VIM-4134](https://youtrack.jetbrains.com/issue/VIM-4134) Fixed undo in commentary - `gcc`/`gc{motion}` changes are now properly grouped as a single undo step
|
||||||
|
* [VIM-4134](https://youtrack.jetbrains.com/issue/VIM-4134) Fixed `=` (format/auto-indent) action in split mode
|
||||||
|
* [VIM-4134](https://youtrack.jetbrains.com/issue/VIM-4134) Fixed global marks causing errors when used inside write actions (e.g., during document modifications)
|
||||||
* [VIM-4105](https://youtrack.jetbrains.com/issue/VIM-4105) Fixed `a"` `a'` `a\`` text objects to include surrounding whitespace per Vim spec
|
* [VIM-4105](https://youtrack.jetbrains.com/issue/VIM-4105) Fixed `a"` `a'` `a\`` text objects to include surrounding whitespace per Vim spec
|
||||||
* [VIM-4097](https://youtrack.jetbrains.com/issue/VIM-4097) Fixed `<A-n>` (NextOccurrence) with text containing backslashes - e.g., selecting `\IntegerField` now works correctly
|
* [VIM-4097](https://youtrack.jetbrains.com/issue/VIM-4097) Fixed `<A-n>` (NextOccurrence) with text containing backslashes - e.g., selecting `\IntegerField` now works correctly
|
||||||
* [VIM-4094](https://youtrack.jetbrains.com/issue/VIM-4094) Fixed UninitializedPropertyAccessException when loading history
|
* [VIM-4094](https://youtrack.jetbrains.com/issue/VIM-4094) Fixed UninitializedPropertyAccessException when loading history
|
||||||
|
* [VIM-4016](https://youtrack.jetbrains.com/issue/VIM-4016) Fixed `:edit` command when project has no source roots
|
||||||
* [VIM-3948](https://youtrack.jetbrains.com/issue/VIM-3948) Improved hint generation visibility checks for better UI component detection
|
* [VIM-3948](https://youtrack.jetbrains.com/issue/VIM-3948) Improved hint generation visibility checks for better UI component detection
|
||||||
|
* [VIM-3473](https://youtrack.jetbrains.com/issue/VIM-3473) Fixed "Reload .ideavimrc" action in remote development (split) mode - no longer causes File Cache Conflict dialogs
|
||||||
|
* [VIM-2821](https://youtrack.jetbrains.com/issue/VIM-2821) Fixed undo grouping when repeating text insertion with `.` in remote development (split mode)
|
||||||
|
* [VIM-1705](https://youtrack.jetbrains.com/issue/VIM-1705) Fixed window-switching commands (e.g., `<C-w>h`) during macro playback
|
||||||
|
* Fixed `pumvisible()` function returning incorrect result (was inverted)
|
||||||
|
* Fixed `<Esc>` not properly exiting insert mode in Rider/CLion when canceling a completion lookup
|
||||||
|
* Fixed `<Esc>` not exiting insert mode after `<C-Space>` completion in Rider
|
||||||
|
* Fixed `<Esc>` in search bar no longer inserts `^[` literal text when search is not found - panel is now properly closed
|
||||||
|
* Fixed IdeaVim entering broken state when a VimScript extension plugin fails to initialize
|
||||||
|
* Fixed compatibility issues with external plugins (e.g., IdeaVim-EasyMotion, multicursor)
|
||||||
|
* Fixed recursive key mappings (e.g., `map b wbb`) causing an apparent infinite loop - `maxmapdepth` limit now properly terminates the entire mapping chain
|
||||||
|
* Fixed NERDTree `gs`/`gi` preview split commands to keep focus on the tree
|
||||||
|
* Fixed visual marks (`<` and `>`) position tracking after text deletion - `gv` now re-selects correctly
|
||||||
|
* Fixed `IndexOutOfBoundsException` when using text objects like `a)` at end of file
|
||||||
* Fixed high CPU usage while showing command line
|
* Fixed high CPU usage while showing command line
|
||||||
* Fixed comparison of String and Number in VimScript expressions
|
* Fixed comparison of String and Number in VimScript expressions
|
||||||
|
|
||||||
### Merged PRs:
|
### Merged PRs:
|
||||||
|
* [1632](https://github.com/JetBrains/ideavim/pull/1632) by [chylex](https://github.com/chylex): Fix pumvisible returning opposite result
|
||||||
|
* [1615](https://github.com/JetBrains/ideavim/pull/1615) by [1grzyb1](https://github.com/1grzyb1): Fix IndexOutOfBoundsException in findBlock when caret is at end of file
|
||||||
|
* [1613](https://github.com/JetBrains/ideavim/pull/1613) by [1grzyb1](https://github.com/1grzyb1): VIM-3473 Sync ideavim in remdev
|
||||||
|
* [1608](https://github.com/JetBrains/ideavim/pull/1608) by [1grzyb1](https://github.com/1grzyb1): VIM-4134 format using = action in split mode
|
||||||
|
* [1585](https://github.com/JetBrains/ideavim/pull/1585) by [1grzyb1](https://github.com/1grzyb1): Break in case of maximum recursion depth
|
||||||
* [1414](https://github.com/JetBrains/ideavim/pull/1414) by [Matt Ellis](https://github.com/citizenmatt): Refactor/functions
|
* [1414](https://github.com/JetBrains/ideavim/pull/1414) by [Matt Ellis](https://github.com/citizenmatt): Refactor/functions
|
||||||
* [1442](https://github.com/JetBrains/ideavim/pull/1442) by [Matt Ellis](https://github.com/citizenmatt): Fix high CPU usage while showing command line
|
* [1442](https://github.com/JetBrains/ideavim/pull/1442) by [Matt Ellis](https://github.com/citizenmatt): Fix high CPU usage while showing command line
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
package com.intellij.vim.api.scopes
|
package com.intellij.vim.api.scopes
|
||||||
|
|
||||||
import com.intellij.vim.api.VimApi
|
import com.intellij.vim.api.VimApi
|
||||||
|
import com.intellij.vim.api.models.CaretId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the range of a text object selection.
|
* Represents the range of a text object selection.
|
||||||
@@ -113,6 +114,15 @@ interface TextObjectScope {
|
|||||||
keys: String,
|
keys: String,
|
||||||
registerDefaultMapping: Boolean = true,
|
registerDefaultMapping: Boolean = true,
|
||||||
preserveSelectionAnchor: Boolean = true,
|
preserveSelectionAnchor: Boolean = true,
|
||||||
rangeProvider: suspend VimApi.(count: Int) -> TextObjectRange?,
|
rangeProvider: suspend VimApi.(caret: CaretId, count: Int) -> TextObjectRange?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun register(
|
||||||
|
keys: String,
|
||||||
|
registerDefaultMapping: Boolean = true,
|
||||||
|
preserveSelectionAnchor: Boolean = true,
|
||||||
|
rangeProvider: suspend VimApi.(count: Int) -> TextObjectRange?,
|
||||||
|
) {
|
||||||
|
register(keys, registerDefaultMapping, preserveSelectionAnchor) { _, count -> rangeProvider(count) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
120
build.gradle.kts
120
build.gradle.kts
@@ -6,6 +6,7 @@
|
|||||||
* https://opensource.org/licenses/MIT.
|
* https://opensource.org/licenses/MIT.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
|
||||||
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
|
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
|
||||||
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
|
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
@@ -26,11 +27,11 @@ buildscript {
|
|||||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.6.0.202603022253-r")
|
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.6.0.202603022253-r")
|
||||||
classpath("org.kohsuke:github-api:1.305")
|
classpath("org.kohsuke:github-api:1.305")
|
||||||
|
|
||||||
classpath("io.ktor:ktor-client-core:3.4.1")
|
classpath("io.ktor:ktor-client-core:3.4.2")
|
||||||
classpath("io.ktor:ktor-client-cio:3.4.1")
|
classpath("io.ktor:ktor-client-cio:3.4.2")
|
||||||
classpath("io.ktor:ktor-client-auth:3.4.1")
|
classpath("io.ktor:ktor-client-auth:3.4.2")
|
||||||
classpath("io.ktor:ktor-client-content-negotiation:3.4.1")
|
classpath("io.ktor:ktor-client-content-negotiation:3.4.2")
|
||||||
classpath("io.ktor:ktor-serialization-kotlinx-json:3.4.1")
|
classpath("io.ktor:ktor-serialization-kotlinx-json:3.4.2")
|
||||||
|
|
||||||
// This comes from the changelog plugin
|
// This comes from the changelog plugin
|
||||||
// classpath("org.jetbrains:markdown:0.3.1")
|
// classpath("org.jetbrains:markdown:0.3.1")
|
||||||
@@ -112,7 +113,7 @@ dependencies {
|
|||||||
testFramework(TestFrameworkType.Platform)
|
testFramework(TestFrameworkType.Platform)
|
||||||
testFramework(TestFrameworkType.JUnit5)
|
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-common")))
|
||||||
pluginModule(runtimeOnly(project(":modules:ideavim-frontend")))
|
pluginModule(runtimeOnly(project(":modules:ideavim-frontend")))
|
||||||
@@ -225,6 +226,30 @@ tasks {
|
|||||||
// localPath = file("/Users/{user}/Applications/WebStorm.app")
|
// localPath = file("/Users/{user}/Applications/WebStorm.app")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
val runPycharm by intellijPlatformTesting.runIde.registering {
|
||||||
|
type = IntelliJPlatformType.PyCharmProfessional
|
||||||
|
version = "2025.3.2"
|
||||||
|
task {
|
||||||
|
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val runWebstorm by intellijPlatformTesting.runIde.registering {
|
||||||
|
type = IntelliJPlatformType.WebStorm
|
||||||
|
version = "2025.3.2"
|
||||||
|
task {
|
||||||
|
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val runClion by intellijPlatformTesting.runIde.registering {
|
||||||
|
type = IntelliJPlatformType.CLion
|
||||||
|
version = "2025.3.2"
|
||||||
|
task {
|
||||||
|
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
|
val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
|
||||||
task {
|
task {
|
||||||
jvmArgumentProviders += CommandLineArgumentProvider {
|
jvmArgumentProviders += CommandLineArgumentProvider {
|
||||||
@@ -247,6 +272,55 @@ tasks {
|
|||||||
val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
|
val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
|
||||||
splitMode = true
|
splitMode = true
|
||||||
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
plugin("AceJump", "3.8.22")
|
||||||
|
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val runWebstormSplitMode by intellijPlatformTesting.runIde.registering {
|
||||||
|
type = IntelliJPlatformType.WebStorm
|
||||||
|
version = "2025.3.2"
|
||||||
|
splitMode = true
|
||||||
|
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
plugin("AceJump", "3.8.22")
|
||||||
|
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val runRider by intellijPlatformTesting.runIde.registering {
|
||||||
|
type = IntelliJPlatformType.Rider
|
||||||
|
version = "2026.1"
|
||||||
|
task {
|
||||||
|
systemProperty("idea.log.debug.categories", "com.maddyhome.idea.vim.handler.EditorHandlersChainLogger")
|
||||||
|
}
|
||||||
|
plugins {
|
||||||
|
plugin("AceJump", "3.8.22")
|
||||||
|
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val runCLionSplitMode by intellijPlatformTesting.runIde.registering {
|
||||||
|
type = IntelliJPlatformType.CLion
|
||||||
|
version = "2025.3.2"
|
||||||
|
splitMode = true
|
||||||
|
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
plugin("AceJump", "3.8.22")
|
||||||
|
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val runPycharmSplitMode by intellijPlatformTesting.runIde.registering {
|
||||||
|
type = IntelliJPlatformType.PyCharmProfessional
|
||||||
|
version = "2025.3.2"
|
||||||
|
splitMode = true
|
||||||
|
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
plugin("AceJump", "3.8.22")
|
||||||
|
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run split mode with a JDWP debug agent on the frontend (JetBrains Client) process.
|
// Run split mode with a JDWP debug agent on the frontend (JetBrains Client) process.
|
||||||
@@ -255,6 +329,11 @@ tasks {
|
|||||||
splitMode = true
|
splitMode = true
|
||||||
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
plugin("AceJump", "3.8.22")
|
||||||
|
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
|
||||||
|
}
|
||||||
|
|
||||||
prepareSandboxTask {
|
prepareSandboxTask {
|
||||||
val sandboxDir = project.layout.buildDirectory.dir("idea-sandbox").map { it.asFile }
|
val sandboxDir = project.layout.buildDirectory.dir("idea-sandbox").map { it.asFile }
|
||||||
doLast {
|
doLast {
|
||||||
@@ -286,6 +365,12 @@ tasks {
|
|||||||
val testIdeSplitMode by intellijPlatformTesting.testIde.registering {
|
val testIdeSplitMode by intellijPlatformTesting.testIde.registering {
|
||||||
splitMode = true
|
splitMode = true
|
||||||
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
splitModeTarget = SplitModeAware.SplitModeTarget.BOTH
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
plugin("AceJump", "3.8.22")
|
||||||
|
plugin("org.jetbrains.IdeaVim-EasyMotion", "1.16")
|
||||||
|
}
|
||||||
|
|
||||||
task {
|
task {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
@@ -359,14 +444,37 @@ intellijPlatform {
|
|||||||
* <a href="https://youtrack.jetbrains.com/issue/VIM-566">VIM-566</a> Added support for <code>:set foldlevel</code> option - control fold visibility level<br>
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-566">VIM-566</a> Added support for <code>:set foldlevel</code> option - control fold visibility level<br>
|
||||||
<br>
|
<br>
|
||||||
<b>Fixes:</b><br>
|
<b>Fixes:</b><br>
|
||||||
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-4135">VIM-4135</a> Fixed IdeaVim not loading in Rider<br>
|
||||||
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-4134">VIM-4134</a> Fixed undo in commentary - <code>gcc</code>/<code>gc{motion}</code> changes are now properly grouped as a single undo step<br>
|
||||||
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-4134">VIM-4134</a> Fixed <code>=</code> (format/auto-indent) action in split mode<br>
|
||||||
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-4134">VIM-4134</a> Fixed global marks causing errors when used inside write actions (e.g., during document modifications)<br>
|
||||||
* <a href="https://youtrack.jetbrains.com/issue/VIM-4105">VIM-4105</a> Fixed <code>a"</code> <code>a'</code> <code>a`</code> text objects to include surrounding whitespace per Vim spec<br>
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-4105">VIM-4105</a> Fixed <code>a"</code> <code>a'</code> <code>a`</code> text objects to include surrounding whitespace per Vim spec<br>
|
||||||
* <a href="https://youtrack.jetbrains.com/issue/VIM-4097">VIM-4097</a> Fixed <code><A-n></code> (NextOccurrence) with text containing backslashes - e.g., selecting <code>\IntegerField</code> now works correctly<br>
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-4097">VIM-4097</a> Fixed <code><A-n></code> (NextOccurrence) with text containing backslashes - e.g., selecting <code>\IntegerField</code> now works correctly<br>
|
||||||
* <a href="https://youtrack.jetbrains.com/issue/VIM-4094">VIM-4094</a> Fixed UninitializedPropertyAccessException when loading history<br>
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-4094">VIM-4094</a> Fixed UninitializedPropertyAccessException when loading history<br>
|
||||||
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-4016">VIM-4016</a> Fixed <code>:edit</code> command when project has no source roots<br>
|
||||||
* <a href="https://youtrack.jetbrains.com/issue/VIM-3948">VIM-3948</a> Improved hint generation visibility checks for better UI component detection<br>
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-3948">VIM-3948</a> Improved hint generation visibility checks for better UI component detection<br>
|
||||||
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-3473">VIM-3473</a> Fixed "Reload .ideavimrc" action in remote development (split) mode - no longer causes File Cache Conflict dialogs<br>
|
||||||
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-2821">VIM-2821</a> Fixed undo grouping when repeating text insertion with <code>.</code> in remote development (split mode)<br>
|
||||||
|
* <a href="https://youtrack.jetbrains.com/issue/VIM-1705">VIM-1705</a> Fixed window-switching commands (e.g., <code><C-w>h</code>) during macro playback<br>
|
||||||
|
* Fixed <code>pumvisible()</code> function returning incorrect result (was inverted)<br>
|
||||||
|
* Fixed <code><Esc></code> not properly exiting insert mode in Rider/CLion when canceling a completion lookup<br>
|
||||||
|
* Fixed <code><Esc></code> not exiting insert mode after <code><C-Space></code> completion in Rider<br>
|
||||||
|
* Fixed <code><Esc></code> in search bar no longer inserts <code>^[</code> literal text when search is not found - panel is now properly closed<br>
|
||||||
|
* Fixed IdeaVim entering broken state when a VimScript extension plugin fails to initialize<br>
|
||||||
|
* Fixed compatibility issues with external plugins (e.g., IdeaVim-EasyMotion, multicursor)<br>
|
||||||
|
* Fixed recursive key mappings (e.g., <code>map b wbb</code>) causing an apparent infinite loop - <code>maxmapdepth</code> limit now properly terminates the entire mapping chain<br>
|
||||||
|
* Fixed NERDTree <code>gs</code>/<code>gi</code> preview split commands to keep focus on the tree<br>
|
||||||
|
* Fixed visual marks (<code><</code> and <code>></code>) position tracking after text deletion - <code>gv</code> now re-selects correctly<br>
|
||||||
|
* Fixed <code>IndexOutOfBoundsException</code> when using text objects like <code>a)</code> at end of file<br>
|
||||||
* Fixed high CPU usage while showing command line<br>
|
* Fixed high CPU usage while showing command line<br>
|
||||||
* Fixed comparison of String and Number in VimScript expressions<br>
|
* Fixed comparison of String and Number in VimScript expressions<br>
|
||||||
<br>
|
<br>
|
||||||
<b>Merged PRs:</b><br>
|
<b>Merged PRs:</b><br>
|
||||||
|
* <a href="https://github.com/JetBrains/ideavim/pull/1632">1632</a> by <a href="https://github.com/chylex">chylex</a>: Fix pumvisible returning opposite result<br>
|
||||||
|
* <a href="https://github.com/JetBrains/ideavim/pull/1615">1615</a> by <a href="https://github.com/1grzyb1">1grzyb1</a>: Fix IndexOutOfBoundsException in findBlock when caret is at end of file<br>
|
||||||
|
* <a href="https://github.com/JetBrains/ideavim/pull/1613">1613</a> by <a href="https://github.com/1grzyb1">1grzyb1</a>: VIM-3473 Sync ideavim in remdev<br>
|
||||||
|
* <a href="https://github.com/JetBrains/ideavim/pull/1608">1608</a> by <a href="https://github.com/1grzyb1">1grzyb1</a>: VIM-4134 format using = action in split mode<br>
|
||||||
|
* <a href="https://github.com/JetBrains/ideavim/pull/1585">1585</a> by <a href="https://github.com/1grzyb1">1grzyb1</a>: Break in case of maximum recursion depth<br>
|
||||||
* <a href="https://github.com/JetBrains/ideavim/pull/1414">1414</a> by <a href="https://github.com/citizenmatt">Matt Ellis</a>: Refactor/functions<br>
|
* <a href="https://github.com/JetBrains/ideavim/pull/1414">1414</a> by <a href="https://github.com/citizenmatt">Matt Ellis</a>: Refactor/functions<br>
|
||||||
* <a href="https://github.com/JetBrains/ideavim/pull/1442">1442</a> by <a href="https://github.com/citizenmatt">Matt Ellis</a>: Fix high CPU usage while showing command line<br>
|
* <a href="https://github.com/JetBrains/ideavim/pull/1442">1442</a> by <a href="https://github.com/citizenmatt">Matt Ellis</a>: Fix high CPU usage while showing command line<br>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ ideaVersion=2026.1
|
|||||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||||
ideaType=IU
|
ideaType=IU
|
||||||
instrumentPluginCode=true
|
instrumentPluginCode=true
|
||||||
version=chylex-54
|
version=chylex-57
|
||||||
javaVersion=21
|
javaVersion=21
|
||||||
remoteRobotVersion=0.11.23
|
remoteRobotVersion=0.11.23
|
||||||
antlrVersion=4.10.1
|
antlrVersion=4.10.1
|
||||||
|
|||||||
2
gradlew
vendored
2
gradlew
vendored
@@ -57,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/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.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|||||||
@@ -165,25 +165,27 @@ internal class FileRemoteApiImpl : FileRemoteApi {
|
|||||||
// ======================== Private helpers ========================
|
// ======================== Private helpers ========================
|
||||||
|
|
||||||
private fun findFile(filename: String, project: Project): VirtualFile? {
|
private fun findFile(filename: String, project: Project): VirtualFile? {
|
||||||
var found: VirtualFile?
|
|
||||||
if (filename.startsWith("~/") || filename.startsWith("~\\")) {
|
if (filename.startsWith("~/") || filename.startsWith("~\\")) {
|
||||||
val relativePath = filename.substring(2)
|
val relativePath = filename.substring(2)
|
||||||
val dir = System.getProperty("user.home")
|
val dir = System.getProperty("user.home")
|
||||||
logger.debug { "home dir file" }
|
logger.debug { "home dir file" }
|
||||||
logger.debug { "looking for $relativePath in $dir" }
|
logger.debug { "looking for $relativePath in $dir" }
|
||||||
found = LocalFileSystem.getInstance().refreshAndFindFileByNioFile(Path(dir, relativePath))
|
return 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 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 {
|
private fun buildFileInfoMessage(editor: Editor, project: Project, fullPath: Boolean): String {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<idea-plugin>
|
<idea-plugin>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<module name="com.intellij.modules.rider"/>
|
<plugin id="com.intellij.modules.rider"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<projectListeners>
|
<projectListeners>
|
||||||
<listener class="com.maddyhome.idea.vim.listener.RiderActionListener"
|
<listener class="com.maddyhome.idea.vim.listener.RiderActionListener"
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ dependencies {
|
|||||||
testImplementation("org.junit.jupiter:junit-jupiter:6.0.3")
|
testImplementation("org.junit.jupiter:junit-jupiter:6.0.3")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|
||||||
implementation("io.ktor:ktor-client-core:3.4.1")
|
implementation("io.ktor:ktor-client-core:3.4.2")
|
||||||
implementation("io.ktor:ktor-client-cio:3.4.1")
|
implementation("io.ktor:ktor-client-cio:3.4.2")
|
||||||
implementation("io.ktor:ktor-client-content-negotiation:3.4.1")
|
implementation("io.ktor:ktor-client-content-negotiation:3.4.2")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:3.4.1")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:3.4.2")
|
||||||
implementation("io.ktor:ktor-client-auth:3.4.1")
|
implementation("io.ktor:ktor-client-auth:3.4.2")
|
||||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||||
|
|
||||||
// This is needed for jgit to connect to ssh
|
// This is needed for jgit to connect to ssh
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import com.intellij.openapi.util.Disposer;
|
|||||||
import com.maddyhome.idea.vim.api.*;
|
import com.maddyhome.idea.vim.api.*;
|
||||||
import com.maddyhome.idea.vim.config.VimState;
|
import com.maddyhome.idea.vim.config.VimState;
|
||||||
import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator;
|
import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator;
|
||||||
|
import com.maddyhome.idea.vim.group.ChangeGroup;
|
||||||
|
import com.maddyhome.idea.vim.group.KeyGroup;
|
||||||
import com.maddyhome.idea.vim.group.VimNotifications;
|
import com.maddyhome.idea.vim.group.VimNotifications;
|
||||||
import com.maddyhome.idea.vim.group.VimWindowGroup;
|
import com.maddyhome.idea.vim.group.VimWindowGroup;
|
||||||
import com.maddyhome.idea.vim.history.VimHistory;
|
import com.maddyhome.idea.vim.history.VimHistory;
|
||||||
@@ -89,8 +91,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
return VimInjectorKt.getInjector().getMotion();
|
return VimInjectorKt.getInjector().getMotion();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull VimChangeGroup getChange() {
|
public static @NotNull ChangeGroup getChange() {
|
||||||
return VimInjectorKt.getInjector().getChangeGroup();
|
return ((ChangeGroup)VimInjectorKt.getInjector().getChangeGroup());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull VimCommandGroup getCommand() {
|
public static @NotNull VimCommandGroup getCommand() {
|
||||||
@@ -130,12 +132,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
return VimInjectorKt.getInjector().getHistoryGroup();
|
return VimInjectorKt.getInjector().getHistoryGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull VimKeyGroup getKey() {
|
public static @NotNull KeyGroup getKey() {
|
||||||
return VimInjectorKt.getInjector().getKeyGroup();
|
return ((KeyGroup)VimInjectorKt.getInjector().getKeyGroup());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @Nullable VimKeyGroup getKeyIfCreated() {
|
public static @Nullable KeyGroup getKeyIfCreated() {
|
||||||
return ApplicationManager.getApplication().getServiceIfCreated(VimKeyGroup.class);
|
return ApplicationManager.getApplication().getServiceIfCreated(KeyGroup.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull VimWindowGroup getWindow() {
|
public static @NotNull VimWindowGroup getWindow() {
|
||||||
@@ -337,7 +339,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (element.getChild("shortcut-conflicts") != null) {
|
if (element.getChild("shortcut-conflicts") != null) {
|
||||||
((VimKeyGroupBase)getKey()).loadShortcutConflictsData(element);
|
getKey().loadShortcutConflictsData(element);
|
||||||
}
|
}
|
||||||
if (element.getChild("editor") != null) {
|
if (element.getChild("editor") != null) {
|
||||||
getEditor().loadEditorStateData(element);
|
getEditor().loadEditorStateData(element);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.intellij.openapi.editor.impl.EditorComponentImpl
|
|||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.progress.ProcessCanceledException
|
||||||
import com.intellij.openapi.project.DumbAware
|
import com.intellij.openapi.project.DumbAware
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.openapi.util.SystemInfoRt
|
||||||
import com.intellij.openapi.util.registry.Registry
|
import com.intellij.openapi.util.registry.Registry
|
||||||
import com.intellij.ui.KeyStrokeAdapter
|
import com.intellij.ui.KeyStrokeAdapter
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
@@ -226,8 +227,9 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
|||||||
val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
|
val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
|
||||||
val strokeCache = keyStrokeCache
|
val strokeCache = keyStrokeCache
|
||||||
if (defaultKeyStroke != null) {
|
if (defaultKeyStroke != null) {
|
||||||
keyStrokeCache = inputEvent.`when` to defaultKeyStroke
|
val fixedKeyStroke = fixKeyStroke(defaultKeyStroke)
|
||||||
return defaultKeyStroke
|
keyStrokeCache = inputEvent.`when` to fixedKeyStroke
|
||||||
|
return fixedKeyStroke
|
||||||
} else if (strokeCache.first == inputEvent.`when`) {
|
} else if (strokeCache.first == inputEvent.`when`) {
|
||||||
keyStrokeCache = null to null
|
keyStrokeCache = null to null
|
||||||
return strokeCache.second
|
return strokeCache.second
|
||||||
@@ -237,6 +239,19 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun fixKeyStroke(key: KeyStroke): KeyStroke {
|
||||||
|
return if (
|
||||||
|
key.modifiers and CTRL_ALT_MASK != 0 &&
|
||||||
|
key.isOnKeyRelease &&
|
||||||
|
SystemInfoRt.isWindows &&
|
||||||
|
Registry.`is`("actionSystem.fix.alt.gr", true)
|
||||||
|
) {
|
||||||
|
KeyStroke.getKeyStroke(key.keyCode, key.modifiers)
|
||||||
|
} else {
|
||||||
|
key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getEditor(e: AnActionEvent): Editor? {
|
private fun getEditor(e: AnActionEvent): Editor? {
|
||||||
return e.getData(PlatformDataKeys.EDITOR)
|
return e.getData(PlatformDataKeys.EDITOR)
|
||||||
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
|
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
|
||||||
@@ -317,6 +332,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
|||||||
).build()
|
).build()
|
||||||
|
|
||||||
private const val ACTION_ID = "VimShortcutKeyAction"
|
private const val ACTION_ID = "VimShortcutKeyAction"
|
||||||
|
private const val CTRL_ALT_MASK = InputEvent.CTRL_DOWN_MASK or InputEvent.ALT_DOWN_MASK
|
||||||
|
|
||||||
private val LOG = logger<VimShortcutKeyAction>()
|
private val LOG = logger<VimShortcutKeyAction>()
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.intellij.openapi.application.ApplicationManager
|
|||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.extensions.ExtensionPointListener
|
import com.intellij.openapi.extensions.ExtensionPointListener
|
||||||
import com.intellij.openapi.extensions.PluginDescriptor
|
import com.intellij.openapi.extensions.PluginDescriptor
|
||||||
|
import com.intellij.vim.api.VimInitApi
|
||||||
import com.maddyhome.idea.vim.api.VimExtensionRegistrator
|
import com.maddyhome.idea.vim.api.VimExtensionRegistrator
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.api.setToggleOption
|
import com.maddyhome.idea.vim.api.setToggleOption
|
||||||
@@ -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.OptionAccessScope
|
||||||
import com.maddyhome.idea.vim.options.OptionDeclaredScope
|
import com.maddyhome.idea.vim.options.OptionDeclaredScope
|
||||||
import com.maddyhome.idea.vim.options.ToggleOption
|
import com.maddyhome.idea.vim.options.ToggleOption
|
||||||
import com.intellij.vim.api.VimInitApi
|
|
||||||
import com.maddyhome.idea.vim.statistic.ExtensionTracking
|
import com.maddyhome.idea.vim.statistic.ExtensionTracking
|
||||||
import com.maddyhome.idea.vim.thinapi.VimApiImpl
|
import com.maddyhome.idea.vim.thinapi.VimApiImpl
|
||||||
|
|
||||||
@@ -106,9 +106,13 @@ class VimExtensionRegistrar : VimExtensionRegistrator {
|
|||||||
override fun enableDelayedExtensions() {
|
override fun enableDelayedExtensions() {
|
||||||
delayedExtensionEnabling.forEach {
|
delayedExtensionEnabling.forEach {
|
||||||
val name = it.name ?: it.instance.name
|
val name = it.name ?: it.instance.name
|
||||||
val initApi = createVimApi(name)
|
try {
|
||||||
it.instance.init(initApi)
|
val initApi = createVimApi(name)
|
||||||
logger.info("IdeaVim extension '$name' initialized")
|
it.instance.init(initApi)
|
||||||
|
logger.info("IdeaVim extension '$name' initialized")
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
logger.error("Failed to initialize IdeaVim extension '$name'", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delayedExtensionEnabling.clear()
|
delayedExtensionEnabling.clear()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ package com.maddyhome.idea.vim.extension.argtextobj
|
|||||||
|
|
||||||
import com.intellij.vim.api.VimApi
|
import com.intellij.vim.api.VimApi
|
||||||
import com.intellij.vim.api.VimInitApi
|
import com.intellij.vim.api.VimInitApi
|
||||||
|
import com.intellij.vim.api.models.CaretId
|
||||||
import com.intellij.vim.api.scopes.TextObjectRange
|
import com.intellij.vim.api.scopes.TextObjectRange
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.extension.VimExtension
|
import com.maddyhome.idea.vim.extension.VimExtension
|
||||||
@@ -121,11 +122,11 @@ class VimArgTextObjExtension : VimExtension {
|
|||||||
|
|
||||||
override fun init(initApi: VimInitApi) {
|
override fun init(initApi: VimInitApi) {
|
||||||
initApi.textObjects {
|
initApi.textObjects {
|
||||||
register("ia", preserveSelectionAnchor = false) { count ->
|
register("ia", preserveSelectionAnchor = false) { caret, count ->
|
||||||
findArgumentRange(isInner = true, count)
|
findArgumentRange(isInner = true, caret, count)
|
||||||
}
|
}
|
||||||
register("aa", preserveSelectionAnchor = false) { count ->
|
register("aa", preserveSelectionAnchor = false) { caret, count ->
|
||||||
findArgumentRange(isInner = false, count)
|
findArgumentRange(isInner = false, caret, count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,7 +611,7 @@ private object ArgTextObjUtil {
|
|||||||
/**
|
/**
|
||||||
* Find argument range using the new VimApi.
|
* Find argument range using the new VimApi.
|
||||||
*/
|
*/
|
||||||
private suspend fun VimApi.findArgumentRange(isInner: Boolean, count: Int): TextObjectRange? {
|
private suspend fun VimApi.findArgumentRange(isInner: Boolean, caret: CaretId, count: Int): TextObjectRange? {
|
||||||
var bracketPairs: BracketPairs = ArgTextObjUtil.DEFAULT_BRACKET_PAIRS
|
var bracketPairs: BracketPairs = ArgTextObjUtil.DEFAULT_BRACKET_PAIRS
|
||||||
val bracketPairsVar: String? = ArgTextObjUtil.bracketPairsVariable()
|
val bracketPairsVar: String? = ArgTextObjUtil.bracketPairsVariable()
|
||||||
if (bracketPairsVar != null) {
|
if (bracketPairsVar != null) {
|
||||||
@@ -625,7 +626,7 @@ private suspend fun VimApi.findArgumentRange(isInner: Boolean, count: Int): Text
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val (text, caretOffset) = editor { read { text to withPrimaryCaret { offset } } }
|
val (text, caretOffset) = editor { read { text to with(caret) { offset } } }
|
||||||
val finder = ArgBoundsFinder(text, this, bracketPairs)
|
val finder = ArgBoundsFinder(text, this, bracketPairs)
|
||||||
var pos = caretOffset
|
var pos = caretOffset
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ internal class JumpRemoteTopicListener : ProjectRemoteTopicListener<JumpInfo> {
|
|||||||
|
|
||||||
if (event.added) {
|
if (event.added) {
|
||||||
jumpService.addJump(projectId, jump, true)
|
jumpService.addJump(projectId, jump, true)
|
||||||
|
injector.markService.setJumpMark(event.filepath, event.protocol, event.line, event.col)
|
||||||
} else {
|
} else {
|
||||||
jumpService.removeJump(projectId, jump)
|
jumpService.removeJump(projectId, jump)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* 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.
|
* This doesn't work the same as in IJ.
|
||||||
* In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this
|
* In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this
|
||||||
* behaviour causes a lot of complaining from users. Since the rider handler gets execution control, we don't
|
* behaviour causes a lot of complaining from users. Since the rider handler gets execution control, we don't
|
||||||
* receive an event and don't exit the insert mode.
|
* receive an event and don't exit the insert mode.
|
||||||
* To fix it, this special handler exists only for rider and stands before the rider's handler. We don't execute the
|
* To fix it, this special handler exists only for rider and stands before the rider's handler. We don't execute the
|
||||||
* handler from rider because the autocompletion is closed automatically anyway.
|
* handler from rider because the autocompletion is closed automatically anyway.
|
||||||
|
*
|
||||||
|
* NOTE: This handler only works when octopus is enabled (non-Rider IDEs). For Rider, where octopus is disabled
|
||||||
|
* (VIM-3815) and Escape is consumed by the popup manager before the EditorEscape chain fires, the fix is in
|
||||||
|
* [com.maddyhome.idea.vim.listener.IdeaSpecifics.LookupTopicListener] via a LookupListener.
|
||||||
*/
|
*/
|
||||||
internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
|
internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
|
||||||
override val key: String = "<Esc>"
|
override val key: String = "<Esc>"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.maddyhome.idea.vim.newapi.vim
|
|||||||
import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual
|
import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual
|
||||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
import org.jetbrains.annotations.Contract
|
import org.jetbrains.annotations.Contract
|
||||||
|
import java.awt.Color
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.swing.Timer
|
import javax.swing.Timer
|
||||||
@@ -87,7 +88,7 @@ fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, end: Int):
|
|||||||
}
|
}
|
||||||
|
|
||||||
val removeHighlightsEditors = mutableListOf<Editor>()
|
val removeHighlightsEditors = mutableListOf<Editor>()
|
||||||
val removeHighlightsTimer = Timer(400) {
|
val removeHighlightsTimer = Timer(450) {
|
||||||
removeHighlightsEditors.forEach(::removeSearchHighlights)
|
removeHighlightsEditors.forEach(::removeSearchHighlights)
|
||||||
removeHighlightsEditors.clear()
|
removeHighlightsEditors.clear()
|
||||||
}
|
}
|
||||||
@@ -161,7 +162,7 @@ private fun updateSearchHighlights(
|
|||||||
if (editor === currentEditor?.ij) {
|
if (editor === currentEditor?.ij) {
|
||||||
currentMatchOffset = findClosestMatch(results, initialOffset, count1, forwards)
|
currentMatchOffset = findClosestMatch(results, initialOffset, count1, forwards)
|
||||||
}
|
}
|
||||||
highlightSearchResults(editor, pattern, results, currentMatchOffset)
|
highlightSearchResults(editor, results, currentMatchOffset)
|
||||||
if (!isSearching) {
|
if (!isSearching) {
|
||||||
removeHighlightsEditors.add(editor)
|
removeHighlightsEditors.add(editor)
|
||||||
removeHighlightsTimer.restart()
|
removeHighlightsTimer.restart()
|
||||||
@@ -184,7 +185,7 @@ private fun updateSearchHighlights(
|
|||||||
if (result != null) {
|
if (result != null) {
|
||||||
if (!it.inVisualMode && !it.inCommandLineModeWithVisual) {
|
if (!it.inVisualMode && !it.inCommandLineModeWithVisual) {
|
||||||
val results = listOf(result)
|
val results = listOf(result)
|
||||||
highlightSearchResults(editor, pattern, results, result.startOffset)
|
highlightSearchResults(editor, results, result.startOffset)
|
||||||
}
|
}
|
||||||
currentMatchOffset = result.startOffset
|
currentMatchOffset = result.startOffset
|
||||||
}
|
}
|
||||||
@@ -265,9 +266,18 @@ private fun findClosestMatch(
|
|||||||
return sortedResults[nextIndex % results.size].startOffset
|
return sortedResults[nextIndex % results.size].startOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UseJBColor")
|
||||||
|
private val DEFAULT_RESULT_ATTRIBUTES = TextAttributes().apply {
|
||||||
|
backgroundColor = Color(50, 81, 61)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UseJBColor")
|
||||||
|
private val NEARBY_RESULT_ATTRIBUTES = TextAttributes().apply {
|
||||||
|
backgroundColor = Color(89, 80, 50)
|
||||||
|
}
|
||||||
|
|
||||||
fun highlightSearchResults(
|
fun highlightSearchResults(
|
||||||
editor: Editor,
|
editor: Editor,
|
||||||
pattern: String,
|
|
||||||
results: List<TextRange>,
|
results: List<TextRange>,
|
||||||
currentMatchOffset: Int,
|
currentMatchOffset: Int,
|
||||||
) {
|
) {
|
||||||
@@ -276,38 +286,28 @@ fun highlightSearchResults(
|
|||||||
highlighters = mutableListOf()
|
highlighters = mutableListOf()
|
||||||
editor.vimLastHighlighters = highlighters
|
editor.vimLastHighlighters = highlighters
|
||||||
}
|
}
|
||||||
for (range in results) {
|
|
||||||
val current = range.startOffset == currentMatchOffset
|
val allCaretOffsets = editor.caretModel.allCarets.map { it.offset }
|
||||||
val highlighter = highlightMatch(editor, range.startOffset, range.endOffset, current, pattern)
|
|
||||||
highlighters.add(highlighter)
|
for ((index, range) in results.withIndex()) {
|
||||||
|
if (allCaretOffsets.any { range.startOffset == it }) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val attributes = if (allCaretOffsets.any { (index > 0 && results[index - 1].startOffset == it) || (index < results.lastIndex && results[index + 1].startOffset == it) })
|
||||||
|
NEARBY_RESULT_ATTRIBUTES
|
||||||
|
else
|
||||||
|
DEFAULT_RESULT_ATTRIBUTES
|
||||||
|
|
||||||
|
highlighters.add(highlightMatch(editor, range.startOffset, range.endOffset, attributes))
|
||||||
}
|
}
|
||||||
editor.vimIncsearchCurrentMatchOffset = currentMatchOffset
|
editor.vimIncsearchCurrentMatchOffset = currentMatchOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun highlightMatch(editor: Editor, start: Int, end: Int, current: Boolean, tooltip: String): RangeHighlighter {
|
private fun highlightMatch(editor: Editor, start: Int, end: Int, attributes: TextAttributes): RangeHighlighter {
|
||||||
val layer = HighlighterLayer.SELECTION - 1
|
val layer = HighlighterLayer.SELECTION - 1
|
||||||
val targetArea = HighlighterTargetArea.EXACT_RANGE
|
val targetArea = HighlighterTargetArea.EXACT_RANGE
|
||||||
if (!current) {
|
return editor.markupModel.addRangeHighlighter(start, end, layer, attributes, targetArea)
|
||||||
// If we use a text attribute key, it will update automatically when the editor's colour scheme changes
|
|
||||||
val highlighter =
|
|
||||||
editor.markupModel.addRangeHighlighter(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES, start, end, layer, targetArea)
|
|
||||||
highlighter.errorStripeTooltip = tooltip
|
|
||||||
return highlighter
|
|
||||||
}
|
|
||||||
|
|
||||||
// There isn't a text attribute key for current selection. This means we won't update automatically when the editor's
|
|
||||||
// colour scheme changes. However, this is only used during incsearch, so it should be replaced pretty quickly. It's a
|
|
||||||
// small visual glitch that will fix itself quickly. Let's not bother implementing an editor colour scheme listener
|
|
||||||
// just for this.
|
|
||||||
// These are the same modifications that the Find live preview does. We could look at using LivePreviewPresentation,
|
|
||||||
// which might also be useful for text attributes in selection (if we supported that)
|
|
||||||
val attributes = editor.colorsScheme.getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES).clone().apply {
|
|
||||||
effectType = EffectType.ROUNDED_BOX
|
|
||||||
effectColor = editor.colorsScheme.getColor(EditorColors.CARET_COLOR)
|
|
||||||
}
|
|
||||||
return editor.markupModel.addRangeHighlighter(start, end, layer, attributes, targetArea).apply {
|
|
||||||
errorStripeTooltip = tooltip
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,12 +29,14 @@ import com.intellij.openapi.actionSystem.AnActionEvent
|
|||||||
import com.intellij.openapi.actionSystem.AnActionResult
|
import com.intellij.openapi.actionSystem.AnActionResult
|
||||||
import com.intellij.openapi.actionSystem.AnActionWrapper
|
import com.intellij.openapi.actionSystem.AnActionWrapper
|
||||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||||
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
||||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.RangeMarker
|
import com.intellij.openapi.editor.RangeMarker
|
||||||
import com.intellij.openapi.editor.actions.EnterAction
|
import com.intellij.openapi.editor.actions.EnterAction
|
||||||
import com.intellij.openapi.editor.impl.ScrollingModelImpl
|
import com.intellij.openapi.editor.impl.ScrollingModelImpl
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
import com.intellij.openapi.keymap.KeymapManager
|
import com.intellij.openapi.keymap.KeymapManager
|
||||||
import com.intellij.openapi.project.DumbAwareToggleAction
|
import com.intellij.openapi.project.DumbAwareToggleAction
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
@@ -50,6 +52,8 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
|
|||||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||||
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
|
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
|
||||||
|
import com.maddyhome.idea.vim.ide.isClionNova
|
||||||
|
import com.maddyhome.idea.vim.ide.isRider
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.newapi.initInjector
|
import com.maddyhome.idea.vim.newapi.initInjector
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
@@ -85,6 +89,11 @@ internal object IdeaSpecifics {
|
|||||||
caretOffset = hostEditor.caretModel.offset
|
caretOffset = hostEditor.caretModel.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val actionId = ActionManager.getInstance().getId(action)
|
||||||
|
if (isGotoAction(actionId)) {
|
||||||
|
saveJumpBeforeGoto(event, editor)
|
||||||
|
}
|
||||||
|
|
||||||
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
||||||
if (!isVimAction && injector.vimState.mode == Mode.INSERT && action !is EnterAction) {
|
if (!isVimAction && injector.vimState.mode == Mode.INSERT && action !is EnterAction) {
|
||||||
val undoService = injector.undo as VimTimestampBasedUndoService
|
val undoService = injector.undo as VimTimestampBasedUndoService
|
||||||
@@ -204,6 +213,20 @@ internal object IdeaSpecifics {
|
|||||||
this.completionData = null
|
this.completionData = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isGotoAction(actionId: String?): Boolean =
|
||||||
|
actionId == IdeActions.ACTION_GOTO_BACK || actionId == IdeActions.ACTION_GOTO_FORWARD
|
||||||
|
|
||||||
|
private fun saveJumpBeforeGoto(event: AnActionEvent, editor: Editor?) {
|
||||||
|
val project = event.dataContext.getData(CommonDataKeys.PROJECT)
|
||||||
|
val currentEditor = editor
|
||||||
|
?: event.dataContext.getData(CommonDataKeys.EDITOR)
|
||||||
|
?: project?.let { VimListenerManager.VimLastSelectedEditorTracker.getLastSelectedEditor(it) }
|
||||||
|
?: project?.let { FileEditorManager.getInstance(it).selectedTextEditor }
|
||||||
|
if (currentEditor != null && !currentEditor.isIdeaVimDisabledHere) {
|
||||||
|
injector.jumpService.saveJumpLocation(currentEditor.vim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private data class CompletionData(
|
private data class CompletionData(
|
||||||
val completionStartMarker: RangeMarker,
|
val completionStartMarker: RangeMarker,
|
||||||
val originalStartOffset: Int,
|
val originalStartOffset: Int,
|
||||||
@@ -350,6 +373,16 @@ internal object IdeaSpecifics {
|
|||||||
if (newLookup.editor.isIdeaVimDisabledHere) return
|
if (newLookup.editor.isIdeaVimDisabledHere) return
|
||||||
|
|
||||||
(VimPlugin.getKey() as VimKeyGroupBase).registerShortcutsForLookup(newLookup)
|
(VimPlugin.getKey() as VimKeyGroupBase).registerShortcutsForLookup(newLookup)
|
||||||
|
|
||||||
|
// In Rider/CLion Nova, octopus is disabled (VIM-3815) and Escape is consumed by the popup manager
|
||||||
|
// (due to LookupSummaryInfo popup) before the action system runs, so IdeaVim never sees it.
|
||||||
|
// Listen for explicit lookup cancellation (Escape) to exit insert mode.
|
||||||
|
// Note: we check isRider/isClionNova specifically, not !isOctopusEnabled(), because
|
||||||
|
// JetBrains Client (split mode) also has octopus disabled but doesn't need this workaround,
|
||||||
|
// and isCanceledExplicitly can be true for non-Escape keys (e.g. space) in that environment.
|
||||||
|
if (isRider() || isClionNova()) {
|
||||||
|
newLookup.addLookupListener(RiderEscLookupListener(newLookup.editor))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup closed
|
// Lookup closed
|
||||||
@@ -361,6 +394,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
|
//endregion
|
||||||
|
|
||||||
//region Hide Vim search highlights when showing IntelliJ search results
|
//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.LogicalPosition
|
||||||
import com.intellij.openapi.editor.VisualPosition
|
import com.intellij.openapi.editor.VisualPosition
|
||||||
import com.maddyhome.idea.vim.api.BufferPosition
|
import com.maddyhome.idea.vim.api.BufferPosition
|
||||||
|
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||||
import com.maddyhome.idea.vim.api.LocalMarkStorage
|
import com.maddyhome.idea.vim.api.LocalMarkStorage
|
||||||
import com.maddyhome.idea.vim.api.SelectionInfo
|
import com.maddyhome.idea.vim.api.SelectionInfo
|
||||||
import com.maddyhome.idea.vim.api.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
@@ -198,3 +199,12 @@ class IjVimCaret(val caret: Caret) : VimCaretBase() {
|
|||||||
|
|
||||||
override fun hashCode(): Int = this.caret.hashCode()
|
override fun hashCode(): Int = this.caret.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val Caret.vim: VimCaret
|
||||||
|
get() = VimEditorFactory.getInstance().createVimCaret(this)
|
||||||
|
|
||||||
|
val VimCaret.ij: Caret
|
||||||
|
get() = VimEditorFactory.getInstance().extractCaret(this)
|
||||||
|
|
||||||
|
val ImmutableVimCaret.ij: Caret
|
||||||
|
get() = VimEditorFactory.getInstance().extractCaret(this)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,17 @@ internal class IjVimMessages : VimMessagesBase() {
|
|||||||
private var lastBeepTimeMillis = 0L
|
private var lastBeepTimeMillis = 0L
|
||||||
|
|
||||||
override fun showMessage(editor: VimEditor, message: String?) {
|
override fun showMessage(editor: VimEditor, message: String?) {
|
||||||
|
injector.outputPanel.clear(editor, injector.executionContextManager.getEditorExecutionContext(editor))
|
||||||
showMessageInternal(editor, message, MessageType.STANDARD)
|
showMessageInternal(editor, message, MessageType.STANDARD)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorMessage(editor: VimEditor, message: String?) {
|
override fun showErrorMessage(editor: VimEditor, message: String?) {
|
||||||
|
injector.outputPanel.clear(editor, injector.executionContextManager.getEditorExecutionContext(editor))
|
||||||
|
showMessageInternal(editor, message, MessageType.ERROR)
|
||||||
|
indicateError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun appendErrorMessage(editor: VimEditor, message: String?) {
|
||||||
showMessageInternal(editor, message, MessageType.ERROR)
|
showMessageInternal(editor, message, MessageType.ERROR)
|
||||||
indicateError()
|
indicateError()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Ele
|
|||||||
editor, pattern, startLine, endLine,
|
editor, pattern, startLine, endLine,
|
||||||
shouldIgnoreCase(pattern, lastIgnoreSmartCase)
|
shouldIgnoreCase(pattern, lastIgnoreSmartCase)
|
||||||
)
|
)
|
||||||
highlightSearchResults(editor.ij, pattern, results, -1)
|
highlightSearchResults(editor.ij, results, -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||||
import com.maddyhome.idea.vim.api.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
|
||||||
|
|
||||||
interface VimEditorFactory {
|
interface VimEditorFactory {
|
||||||
fun createVimEditor(editor: Editor): VimEditor
|
fun createVimEditor(editor: Editor): VimEditor
|
||||||
@@ -27,21 +26,3 @@ interface VimEditorFactory {
|
|||||||
fun getInstance(): VimEditorFactory = service()
|
fun getInstance(): VimEditorFactory = service()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val Editor.vim: VimEditor
|
|
||||||
get() = VimEditorFactory.getInstance().createVimEditor(this)
|
|
||||||
|
|
||||||
val VimEditor.ij: Editor
|
|
||||||
get() = VimEditorFactory.getInstance().extractEditor(this)
|
|
||||||
|
|
||||||
val Caret.vim: VimCaret
|
|
||||||
get() = VimEditorFactory.getInstance().createVimCaret(this)
|
|
||||||
|
|
||||||
val VimCaret.ij: Caret
|
|
||||||
get() = VimEditorFactory.getInstance().extractCaret(this)
|
|
||||||
|
|
||||||
val ImmutableVimCaret.ij: Caret
|
|
||||||
get() = VimEditorFactory.getInstance().extractCaret(this)
|
|
||||||
|
|
||||||
val com.intellij.openapi.util.TextRange.vim: TextRange
|
|
||||||
get() = TextRange(this.startOffset, this.endOffset)
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.intellij.ide.ui.LafManager
|
|||||||
import com.intellij.ide.ui.LafManagerListener
|
import com.intellij.ide.ui.LafManagerListener
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
|
||||||
import com.intellij.openapi.wm.impl.IdeBackgroundUtil
|
import com.intellij.openapi.wm.impl.IdeBackgroundUtil
|
||||||
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
|
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
|
||||||
import com.intellij.ui.ClientProperty
|
import com.intellij.ui.ClientProperty
|
||||||
@@ -18,6 +19,7 @@ import com.intellij.ui.JBColor
|
|||||||
import com.intellij.ui.components.JBPanel
|
import com.intellij.ui.components.JBPanel
|
||||||
import com.intellij.ui.components.JBScrollPane
|
import com.intellij.ui.components.JBScrollPane
|
||||||
import com.intellij.util.IJSwingUtilities
|
import com.intellij.util.IJSwingUtilities
|
||||||
|
import com.intellij.util.messages.MessageBusConnection
|
||||||
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
|
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
@@ -64,6 +66,7 @@ class OutputPanel private constructor(
|
|||||||
private var glassPane: JComponent? = null
|
private var glassPane: JComponent? = null
|
||||||
private var originalLayout: LayoutManager? = null
|
private var originalLayout: LayoutManager? = null
|
||||||
private var wasOpaque = false
|
private var wasOpaque = false
|
||||||
|
private var toolWindowListenerConnection: MessageBusConnection? = null
|
||||||
|
|
||||||
var active: Boolean = false
|
var active: Boolean = false
|
||||||
private val segments = mutableListOf<TextLine>()
|
private val segments = mutableListOf<TextLine>()
|
||||||
@@ -91,7 +94,6 @@ class OutputPanel private constructor(
|
|||||||
|
|
||||||
// Suppress the fancy frame background used in the Islands theme
|
// Suppress the fancy frame background used in the Islands theme
|
||||||
ClientProperty.putRecursive(this, IdeBackgroundUtil.NO_BACKGROUND, true)
|
ClientProperty.putRecursive(this, IdeBackgroundUtil.NO_BACKGROUND, true)
|
||||||
putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, editor.component)
|
|
||||||
|
|
||||||
// Initialize panel
|
// Initialize panel
|
||||||
setLayout(BorderLayout(0, 0))
|
setLayout(BorderLayout(0, 0))
|
||||||
@@ -101,6 +103,7 @@ class OutputPanel private constructor(
|
|||||||
val keyListener = OutputPanelKeyListener()
|
val keyListener = OutputPanelKeyListener()
|
||||||
addKeyListener(keyListener)
|
addKeyListener(keyListener)
|
||||||
textPane.addKeyListener(keyListener)
|
textPane.addKeyListener(keyListener)
|
||||||
|
editor?.let { putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, it.component) }
|
||||||
|
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
@@ -264,6 +267,8 @@ class OutputPanel private constructor(
|
|||||||
}
|
}
|
||||||
if (glassPane != null) {
|
if (glassPane != null) {
|
||||||
glassPane!!.removeComponentListener(resizeAdapter)
|
glassPane!!.removeComponentListener(resizeAdapter)
|
||||||
|
toolWindowListenerConnection?.disconnect()
|
||||||
|
toolWindowListenerConnection = null
|
||||||
glassPane!!.isVisible = false
|
glassPane!!.isVisible = false
|
||||||
glassPane!!.remove(this)
|
glassPane!!.remove(this)
|
||||||
glassPane!!.setOpaque(wasOpaque)
|
glassPane!!.setOpaque(wasOpaque)
|
||||||
@@ -285,6 +290,8 @@ class OutputPanel private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
active = true
|
active = true
|
||||||
|
if (isSingleLine) return
|
||||||
|
|
||||||
requestFocus(textPane)
|
requestFocus(textPane)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +307,11 @@ class OutputPanel private constructor(
|
|||||||
glassPane!!.setOpaque(false)
|
glassPane!!.setOpaque(false)
|
||||||
glassPane!!.add(this)
|
glassPane!!.add(this)
|
||||||
glassPane!!.addComponentListener(resizeAdapter)
|
glassPane!!.addComponentListener(resizeAdapter)
|
||||||
|
val project = editor.project
|
||||||
|
if (project != null) {
|
||||||
|
toolWindowListenerConnection = project.messageBus.connect()
|
||||||
|
toolWindowListenerConnection!!.subscribe(ToolWindowManagerListener.TOPIC, ToolWindowPositioningListener { positionPanel() })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.ui
|
||||||
|
|
||||||
|
import com.intellij.openapi.wm.ToolWindowManager
|
||||||
|
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
|
||||||
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repositions a panel whenever a tool window visibility state changes.
|
||||||
|
* Shared between [com.maddyhome.idea.vim.ui.ex.ExEntryPanel] and [OutputPanel].
|
||||||
|
*/
|
||||||
|
internal class ToolWindowPositioningListener(private val reposition: () -> Unit) : ToolWindowManagerListener {
|
||||||
|
override fun stateChanged(toolWindowManager: ToolWindowManager) {
|
||||||
|
SwingUtilities.invokeLater(reposition)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,9 +15,11 @@ import com.intellij.openapi.diagnostic.Logger
|
|||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.colors.EditorColors
|
import com.intellij.openapi.editor.colors.EditorColors
|
||||||
import com.intellij.openapi.wm.IdeFocusManager
|
import com.intellij.openapi.wm.IdeFocusManager
|
||||||
|
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
|
||||||
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
|
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
|
||||||
import com.intellij.ui.DocumentAdapter
|
import com.intellij.ui.DocumentAdapter
|
||||||
import com.intellij.util.IJSwingUtilities
|
import com.intellij.util.IJSwingUtilities
|
||||||
|
import com.intellij.util.messages.MessageBusConnection
|
||||||
import com.maddyhome.idea.vim.EventFacade
|
import com.maddyhome.idea.vim.EventFacade
|
||||||
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
|
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
@@ -39,6 +41,7 @@ import com.maddyhome.idea.vim.key.interceptors.VimInputInterceptor
|
|||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.ui.ExPanelBorder
|
import com.maddyhome.idea.vim.ui.ExPanelBorder
|
||||||
|
import com.maddyhome.idea.vim.ui.ToolWindowPositioningListener
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.Command
|
import com.maddyhome.idea.vim.vimscript.model.commands.Command
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand
|
import com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.SubstituteCommand
|
import com.maddyhome.idea.vim.vimscript.model.commands.SubstituteCommand
|
||||||
@@ -143,6 +146,11 @@ class ExEntryPanel private constructor() : JPanel(), VimCommandLine {
|
|||||||
glassPane.setOpaque(false)
|
glassPane.setOpaque(false)
|
||||||
glassPane.add(this)
|
glassPane.add(this)
|
||||||
glassPane.addComponentListener(resizePanelListener)
|
glassPane.addComponentListener(resizePanelListener)
|
||||||
|
val project = editor.project
|
||||||
|
if (project != null) {
|
||||||
|
toolWindowListenerConnection = project.messageBus.connect()
|
||||||
|
toolWindowListenerConnection!!.subscribe(ToolWindowManagerListener.TOPIC, ToolWindowPositioningListener { positionPanel() })
|
||||||
|
}
|
||||||
positionPanel()
|
positionPanel()
|
||||||
glassPane.isVisible = true
|
glassPane.isVisible = true
|
||||||
putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, parent)
|
putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, parent)
|
||||||
@@ -195,6 +203,8 @@ class ExEntryPanel private constructor() : JPanel(), VimCommandLine {
|
|||||||
|
|
||||||
putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, null)
|
putClientProperty(ToolWindowManagerImpl.PARENT_COMPONENT, null)
|
||||||
oldGlass!!.removeComponentListener(resizePanelListener)
|
oldGlass!!.removeComponentListener(resizePanelListener)
|
||||||
|
toolWindowListenerConnection?.disconnect()
|
||||||
|
toolWindowListenerConnection = null
|
||||||
oldGlass!!.isVisible = false
|
oldGlass!!.isVisible = false
|
||||||
oldGlass!!.remove(this)
|
oldGlass!!.remove(this)
|
||||||
oldGlass!!.setOpaque(wasOpaque)
|
oldGlass!!.setOpaque(wasOpaque)
|
||||||
@@ -510,6 +520,8 @@ class ExEntryPanel private constructor() : JPanel(), VimCommandLine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var toolWindowListenerConnection: MessageBusConnection? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
||||||
val layout = GridBagLayout()
|
val layout = GridBagLayout()
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ class ExTextField internal constructor(private val myParentPanel: ExEntryPanel)
|
|||||||
// handler adds all non-control characters to the text field. We want to add all characters, so if we have an
|
// handler adds all non-control characters to the text field. We want to add all characters, so if we have an
|
||||||
// actual character, just add it. Anything else, we'll pass to the super class like before (even though it's unclear
|
// actual character, just add it. Anything else, we'll pass to the super class like before (even though it's unclear
|
||||||
// what it will do with the keystroke)
|
// what it will do with the keystroke)
|
||||||
if (stroke.keyChar != KeyEvent.CHAR_UNDEFINED) {
|
if (stroke.keyChar != KeyEvent.CHAR_UNDEFINED && !isKeyCharEnterOrEscape(stroke.keyChar)) {
|
||||||
replaceSelection(stroke.keyChar.toString())
|
replaceSelection(stroke.keyChar.toString())
|
||||||
} else {
|
} else {
|
||||||
val event = KeyEvent(
|
val event = KeyEvent(
|
||||||
|
|||||||
@@ -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",
|
"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"
|
"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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -8,11 +8,16 @@
|
|||||||
|
|
||||||
package org.jetbrains.plugins.ideavim.action.motion.search
|
package org.jetbrains.plugins.ideavim.action.motion.search
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class SearchEntryFwdActionTest : VimTestCase() {
|
class SearchEntryFwdActionTest : VimTestCase() {
|
||||||
@Test
|
@Test
|
||||||
@@ -24,6 +29,14 @@ class SearchEntryFwdActionTest : VimTestCase() {
|
|||||||
assertStatusLineCleared()
|
assertStatusLineCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test search not found shows only error message on output panel`() {
|
||||||
|
configureByText("lorem ipsum dolor sit amet")
|
||||||
|
enterSearch("nonexistent")
|
||||||
|
val panelText = injector.outputPanel.getCurrentOutputPanel()?.text ?: ""
|
||||||
|
assertEquals("E486: Pattern not found: nonexistent", panelText)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `search in visual mode`() {
|
fun `search in visual mode`() {
|
||||||
doTest(
|
doTest(
|
||||||
@@ -110,6 +123,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")
|
@Disabled("Ctrl-o doesn't work yet in select mode")
|
||||||
@Test
|
@Test
|
||||||
fun `search in one time from select mode`() {
|
fun `search in one time from select mode`() {
|
||||||
|
|||||||
@@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -65,6 +65,49 @@ class SearchWholeWordForwardActionTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test repeated star search wraps around`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
aaa
|
||||||
|
abc
|
||||||
|
def
|
||||||
|
abc
|
||||||
|
dfg
|
||||||
|
abc
|
||||||
|
agg
|
||||||
|
abc
|
||||||
|
xyz
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
typeText("5j") // move to line 5, "abc"
|
||||||
|
assertPosition(5, 0)
|
||||||
|
|
||||||
|
typeText("*")
|
||||||
|
assertPosition(7, 0) // next "abc" forward
|
||||||
|
|
||||||
|
typeText("*")
|
||||||
|
assertPosition(1, 0) // wraps to first "abc"
|
||||||
|
|
||||||
|
typeText("*")
|
||||||
|
assertPosition(3, 0)
|
||||||
|
|
||||||
|
typeText("*")
|
||||||
|
assertPosition(5, 0)
|
||||||
|
|
||||||
|
typeText("*")
|
||||||
|
assertPosition(7, 0)
|
||||||
|
|
||||||
|
typeText("*")
|
||||||
|
assertPosition(1, 0) // wraps again
|
||||||
|
|
||||||
|
typeText("*")
|
||||||
|
assertPosition(3, 0)
|
||||||
|
|
||||||
|
typeText("*")
|
||||||
|
assertPosition(5, 0)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test last word`() {
|
fun `test last word`() {
|
||||||
doTest(
|
doTest(
|
||||||
|
|||||||
@@ -169,4 +169,31 @@ class AddressTest : VimTestCase() {
|
|||||||
typeText(commandToKeys("/bar//foo/d"))
|
typeText(commandToKeys("/bar//foo/d"))
|
||||||
assertState("a\nfoo\nbar\nbar\nbaz\n")
|
assertState("a\nfoo\nbar\nbar\nbaz\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test backslash-slash range without previous search reports E35`() {
|
||||||
|
// Before this fix, using \/ with no previous search caused a NullPointerException instead of E35
|
||||||
|
configureByText("1\n2\n3\n")
|
||||||
|
typeText(commandToKeys("\\/d"))
|
||||||
|
assertPluginError(true)
|
||||||
|
assertPluginErrorMessage("E35: No previous regular expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test backslash-question range without previous search reports E35`() {
|
||||||
|
// Before this fix, using \? with no previous search caused a NullPointerException instead of E35
|
||||||
|
configureByText("1\n2\n3\n")
|
||||||
|
typeText(commandToKeys("\\?d"))
|
||||||
|
assertPluginError(true)
|
||||||
|
assertPluginErrorMessage("E35: No previous regular expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test backslash-ampersand range without previous substitute reports E33`() {
|
||||||
|
// Before this fix, using \& with no previous substitute caused a NullPointerException instead of E33
|
||||||
|
configureByText("1\n2\n3\n")
|
||||||
|
typeText(commandToKeys("\\&d"))
|
||||||
|
assertPluginError(true)
|
||||||
|
assertPluginErrorMessage("E33: No previous substitute regular expression")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,17 +224,6 @@ class HistoryCommandTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test history cmd lists empty command history`() {
|
|
||||||
assertCommandOutput(
|
|
||||||
"history cmd",
|
|
||||||
"""
|
|
||||||
| # cmd history
|
|
||||||
|> 1 history cmd
|
|
||||||
""".trimMargin()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test history cmd lists current cmd in history`() {
|
fun `test history cmd lists current cmd in history`() {
|
||||||
assertCommandOutput(
|
assertCommandOutput(
|
||||||
@@ -499,7 +488,7 @@ class HistoryCommandTest : VimTestCase() {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test history search with first number lists single entry from saerch history`() {
|
fun `test history search with first number lists single entry from search history`() {
|
||||||
repeat(10) { i -> enterSearch("foo${i + 1}") }
|
repeat(10) { i -> enterSearch("foo${i + 1}") }
|
||||||
injector.outputPanel.getCurrentOutputPanel()?.clearText()
|
injector.outputPanel.getCurrentOutputPanel()?.clearText()
|
||||||
assertCommandOutput(
|
assertCommandOutput(
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ dependencies {
|
|||||||
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
||||||
testImplementation(testFixtures(project(":"))) // The root project
|
testImplementation(testFixtures(project(":"))) // The root project
|
||||||
testImplementation("org.junit.vintage:junit-vintage-engine:6.0.3")
|
testImplementation("org.junit.vintage:junit-vintage-engine:6.0.3")
|
||||||
|
testImplementation("org.jetbrains:jetCheck:0.2.3")
|
||||||
|
|
||||||
intellijPlatform {
|
intellijPlatform {
|
||||||
// Snapshots don't use installers
|
// Snapshots don't use installers
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class FileOpsSplitTest : IdeaVimStarterTestBase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `save file with write command`() {
|
fun `save file with write command`() {
|
||||||
openFile(createFile("src/Save1.txt", "Line 1\nLine 2\nLine 3\n"))
|
openFile(createFile("src/Save1.txt", "Line 1\nLine 2\nLine 3\n"))
|
||||||
clickEditor()
|
|
||||||
typeVimAndEscape("0iSAVED ")
|
typeVimAndEscape("0iSAVED ")
|
||||||
pause()
|
pause()
|
||||||
exCommand("w")
|
exCommand("w")
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import com.intellij.ide.starter.plugins.PluginConfigurator
|
|||||||
import com.intellij.ide.starter.project.LocalProjectInfo
|
import com.intellij.ide.starter.project.LocalProjectInfo
|
||||||
import com.intellij.ide.starter.runner.Starter
|
import com.intellij.ide.starter.runner.Starter
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
@@ -125,9 +124,45 @@ abstract class IdeaVimStarterTestBase {
|
|||||||
|
|
||||||
// ── IDE interaction helpers ─────────────────────────────────
|
// ── IDE interaction helpers ─────────────────────────────────
|
||||||
|
|
||||||
/** Opens a file in the editor by relative path. */
|
/** Opens a file in the editor by relative path and waits until the editor is ready for input. */
|
||||||
protected fun openFile(relativePath: String) {
|
protected fun openFile(relativePath: String) {
|
||||||
driver.withContext { openFile(relativePath) }
|
driver.withContext { openFile(relativePath) }
|
||||||
|
ensureEditorReady(relativePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureEditorReady(relativePath: String, timeoutMs: Long = 10_000) {
|
||||||
|
val deadline = System.currentTimeMillis() + timeoutMs
|
||||||
|
var lastText = ""
|
||||||
|
while (System.currentTimeMillis() < deadline) {
|
||||||
|
try {
|
||||||
|
lastText = editorText()
|
||||||
|
if (lastText.isNotBlank()) break
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// Editor component not yet available — retry
|
||||||
|
}
|
||||||
|
Thread.sleep(200)
|
||||||
|
}
|
||||||
|
check(lastText.isNotBlank()) {
|
||||||
|
"Editor for '$relativePath' did not become ready within ${timeoutMs}ms"
|
||||||
|
}
|
||||||
|
clickEditor()
|
||||||
|
|
||||||
|
// Verify IdeaVim's key handler is actually attached by sending `gg` (go to first line)
|
||||||
|
// and checking the caret moves to line 1. This confirms vim is processing keystrokes.
|
||||||
|
val vimReady = waitUntil(timeoutMs = 10_000, pollMs = 500) {
|
||||||
|
try {
|
||||||
|
driver.withContext {
|
||||||
|
ideFrame { codeEditor().apply { waitFound(); keyboard { typeText("gg") } } }
|
||||||
|
}
|
||||||
|
Thread.sleep(300)
|
||||||
|
caretLine() <= 1
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(vimReady) {
|
||||||
|
"IdeaVim key handler did not attach for '$relativePath' within timeout"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Types vim keys in the active editor. */
|
/** Types vim keys in the active editor. */
|
||||||
@@ -177,6 +212,27 @@ abstract class IdeaVimStarterTestBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Triggers the IDE "Navigate > Back" action (Cmd+[ on macOS, Ctrl+Alt+Left on Linux). */
|
||||||
|
protected fun ideaGoBack() {
|
||||||
|
driver.withContext {
|
||||||
|
ideFrame {
|
||||||
|
codeEditor().apply {
|
||||||
|
if (System.getProperty("os.name").lowercase().contains("mac")) {
|
||||||
|
keyboard { hotKey(java.awt.event.KeyEvent.VK_META, java.awt.event.KeyEvent.VK_OPEN_BRACKET) }
|
||||||
|
} else {
|
||||||
|
keyboard {
|
||||||
|
hotKey(
|
||||||
|
java.awt.event.KeyEvent.VK_CONTROL,
|
||||||
|
java.awt.event.KeyEvent.VK_ALT,
|
||||||
|
java.awt.event.KeyEvent.VK_LEFT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Presses Ctrl-G (file info). */
|
/** Presses Ctrl-G (file info). */
|
||||||
protected fun ctrlG() {
|
protected fun ctrlG() {
|
||||||
driver.withContext {
|
driver.withContext {
|
||||||
@@ -207,6 +263,23 @@ abstract class IdeaVimStarterTestBase {
|
|||||||
Thread.sleep(ms)
|
Thread.sleep(ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries [check] every [pollMs] until it returns true or [timeoutMs] elapses.
|
||||||
|
* Use instead of fixed pauses to handle variable split-mode RPC latency on CI.
|
||||||
|
*/
|
||||||
|
protected fun waitUntil(
|
||||||
|
timeoutMs: Long = 5000,
|
||||||
|
pollMs: Long = 200,
|
||||||
|
check: () -> Boolean,
|
||||||
|
): Boolean {
|
||||||
|
val deadline = System.currentTimeMillis() + timeoutMs
|
||||||
|
while (System.currentTimeMillis() < deadline) {
|
||||||
|
if (check()) return true
|
||||||
|
Thread.sleep(pollMs)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ── Editor state helpers ────────────────────────────────────
|
// ── Editor state helpers ────────────────────────────────────
|
||||||
|
|
||||||
/** Reads the current editor text. */
|
/** Reads the current editor text. */
|
||||||
@@ -229,42 +302,48 @@ abstract class IdeaVimStarterTestBase {
|
|||||||
|
|
||||||
// ── Assertions ──────────────────────────────────────────────
|
// ── Assertions ──────────────────────────────────────────────
|
||||||
|
|
||||||
/** Asserts the editor contains the given text. */
|
/** Asserts the editor contains the given text, polling until timeout. */
|
||||||
protected fun assertEditorContains(expected: String, message: String? = null) {
|
protected fun assertEditorContains(expected: String, message: String? = null) {
|
||||||
val text = editorText()
|
var text = ""
|
||||||
assertTrue(text.contains(expected)) {
|
val found = waitUntil { text = editorText(); text.contains(expected) }
|
||||||
|
assertTrue(found) {
|
||||||
(message ?: "Editor should contain '$expected'") + ". Actual: $text"
|
(message ?: "Editor should contain '$expected'") + ". Actual: $text"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Asserts the editor does NOT contain the given text. */
|
/** Asserts the editor does NOT contain the given text, polling until timeout. */
|
||||||
protected fun assertEditorNotContains(unexpected: String, message: String? = null) {
|
protected fun assertEditorNotContains(unexpected: String, message: String? = null) {
|
||||||
|
// Give operations time to settle, then check
|
||||||
|
pause(1000)
|
||||||
val text = editorText()
|
val text = editorText()
|
||||||
assertFalse(text.contains(unexpected)) {
|
assertFalse(text.contains(unexpected)) {
|
||||||
(message ?: "Editor should not contain '$unexpected'") + ". Actual: $text"
|
(message ?: "Editor should not contain '$unexpected'") + ". Actual: $text"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Asserts the caret is at the given line (1-based). */
|
/** Asserts the caret is at the given line (1-based), polling until timeout. */
|
||||||
protected fun assertCaretAtLine(expected: Int, message: String? = null) {
|
protected fun assertCaretAtLine(expected: Int, message: String? = null) {
|
||||||
val actual = caretLine()
|
var actual = 0
|
||||||
assertEquals(expected, actual) {
|
val found = waitUntil { actual = caretLine(); actual == expected }
|
||||||
|
assertTrue(found) {
|
||||||
(message ?: "Caret should be at line $expected") + ". Actual line: $actual"
|
(message ?: "Caret should be at line $expected") + ". Actual line: $actual"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Asserts the caret is before the given line. */
|
/** Asserts the caret is before the given line, polling until timeout. */
|
||||||
protected fun assertCaretBefore(line: Int, message: String? = null) {
|
protected fun assertCaretBefore(line: Int, message: String? = null) {
|
||||||
val actual = caretLine()
|
var actual = 0
|
||||||
assertTrue(actual < line) {
|
val found = waitUntil { actual = caretLine(); actual < line }
|
||||||
|
assertTrue(found) {
|
||||||
(message ?: "Caret should be before line $line") + ". Actual line: $actual"
|
(message ?: "Caret should be before line $line") + ". Actual line: $actual"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Asserts the caret is past the given line. */
|
/** Asserts the caret is past the given line, polling until timeout. */
|
||||||
protected fun assertCaretAfter(line: Int, message: String? = null) {
|
protected fun assertCaretAfter(line: Int, message: String? = null) {
|
||||||
val actual = caretLine()
|
var actual = 0
|
||||||
assertTrue(actual > line) {
|
val found = waitUntil { actual = caretLine(); actual > line }
|
||||||
|
assertTrue(found) {
|
||||||
(message ?: "Caret should be after line $line") + ". Actual line: $actual"
|
(message ?: "Caret should be after line $line") + ". Actual line: $actual"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ class JumpNavigationSplitTest : IdeaVimStarterTestBase() {
|
|||||||
openFile(longFile("Jump1"))
|
openFile(longFile("Jump1"))
|
||||||
|
|
||||||
typeVim("G")
|
typeVim("G")
|
||||||
pause(300)
|
pause(500)
|
||||||
assertCaretAfter(40, "G should go to end of file")
|
assertCaretAfter(40, "G should go to end of file")
|
||||||
|
|
||||||
ctrlO()
|
ctrlO()
|
||||||
pause(300)
|
pause(500)
|
||||||
assertCaretBefore(10, "Ctrl-O should jump back to start")
|
assertCaretBefore(10, "Ctrl-O should jump back to start")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class JumpNavigationSplitTest : IdeaVimStarterTestBase() {
|
|||||||
openFile(longFile("Jump2"))
|
openFile(longFile("Jump2"))
|
||||||
|
|
||||||
typeVim("gg")
|
typeVim("gg")
|
||||||
pause(300)
|
pause(500)
|
||||||
|
|
||||||
typeVim("/Line 30\n")
|
typeVim("/Line 30\n")
|
||||||
pause()
|
pause()
|
||||||
@@ -46,4 +46,21 @@ class JumpNavigationSplitTest : IdeaVimStarterTestBase() {
|
|||||||
pause()
|
pause()
|
||||||
assertCaretBefore(10, "Ctrl-O should jump back near start after search")
|
assertCaretBefore(10, "Ctrl-O should jump back near start after search")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `IDE Back navigation records jump so apostrophe mark navigates back`() {
|
||||||
|
openFile(longFile("Jump3"))
|
||||||
|
|
||||||
|
typeVim("G")
|
||||||
|
pause(300)
|
||||||
|
assertCaretAfter(40, "G should move to end of file")
|
||||||
|
|
||||||
|
ideaGoBack()
|
||||||
|
pause(500)
|
||||||
|
assertCaretBefore(10, "IDE Back should return to start of file")
|
||||||
|
|
||||||
|
typeVim("''")
|
||||||
|
pause(300)
|
||||||
|
assertCaretAfter(40, "'' should return to position before IDE Back (end of file)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ class RepeatUndoSplitTest : IdeaVimStarterTestBase() {
|
|||||||
typeVimAndEscape("0sX")
|
typeVimAndEscape("0sX")
|
||||||
assertEditorContains("Xbcdef", "First char should be X")
|
assertEditorContains("Xbcdef", "First char should be X")
|
||||||
|
|
||||||
typeVim("l.")
|
typeVim("l")
|
||||||
|
pause()
|
||||||
|
typeVim(".")
|
||||||
assertEditorContains("XXcdef", "Second char should also be X")
|
assertEditorContains("XXcdef", "Second char should also be X")
|
||||||
|
|
||||||
typeVim("uu")
|
typeVim("u")
|
||||||
|
pause()
|
||||||
|
typeVim("u")
|
||||||
assertEditorContains("Xbcdef", "First undo should revert dot-repeat only")
|
assertEditorContains("Xbcdef", "First undo should revert dot-repeat only")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +42,9 @@ class RepeatUndoSplitTest : IdeaVimStarterTestBase() {
|
|||||||
assertEditorContains("HiHi", "Dot repeat should insert again")
|
assertEditorContains("HiHi", "Dot repeat should insert again")
|
||||||
|
|
||||||
typeVim("u")
|
typeVim("u")
|
||||||
val text = editorText()
|
var text = ""
|
||||||
assertTrue(text.contains("Hi ") && !text.contains("HiHi")) {
|
val found = waitUntil { text = editorText(); text.contains("Hi ") && !text.contains("HiHi") }
|
||||||
|
assertTrue(found) {
|
||||||
"Undo should revert dot repeat only. Actual: $text"
|
"Undo should revert dot repeat only. Actual: $text"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,8 +56,11 @@ class RepeatUndoSplitTest : IdeaVimStarterTestBase() {
|
|||||||
typeVimAndEscape("0cwHELLO")
|
typeVimAndEscape("0cwHELLO")
|
||||||
assertEditorContains("HELLO", "Should have changed word")
|
assertEditorContains("HELLO", "Should have changed word")
|
||||||
|
|
||||||
typeVim("w.")
|
typeVim("w")
|
||||||
val helloCount = editorText().lines().first().split("HELLO").size - 1
|
pause()
|
||||||
|
typeVim(".")
|
||||||
|
var helloCount = 0
|
||||||
|
waitUntil { helloCount = editorText().lines().first().split("HELLO").size - 1; helloCount >= 2 }
|
||||||
assertTrue(helloCount >= 2) { "Should have two HELLOs. Actual: ${editorText()}" }
|
assertTrue(helloCount >= 2) { "Should have two HELLOs. Actual: ${editorText()}" }
|
||||||
|
|
||||||
typeVim("u")
|
typeVim("u")
|
||||||
|
|||||||
@@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -13,14 +13,22 @@ import com.intellij.vim.annotations.Mode
|
|||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||||
|
import com.maddyhome.idea.vim.helper.isCommandLineActionChar
|
||||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<C-K>"], modes = [Mode.INSERT, Mode.CMD_LINE])
|
/**
|
||||||
|
* Insert mode: insert a digraph character via `<C-K>`
|
||||||
|
*
|
||||||
|
* The converted digraph character is re-injected through the key handler so that it is processed as typed input in
|
||||||
|
* Insert mode (handled by the change group).
|
||||||
|
*/
|
||||||
|
@CommandOrMotion(keys = ["<C-K>"], modes = [Mode.INSERT])
|
||||||
class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
|
class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.INSERT
|
override val type: Command.Type = Command.Type.INSERT
|
||||||
|
|
||||||
@@ -29,22 +37,6 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
|
|||||||
// We're waiting for it to complete and give us a CHARACTER
|
// We're waiting for it to complete and give us a CHARACTER
|
||||||
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform additional initialisation when starting to wait for an argument
|
|
||||||
*
|
|
||||||
* IdeaVim has two ways of handling digraphs/literals. Actions such as `r` or `f` can accept a digraph, which really
|
|
||||||
* means it accepts a character, but the user can use `<C-K>`/`<C-V>` to type a digraph or literal and convert it into
|
|
||||||
* a character. Unfortunately, there is no mode that can be used to register an "insert digraph/literal" action for
|
|
||||||
* these keys while replace or find is active. So the key handler hard codes these keys and will check for them when
|
|
||||||
* an action expects a digraph (and like Vim, these keys cannot be mapped). Once the state machine has matched a
|
|
||||||
* character, the expected argument is reset to [Argument.Type.CHARACTER] and the character is passed through the key
|
|
||||||
* handler again, potentially mapped, and then attached as an argument to the current command, which is now complete
|
|
||||||
* and executed.
|
|
||||||
*
|
|
||||||
* In Insert and Command-line mode, the `<C-K>` and `<C-V>` keys are actions that will wait for a character argument,
|
|
||||||
* and then insert it. Commands are only executed once complete, so we use [onStartWaitingForArgument] to start the
|
|
||||||
* digraph state machine. This also gives us a repeatable command and captures the keys for `'showcmd'`.
|
|
||||||
*/
|
|
||||||
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||||
val result = keyState.digraphSequence.startDigraphSequence()
|
val result = keyState.digraphSequence.startDigraphSequence()
|
||||||
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
|
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
|
||||||
@@ -56,7 +48,6 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
|
|||||||
cmd: Command,
|
cmd: Command,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// The converted digraph character has been captured as an argument, push it back through key handler
|
|
||||||
val argument = cmd.argument as? Argument.Character ?: return false
|
val argument = cmd.argument as? Argument.Character ?: return false
|
||||||
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
||||||
val keyHandler = KeyHandler.getInstance()
|
val keyHandler = KeyHandler.getInstance()
|
||||||
@@ -64,3 +55,37 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command-line mode: insert a digraph character via `<C-K>`
|
||||||
|
*
|
||||||
|
* Control characters like Escape or Enter are inserted directly into the command line to avoid being matched as
|
||||||
|
* commands. Other characters use [VimCommandLine.handleKey] so that overwrite mode is handled correctly.
|
||||||
|
*/
|
||||||
|
@CommandOrMotion(keys = ["<C-K>"], modes = [Mode.CMD_LINE])
|
||||||
|
class CmdLineCompletedDigraphAction : VimActionHandler.SingleExecution() {
|
||||||
|
override val type: Command.Type = Command.Type.INSERT
|
||||||
|
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
||||||
|
|
||||||
|
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||||
|
val result = keyState.digraphSequence.startDigraphSequence()
|
||||||
|
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun execute(
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
cmd: Command,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
|
): Boolean {
|
||||||
|
val argument = cmd.argument as? Argument.Character ?: return false
|
||||||
|
val commandLine = injector.commandLine.getActiveCommandLine() ?: return false
|
||||||
|
val ch = argument.character
|
||||||
|
if (ch.isCommandLineActionChar()) {
|
||||||
|
commandLine.insertText(commandLine.caret.offset, ch.toString())
|
||||||
|
} else {
|
||||||
|
commandLine.handleKey(KeyStroke.getKeyStroke(ch))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -13,14 +13,22 @@ import com.intellij.vim.annotations.Mode
|
|||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||||
|
import com.maddyhome.idea.vim.helper.isCommandLineActionChar
|
||||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<C-V>", "<C-Q>"], modes = [Mode.INSERT, Mode.CMD_LINE])
|
/**
|
||||||
|
* Insert mode: insert a literal character via `<C-V>` / `<C-Q>`
|
||||||
|
*
|
||||||
|
* The converted literal character is re-injected through the key handler so that it is processed as typed input in
|
||||||
|
* Insert mode (handled by the change group).
|
||||||
|
*/
|
||||||
|
@CommandOrMotion(keys = ["<C-V>", "<C-Q>"], modes = [Mode.INSERT])
|
||||||
class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.INSERT
|
override val type: Command.Type = Command.Type.INSERT
|
||||||
|
|
||||||
@@ -29,22 +37,6 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
|||||||
// We're waiting for it to complete and give us a CHARACTER
|
// We're waiting for it to complete and give us a CHARACTER
|
||||||
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform additional initialisation when starting to wait for an argument
|
|
||||||
*
|
|
||||||
* IdeaVim has two ways of handling digraphs/literals. Actions such as `r` or `f` can accept a digraph, which really
|
|
||||||
* means it accepts a character, but the user can use `<C-K>`/`<C-V>` to type a digraph or literal and convert it into
|
|
||||||
* a character. Unfortunately, there is no mode that can be used to register an "insert digraph/literal" action for
|
|
||||||
* these keys while replace or find is active. So the key handler hard codes these keys and will check for them when
|
|
||||||
* an action expects a digraph (and like Vim, these keys cannot be mapped). Once the state machine has matched a
|
|
||||||
* character, the expected argument is reset to [Argument.Type.CHARACTER] and the character is passed through the key
|
|
||||||
* handler again, potentially mapped, and then attached as an argument to the current command, which is now complete
|
|
||||||
* and executed.
|
|
||||||
*
|
|
||||||
* In Insert and Command-line mode, the `<C-K>` and `<C-V>` keys are actions that will wait for a character argument,
|
|
||||||
* and then insert it. Commands are only executed once complete, so we use [onStartWaitingForArgument] to start the
|
|
||||||
* digraph state machine. This also gives us a repeatable command and captures the keys for `'showcmd'`.
|
|
||||||
*/
|
|
||||||
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||||
val result = keyState.digraphSequence.startLiteralSequence()
|
val result = keyState.digraphSequence.startLiteralSequence()
|
||||||
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
|
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
|
||||||
@@ -56,7 +48,6 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
|||||||
cmd: Command,
|
cmd: Command,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// The converted literal character has been captured as an argument, push it back through key handler
|
|
||||||
val argument = cmd.argument as? Argument.Character ?: return false
|
val argument = cmd.argument as? Argument.Character ?: return false
|
||||||
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
||||||
val keyHandler = KeyHandler.getInstance()
|
val keyHandler = KeyHandler.getInstance()
|
||||||
@@ -64,3 +55,39 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command-line mode: insert a literal character via `<C-V>` / `<C-Q>`
|
||||||
|
*
|
||||||
|
* Control characters like Escape or Enter are inserted directly into the command line to avoid being matched as
|
||||||
|
* commands (e.g., LeaveCommandLineAction). Other characters use [VimCommandLine.handleKey] so that overwrite mode
|
||||||
|
* is handled correctly.
|
||||||
|
*/
|
||||||
|
@CommandOrMotion(keys = ["<C-V>", "<C-Q>"], modes = [Mode.CMD_LINE])
|
||||||
|
class CmdLineCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
||||||
|
override val type: Command.Type = Command.Type.INSERT
|
||||||
|
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
||||||
|
|
||||||
|
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||||
|
val result = keyState.digraphSequence.startLiteralSequence()
|
||||||
|
KeyHandler.getInstance().setPromptCharacterEx(result.promptCharacter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun execute(
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
cmd: Command,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
|
): Boolean {
|
||||||
|
val argument = cmd.argument as? Argument.Character ?: return false
|
||||||
|
val commandLine = injector.commandLine.getActiveCommandLine() ?: return false
|
||||||
|
val ch = argument.character
|
||||||
|
if (ch.isCommandLineActionChar()) {
|
||||||
|
// Insert directly to avoid these being matched as commands by the key handler
|
||||||
|
commandLine.insertText(commandLine.caret.offset, ch.toString())
|
||||||
|
} else {
|
||||||
|
commandLine.handleKey(KeyStroke.getKeyStroke(ch))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,14 +15,12 @@ import com.maddyhome.idea.vim.helper.endOffsetInclusive
|
|||||||
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
|
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
|
||||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||||
import java.lang.Long.toHexString
|
|
||||||
|
|
||||||
abstract class VimFileBase : VimFile {
|
abstract class VimFileBase : VimFile {
|
||||||
override fun displayHexInfo(editor: VimEditor) {
|
override fun displayHexInfo(editor: VimEditor) {
|
||||||
val offset = editor.currentCaret().offset
|
val offset = editor.currentCaret().offset
|
||||||
val ch = editor.text()[offset]
|
val ch = editor.text()[offset]
|
||||||
|
|
||||||
injector.messages.showMessage(editor, toHexString(ch.code.toLong()))
|
injector.messages.showMessage(editor, ch.code.toString(16))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun displayLocationInfo(editor: VimEditor) {
|
override fun displayLocationInfo(editor: VimEditor) {
|
||||||
|
|||||||
@@ -150,6 +150,13 @@ interface VimMarkService {
|
|||||||
*/
|
*/
|
||||||
fun loadLegacyState(element: Any) {}
|
fun loadLegacyState(element: Any) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the BEFORE_JUMP_MARK directly from a position, without requiring an open editor or caret.
|
||||||
|
* Used by jump listeners that receive position data from the IDE navigation history.
|
||||||
|
* Default no-op; implementations that maintain local mark state should override this.
|
||||||
|
*/
|
||||||
|
fun setJumpMark(filepath: String, protocol: String, line: Int, col: Int) {}
|
||||||
|
|
||||||
fun isValidMark(char: Char, operation: Operation, isCaretPrimary: Boolean): Boolean
|
fun isValidMark(char: Char, operation: Operation, isCaretPrimary: Boolean): Boolean
|
||||||
|
|
||||||
enum class Operation {
|
enum class Operation {
|
||||||
|
|||||||
@@ -248,6 +248,10 @@ abstract class VimMarkServiceBase : VimMarkService {
|
|||||||
return setMark(caret, mark)
|
return setMark(caret, mark)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setJumpMark(filepath: String, protocol: String, line: Int, col: Int) {
|
||||||
|
getLocalMarks(filepath)[BEFORE_JUMP_MARK] = VimMark(BEFORE_JUMP_MARK, line, col, filepath, protocol)
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun createGlobalMark(editor: VimEditor, char: Char, offset: Int): Mark? {
|
protected open fun createGlobalMark(editor: VimEditor, char: Char, offset: Int): Mark? {
|
||||||
val markChar = char.normalizeMarkChar()
|
val markChar = char.normalizeMarkChar()
|
||||||
if (!markChar.isGlobalMark()) return null
|
if (!markChar.isGlobalMark()) return null
|
||||||
|
|||||||
@@ -20,10 +20,18 @@ interface VimMessages {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays an error message to the user (typically in red).
|
* Displays an error message to the user (typically in red).
|
||||||
|
* Clears any existing output panel content before showing the message.
|
||||||
* The message panel closes on any keystroke and passes the key through to the editor.
|
* The message panel closes on any keystroke and passes the key through to the editor.
|
||||||
*/
|
*/
|
||||||
fun showErrorMessage(editor: VimEditor, message: String?)
|
fun showErrorMessage(editor: VimEditor, message: String?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an error message to the existing output panel content (typically in red).
|
||||||
|
* Unlike [showErrorMessage], this does not clear prior output.
|
||||||
|
* Use this when reporting errors during script execution where earlier output should be preserved.
|
||||||
|
*/
|
||||||
|
fun appendErrorMessage(editor: VimEditor, message: String?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy method for displaying messages.
|
* Legacy method for displaying messages.
|
||||||
* @deprecated Use [showMessage] or [showErrorMessage] instead.
|
* @deprecated Use [showMessage] or [showErrorMessage] instead.
|
||||||
|
|||||||
@@ -34,4 +34,9 @@ interface VimOutputPanelService {
|
|||||||
text: String,
|
text: String,
|
||||||
messageType: MessageType = MessageType.STANDARD,
|
messageType: MessageType = MessageType.STANDARD,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun clear(
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -18,4 +18,12 @@ abstract class VimOutputPanelServiceBase : VimOutputPanelService {
|
|||||||
panel.addText(text, true, messageType)
|
panel.addText(text, true, messageType)
|
||||||
panel.show()
|
panel.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clear(
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
) {
|
||||||
|
val panel = getOrCreate(editor, context)
|
||||||
|
panel.clearText()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -65,13 +65,13 @@ abstract class VimScriptExecutorBase : VimscriptExecutor {
|
|||||||
}
|
}
|
||||||
finalResult = ExecutionResult.Error
|
finalResult = ExecutionResult.Error
|
||||||
if (indicateErrors) {
|
if (indicateErrors) {
|
||||||
injector.messages.showErrorMessage(editor, e.message)
|
injector.messages.appendErrorMessage(editor, e.message)
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Failed while executing $unit. " + e.message)
|
logger.warn("Failed while executing $unit. " + e.message)
|
||||||
}
|
}
|
||||||
} catch (e: NotImplementedError) {
|
} catch (e: NotImplementedError) {
|
||||||
if (indicateErrors) {
|
if (indicateErrors) {
|
||||||
injector.messages.showErrorMessage(editor, "Not implemented yet :(")
|
injector.messages.appendErrorMessage(editor, "Not implemented yet :(")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.warn(e.toString())
|
logger.warn(e.toString())
|
||||||
|
|||||||
@@ -653,7 +653,9 @@ abstract class VimSearchGroupBase : VimSearchGroup {
|
|||||||
caret.moveToOffset(matchRange.startOffset)
|
caret.moveToOffset(matchRange.startOffset)
|
||||||
val highlight = addSubstitutionConfirmationHighlight(editor, matchRange.startOffset, matchRange.endOffset)
|
val highlight = addSubstitutionConfirmationHighlight(editor, matchRange.startOffset, matchRange.endOffset)
|
||||||
injector.modalInput.create(
|
injector.modalInput.create(
|
||||||
editor, context, injector.messages.message("command.substitute.replace.with.prompt", lineToNextSubstitute.second.second),
|
editor,
|
||||||
|
context,
|
||||||
|
injector.messages.message("command.substitute.replace.with.prompt", lineToNextSubstitute.second.second),
|
||||||
SubstituteWithAskInputInterceptor(
|
SubstituteWithAskInputInterceptor(
|
||||||
editor, caret, nextSubstitute, highlight, line, 0, parent, pattern, regex,
|
editor, caret, nextSubstitute, highlight, line, 0, parent, pattern, regex,
|
||||||
oldLastSubstituteString, line2, hasExpression, substituteString, options,
|
oldLastSubstituteString, line2, hasExpression, substituteString, options,
|
||||||
@@ -854,7 +856,7 @@ abstract class VimSearchGroupBase : VimSearchGroup {
|
|||||||
if (lastMatchLine != -1) {
|
if (lastMatchLine != -1) {
|
||||||
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, lastMatchLine))
|
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, lastMatchLine))
|
||||||
} else {
|
} else {
|
||||||
injector.messages.showStatusBarMessage(null, "E486: Pattern not found: $pattern")
|
injector.messages.showErrorMessage(editor, "E486: Pattern not found: $pattern")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -863,7 +865,7 @@ abstract class VimSearchGroupBase : VimSearchGroup {
|
|||||||
// todo throw multiple exceptions at once
|
// todo throw multiple exceptions at once
|
||||||
if (exceptions.isNotEmpty()) {
|
if (exceptions.isNotEmpty()) {
|
||||||
injector.messages.indicateError()
|
injector.messages.indicateError()
|
||||||
injector.messages.showStatusBarMessage(null, exceptions[0].message)
|
injector.messages.showErrorMessage(editor, exceptions[0].message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,11 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
return TextRange(start, end)
|
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
|
val offset = caret.offset
|
||||||
return findWordAtOrFollowingCursor(editor, offset, isBigWord)
|
return findWordAtOrFollowingCursor(editor, offset, isBigWord)
|
||||||
}
|
}
|
||||||
@@ -168,6 +172,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
override fun findFilenameAtOrFollowingCursor(editor: VimEditor, caret: ImmutableVimCaret): TextRange? {
|
override fun findFilenameAtOrFollowingCursor(editor: VimEditor, caret: ImmutableVimCaret): TextRange? {
|
||||||
return findFilenameAtOrFollowingCursor(editor, caret.offset)
|
return findFilenameAtOrFollowingCursor(editor, caret.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findFilenameAtOrFollowingCursor(editor: VimEditor, offset: Int): TextRange? {
|
override fun findFilenameAtOrFollowingCursor(editor: VimEditor, offset: Int): TextRange? {
|
||||||
val text = editor.text()
|
val text = editor.text()
|
||||||
if (text.isEmpty()) return null
|
if (text.isEmpty()) return null
|
||||||
@@ -300,6 +305,9 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result is VimMatchResult.Failure) {
|
if (result is VimMatchResult.Failure) {
|
||||||
|
if (!showMessages) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
injector.messages.showErrorMessage(editor, injector.messages.message("E486", pattern))
|
injector.messages.showErrorMessage(editor, injector.messages.message("E486", pattern))
|
||||||
} else if (dir === Direction.FORWARDS) {
|
} else if (dir === Direction.FORWARDS) {
|
||||||
@@ -307,7 +315,6 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
} else {
|
} else {
|
||||||
injector.messages.showErrorMessage(editor, injector.messages.message("E384", pattern))
|
injector.messages.showErrorMessage(editor, injector.messages.message("E384", pattern))
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When trying to find the end position for a match, we're allowed to match the current position. But if we do that
|
// When trying to find the end position for a match, we're allowed to match the current position. But if we do that
|
||||||
@@ -334,6 +341,10 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result is VimMatchResult.Failure) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (result as VimMatchResult.Success).range
|
return (result as VimMatchResult.Success).range
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1768,7 +1779,8 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
|
|
||||||
if (isOuter && shouldEndOnWhitespace && start > 0
|
if (isOuter && shouldEndOnWhitespace && start > 0
|
||||||
&& !isWhitespace(editor, chars[end], isBig)
|
&& !isWhitespace(editor, chars[end], isBig)
|
||||||
&& !isWhitespace(editor, chars[start], isBig)) {
|
&& !isWhitespace(editor, chars[start], isBig)
|
||||||
|
) {
|
||||||
|
|
||||||
// Outer word objects normally include following whitespace. But if there's no following whitespace to include,
|
// Outer word objects normally include following whitespace. But if there's no following whitespace to include,
|
||||||
// we should extend the range to include preceding whitespace. However, Vim doesn't select whitespace at the
|
// we should extend the range to include preceding whitespace. However, Vim doesn't select whitespace at the
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ private class SearchAddress(pattern: String, offset: Int, move: Boolean) : Addre
|
|||||||
private val logger = vimLogger<SearchAddress>()
|
private val logger = vimLogger<SearchAddress>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val patterns: MutableList<String?> = mutableListOf()
|
private val patterns: MutableList<String> = mutableListOf()
|
||||||
private val directions: MutableList<Direction> = mutableListOf()
|
private val directions: MutableList<Direction> = mutableListOf()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -216,17 +216,17 @@ private class SearchAddress(pattern: String, offset: Int, move: Boolean) : Addre
|
|||||||
var pat = tok.nextToken()
|
var pat = tok.nextToken()
|
||||||
when (pat) {
|
when (pat) {
|
||||||
"\\/" -> {
|
"\\/" -> {
|
||||||
patterns.add(injector.searchGroup.lastSearchPattern)
|
patterns.add(injector.searchGroup.lastSearchPattern ?: throw exExceptionMessage("E35"))
|
||||||
directions.add(Direction.FORWARDS)
|
directions.add(Direction.FORWARDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
"\\?" -> {
|
"\\?" -> {
|
||||||
patterns.add(injector.searchGroup.lastSearchPattern)
|
patterns.add(injector.searchGroup.lastSearchPattern ?: throw exExceptionMessage("E35"))
|
||||||
directions.add(Direction.BACKWARDS)
|
directions.add(Direction.BACKWARDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
"\\&" -> {
|
"\\&" -> {
|
||||||
patterns.add(injector.searchGroup.lastSubstitutePattern)
|
patterns.add(injector.searchGroup.lastSubstitutePattern ?: throw exExceptionMessage("E33"))
|
||||||
directions.add(Direction.FORWARDS)
|
directions.add(Direction.FORWARDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ private class SearchAddress(pattern: String, offset: Int, move: Boolean) : Addre
|
|||||||
|
|
||||||
// Note that wrapscan, ignorecase, etc. all come from current option values, as expected
|
// Note that wrapscan, ignorecase, etc. all come from current option values, as expected
|
||||||
searchOffset = getSearchOffset(editor, line0, direction)
|
searchOffset = getSearchOffset(editor, line0, direction)
|
||||||
searchOffset = injector.searchGroup.processSearchRange(editor, pattern!!, patternOffset, searchOffset, direction)
|
searchOffset = injector.searchGroup.processSearchRange(editor, pattern, patternOffset, searchOffset, direction)
|
||||||
|
|
||||||
if (searchOffset == -1) {
|
if (searchOffset == -1) {
|
||||||
if (injector.options(editor).wrapscan) {
|
if (injector.options(editor).wrapscan) {
|
||||||
|
|||||||
@@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -25,9 +25,7 @@ fun VimEditor.exitVisualMode() {
|
|||||||
if (inBlockSelection) {
|
if (inBlockSelection) {
|
||||||
removeSecondaryCarets()
|
removeSecondaryCarets()
|
||||||
}
|
}
|
||||||
injector.application.runWriteAction {
|
nativeCarets().forEach(VimCaret::removeSelection)
|
||||||
nativeCarets().forEach(VimCaret::removeSelection)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (inVisualMode || inCommandLineModeWithVisual) {
|
if (inVisualMode || inCommandLineModeWithVisual) {
|
||||||
vimLastSelectionType = selectionType
|
vimLastSelectionType = selectionType
|
||||||
|
|||||||
@@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
@@ -18,3 +18,13 @@ fun KeyStroke.isCloseKeyStroke(): Boolean {
|
|||||||
keyCode == KeyEvent.VK_C && modifiers and InputEvent.CTRL_DOWN_MASK != 0 ||
|
keyCode == KeyEvent.VK_C && modifiers and InputEvent.CTRL_DOWN_MASK != 0 ||
|
||||||
keyCode == '['.code && modifiers and InputEvent.CTRL_DOWN_MASK != 0
|
keyCode == '['.code && modifiers and InputEvent.CTRL_DOWN_MASK != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this character would be matched as a command-line action (close or execute) rather than text input
|
||||||
|
* when re-injected through the key handler in CMD_LINE mode.
|
||||||
|
*
|
||||||
|
* Escape closes the command line, Enter/CR executes it.
|
||||||
|
*/
|
||||||
|
fun Char.isCommandLineActionChar(): Boolean {
|
||||||
|
return this == '\u001B' || this == '\n' || this == '\r'
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
package com.maddyhome.idea.vim.thinapi
|
package com.maddyhome.idea.vim.thinapi
|
||||||
|
|
||||||
import com.intellij.vim.api.VimApi
|
import com.intellij.vim.api.VimApi
|
||||||
|
import com.intellij.vim.api.models.CaretId
|
||||||
import com.intellij.vim.api.scopes.TextObjectRange
|
import com.intellij.vim.api.scopes.TextObjectRange
|
||||||
import com.intellij.vim.api.scopes.TextObjectScope
|
import com.intellij.vim.api.scopes.TextObjectScope
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
@@ -41,7 +42,7 @@ internal class TextObjectScopeImpl(
|
|||||||
keys: String,
|
keys: String,
|
||||||
registerDefaultMapping: Boolean,
|
registerDefaultMapping: Boolean,
|
||||||
preserveSelectionAnchor: Boolean,
|
preserveSelectionAnchor: Boolean,
|
||||||
rangeProvider: suspend VimApi.(count: Int) -> TextObjectRange?,
|
rangeProvider: suspend VimApi.(caret: CaretId, count: Int) -> TextObjectRange?,
|
||||||
) {
|
) {
|
||||||
val plugKeys = "<Plug>($pluginName-$keys)"
|
val plugKeys = "<Plug>($pluginName-$keys)"
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ private class TextObjectExtensionHandler(
|
|||||||
private val listenerOwner: ListenerOwner,
|
private val listenerOwner: ListenerOwner,
|
||||||
private val mappingOwner: MappingOwner,
|
private val mappingOwner: MappingOwner,
|
||||||
private val preserveSelectionAnchor: Boolean,
|
private val preserveSelectionAnchor: Boolean,
|
||||||
private val rangeProvider: suspend VimApi.(count: Int) -> TextObjectRange?,
|
private val rangeProvider: suspend VimApi.(caret: CaretId, count: Int) -> TextObjectRange?,
|
||||||
) : ExtensionHandler {
|
) : ExtensionHandler {
|
||||||
|
|
||||||
override val isRepeatable: Boolean = false
|
override val isRepeatable: Boolean = false
|
||||||
@@ -130,7 +131,7 @@ private class ApiTextObjectActionHandler(
|
|||||||
private val listenerOwner: ListenerOwner,
|
private val listenerOwner: ListenerOwner,
|
||||||
private val mappingOwner: MappingOwner,
|
private val mappingOwner: MappingOwner,
|
||||||
override val preserveSelectionAnchor: Boolean,
|
override val preserveSelectionAnchor: Boolean,
|
||||||
private val rangeProvider: suspend VimApi.(count: Int) -> TextObjectRange?,
|
private val rangeProvider: suspend VimApi.(caret: CaretId, count: Int) -> TextObjectRange?,
|
||||||
) : TextObjectActionHandler() {
|
) : TextObjectActionHandler() {
|
||||||
|
|
||||||
// Will be set based on the result of rangeProvider
|
// Will be set based on the result of rangeProvider
|
||||||
@@ -149,7 +150,7 @@ private class ApiTextObjectActionHandler(
|
|||||||
val vimApi = VimApiImpl(listenerOwner, mappingOwner, editor.projectId)
|
val vimApi = VimApiImpl(listenerOwner, mappingOwner, editor.projectId)
|
||||||
|
|
||||||
// Execute the range provider (suspend lambda bridged via runBlocking for now)
|
// Execute the range provider (suspend lambda bridged via runBlocking for now)
|
||||||
val apiRange = kotlinx.coroutines.runBlocking { vimApi.rangeProvider(count) } ?: return null
|
val apiRange = kotlinx.coroutines.runBlocking { vimApi.rangeProvider(CaretId(caret.id), count) } ?: return null
|
||||||
|
|
||||||
// Convert API range to internal TextRange and set visual type
|
// Convert API range to internal TextRange and set visual type
|
||||||
return when (apiRange) {
|
return when (apiRange) {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ sealed class Command(
|
|||||||
if (Flag.SAVE_SELECTION !in argFlags.flags) {
|
if (Flag.SAVE_SELECTION !in argFlags.flags) {
|
||||||
// Editor.inBlockSelection is not available, because we're not in Visual mode anymore. Check if the primary caret
|
// Editor.inBlockSelection is not available, because we're not in Visual mode anymore. Check if the primary caret
|
||||||
// currently has a selection and if (when we still in Visual) it was a block selection.
|
// currently has a selection and if (when we still in Visual) it was a block selection.
|
||||||
injector.application.runReadAction {
|
injector.application.runWriteAction {
|
||||||
if (editor.primaryCaret().hasSelection() && editor.primaryCaret().lastSelectionInfo.selectionType.isBlock) {
|
if (editor.primaryCaret().hasSelection() && editor.primaryCaret().lastSelectionInfo.selectionType.isBlock) {
|
||||||
editor.removeSecondaryCarets()
|
editor.removeSecondaryCarets()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ data class DelfunctionCommand(
|
|||||||
try {
|
try {
|
||||||
injector.functionService.deleteFunction(name, scope, this)
|
injector.functionService.deleteFunction(name, scope, this)
|
||||||
} catch (e: ExException) {
|
} catch (e: ExException) {
|
||||||
if (e.message != null && e.message!!.startsWith("E130")) {
|
if (e.code != "E130") {
|
||||||
// "ignoreIfMissing" flag handles the "E130: Unknown function" exception
|
|
||||||
} else {
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Range
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||||
import java.lang.Integer.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* see "h :[range]"
|
* see "h :[range]"
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ data class DefinedFunctionHandler(val function: FunctionDeclaration) :
|
|||||||
val returnValue = executeFunctionBody(exceptionsCaught, editor, context)
|
val returnValue = executeFunctionBody(exceptionsCaught, editor, context)
|
||||||
|
|
||||||
if (exceptionsCaught.isNotEmpty()) {
|
if (exceptionsCaught.isNotEmpty()) {
|
||||||
injector.messages.showErrorMessage(editor, exceptionsCaught.last().message)
|
injector.messages.appendErrorMessage(editor, exceptionsCaught.last().message)
|
||||||
}
|
}
|
||||||
return returnValue ?: VimInt.ZERO
|
return returnValue ?: VimInt.ZERO
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,10 +299,15 @@
|
|||||||
"class": "com.maddyhome.idea.vim.action.motion.updown.MotionDownAction",
|
"class": "com.maddyhome.idea.vim.action.motion.updown.MotionDownAction",
|
||||||
"modes": "NXO"
|
"modes": "NXO"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"keys": "<C-K>",
|
||||||
|
"class": "com.maddyhome.idea.vim.action.change.insert.CmdLineCompletedDigraphAction",
|
||||||
|
"modes": "C"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"keys": "<C-K>",
|
"keys": "<C-K>",
|
||||||
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction",
|
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction",
|
||||||
"modes": "IC"
|
"modes": "I"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keys": "<C-Left>",
|
"keys": "<C-Left>",
|
||||||
@@ -409,10 +414,15 @@
|
|||||||
"class": "com.maddyhome.idea.vim.action.window.tabs.PreviousTabAction",
|
"class": "com.maddyhome.idea.vim.action.window.tabs.PreviousTabAction",
|
||||||
"modes": "NXO"
|
"modes": "NXO"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"keys": "<C-Q>",
|
||||||
|
"class": "com.maddyhome.idea.vim.action.change.insert.CmdLineCompletedLiteralAction",
|
||||||
|
"modes": "C"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"keys": "<C-Q>",
|
"keys": "<C-Q>",
|
||||||
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction",
|
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction",
|
||||||
"modes": "IC"
|
"modes": "I"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keys": "<C-R>",
|
"keys": "<C-R>",
|
||||||
@@ -559,10 +569,15 @@
|
|||||||
"class": "com.maddyhome.idea.vim.action.motion.scroll.CtrlUpAction",
|
"class": "com.maddyhome.idea.vim.action.motion.scroll.CtrlUpAction",
|
||||||
"modes": "N"
|
"modes": "N"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"keys": "<C-V>",
|
||||||
|
"class": "com.maddyhome.idea.vim.action.change.insert.CmdLineCompletedLiteralAction",
|
||||||
|
"modes": "C"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"keys": "<C-V>",
|
"keys": "<C-V>",
|
||||||
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction",
|
"class": "com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction",
|
||||||
"modes": "IC"
|
"modes": "I"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keys": "<C-W>",
|
"keys": "<C-W>",
|
||||||
|
|||||||
Reference in New Issue
Block a user