1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-11-25 07:42:59 +01:00

Compare commits

..

19 Commits

Author SHA1 Message Date
9101ca1afc
Set plugin version to chylex-37 2024-07-09 05:56:31 +02:00
7e493e0188
Revert per-caret registers 2024-07-09 05:56:31 +02:00
78b5540636
Revert "Factor disposable objects on editor opening"
This reverts commit 1fa78935
2024-07-09 05:48:41 +02:00
97ba6ae997
Fix(VIM-3364): Exception with mapped Generate action 2024-07-09 05:48:41 +02:00
9ac1a14604
Apply scrolloff after executing native IDEA actions 2024-07-09 05:48:35 +02:00
3c20feba8d
Stay on same line after reindenting 2024-07-09 05:48:29 +02:00
d23475818a
Update search register when using f/t 2024-07-09 05:48:29 +02:00
7f5c9b56ef
Automatically add unambiguous imports after running a macro 2024-07-09 05:48:29 +02:00
c3f5cb4866
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-07-09 05:48:29 +02:00
7a25fbfd31
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-07-09 05:48:29 +02:00
444880b153
Add support for count for visual and line motion surround 2024-07-09 05:48:29 +02:00
0ae06e46da
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-07-09 05:48:29 +02:00
98e085e636
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-07-09 05:48:28 +02:00
67c60ae8cc
Respect count with <Action> mappings 2024-07-09 05:48:28 +02:00
ffd2433da3
Change matchit plugin to use HTML patterns in unrecognized files 2024-07-09 05:48:28 +02:00
cfe219250c
Reset insert mode when switching active editor 2024-07-09 05:48:28 +02:00
68bdae1afc
Remove update checker 2024-07-09 05:48:28 +02:00
77859e03e7
Set custom plugin version 2024-07-09 05:48:28 +02:00
cfd05be288
Revert "Migrate to IntelliJ Platform Gradle Plugin 2.0"
This reverts commit 4913b13a
2024-07-09 05:48:28 +02:00
174 changed files with 1964 additions and 2847 deletions

View File

@ -27,7 +27,7 @@ jobs:
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Run tests - name: Run tests
run: ./gradlew --no-configuration-cache integrationsTest run: ./gradlew integrationsTest
env: env:
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }} YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

@ -16,14 +16,14 @@ jobs:
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
# - name: Setup Gradle - name: Setup Gradle
# uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log & gradle runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@ -37,7 +37,7 @@ jobs:
run: mv tests/ui-ij-tests/video build/reports run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -46,7 +46,7 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-ij-tests/build/reports tests/ui-ij-tests/build/reports
idea-sandbox-log sandbox-idea-log
# build-for-ui-test-linux: # build-for-ui-test-linux:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:

View File

@ -19,14 +19,14 @@ jobs:
python-version: '3.10' python-version: '3.10'
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
# - name: Setup Gradle - name: Setup Gradle
# uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache :runIdeForUiTests -PideaType=PC > build/reports/idea.log & gradle :runIdeForUiTests -PideaType=PC > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@ -40,7 +40,7 @@ jobs:
run: mv tests/ui-py-tests/video build/reports run: mv tests/ui-py-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/PC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -49,4 +49,4 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-py-tests/build/reports tests/ui-py-tests/build/reports
idea-sandbox-log sandbox-idea-log

View File

@ -16,14 +16,14 @@ jobs:
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
# - name: Setup Gradle - name: Setup Gradle
# uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle --no-configuration-cache runIdeForUiTests > build/reports/idea.log & gradle runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@ -37,7 +37,7 @@ jobs:
run: mv tests/ui-ij-tests/video build/reports run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -46,7 +46,7 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-ij-tests/build/reports tests/ui-ij-tests/build/reports
idea-sandbox-log sandbox-idea-log
# build-for-ui-test-linux: # build-for-ui-test-linux:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:

3
.gitignore vendored
View File

@ -1,6 +1,5 @@
*.swp *.swp
/.gradle/ /.gradle/
/.intellijPlatform/
/.idea/ /.idea/
!/.idea/scopes !/.idea/scopes
@ -33,5 +32,3 @@ vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
# Created by github automation # Created by github automation
settings.xml settings.xml
.kotlin

View File

@ -12,7 +12,7 @@
<option name="taskNames"> <option name="taskNames">
<list> <list>
<option value="check" /> <option value="check" />
<option value="verifyPlugin" /> <option value="runPluginVerifier" />
</list> </list>
</option> </option>
<option name="vmOptions" value="" /> <option name="vmOptions" value="" />
@ -20,7 +20,6 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled> <DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>

View File

@ -37,7 +37,7 @@ object Compatibility : IdeaVimBuildType({
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}eu.theblob42.idea.whichkey' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}eu.theblob42.idea.whichkey' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city
# Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city # Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city
# java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.copilot' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.copilot' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.dankinsoid.multicursor' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.dankinsoid.multicursor' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.joshestein.ideavim-quickscope' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.joshestein.ideavim-quickscope' [latest-IU] -team-city
""".trimIndent() """.trimIndent()

View File

@ -22,7 +22,7 @@ object PluginVerifier : IdeaVimBuildType({
steps { steps {
gradle { gradle {
tasks = "clean verifyPlugin" tasks = "clean runPluginVerifier"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
} }

View File

@ -144,7 +144,6 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
gradle { gradle {
name = "Run Integrations" name = "Run Integrations"
tasks = "releaseActions" tasks = "releaseActions"
gradleParams = "--no-configuration-cache"
} }
// gradle { // gradle {
// name = "Slack Notification" // name = "Slack Notification"

View File

@ -222,13 +222,13 @@ Ex commands or via `:map` command mappings:
* Execute an action by `{action_id}`. Works from Ex command line. * Execute an action by `{action_id}`. Works from Ex command line.
* Please don't use `:action` in mappings. Use `<Action>` instead. * Please don't use `:action` in mappings. Use `<Action>` instead.
### Finding action IDs: ### Finding action ids:
* IJ provides `IdeaVim: track action IDs` command to show the id of the executed actions. * IJ provides `IdeaVim: track action Ids` command to show the id of the executed actions.
This command can be found in "Search everywhere" (double `shift`). This command can be found in "Search everywhere" (double `shift`).
<details> <details>
<summary><strong>"Track action IDs" Details</strong> (click to see)</summary> <summary><strong>"Track action Ids" Details</strong> (click to see)</summary>
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif"> <source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif">
<img src="assets/readme/track_action_light.gif" alt="track action ids"/> <img src="assets/readme/track_action_light.gif" alt="track action ids"/>
@ -369,8 +369,6 @@ is the full list of synonyms.
- Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27): - Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27):
> The local value is set to -123456 when the global value is to be used. > The local value is set to -123456 when the global value is to be used.
- Vi (not Vim) is a POSIX standard, and [has a spec](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html)! Vim is mostly POSIX compliant when Vi compatibility is selected with the `'compatible'` option, but there are still some differences that can be changed with `'copoptions'`. The spec is interesting because it documents the behaviour of different commands in a stricter style than the user documentation, describing the current line and column after the command, for example. [More details can be found by reading `:help posix`](https://vimhelp.org/vi_diff.txt.html#posix).
License License
------- -------

View File

@ -8,7 +8,7 @@
plugins { plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.serialization") version "2.0.0" kotlin("plugin.serialization") version "1.9.22"
} }
val kotlinxSerializationVersion: String by project val kotlinxSerializationVersion: String by project
@ -21,7 +21,7 @@ repositories {
} }
dependencies { dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.24") compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.22")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution // kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

@ -32,8 +32,6 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
import org.kohsuke.github.GHUser import org.kohsuke.github.GHUser
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@ -45,7 +43,7 @@ buildscript {
} }
dependencies { dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2") classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2")
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
@ -66,23 +64,25 @@ buildscript {
plugins { plugins {
java java
kotlin("jvm") version "2.0.0" kotlin("jvm") version "1.9.22"
application application
id("java-test-fixtures") id("java-test-fixtures")
id("org.jetbrains.intellij.platform") version "2.0.0-rc2"
id("org.jetbrains.changelog") version "2.2.1" id("org.jetbrains.intellij") version "1.17.3"
id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.6.1" id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "4.0.0" id("com.dorongold.task-tree") version "4.0.0"
id("com.google.devtools.ksp") version "2.0.0-1.0.23"
}
val moduleSources by configurations.registering id("com.google.devtools.ksp") version "1.9.22-1.0.17"
}
// Import variables from gradle.properties file // Import variables from gradle.properties file
val javaVersion: String by project val javaVersion: String by project
val kotlinVersion: String by project val kotlinVersion: String by project
val ideaVersion: String by project val ideaVersion: String by project
val ideaType: String by project val ideaType: String by project
val downloadIdeaSources: String by project
val instrumentPluginCode: String by project val instrumentPluginCode: String by project
val remoteRobotVersion: String by project val remoteRobotVersion: String by project
val splitModeVersion: String by project val splitModeVersion: String by project
@ -97,9 +97,7 @@ val releaseType: String? by project
repositories { repositories {
mavenCentral() mavenCentral()
intellijPlatform { maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
defaultRepositories()
}
} }
dependencies { dependencies {
@ -110,27 +108,10 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.1.0") compileOnly("org.jetbrains:annotations:24.1.0")
intellijPlatform {
// Note that it is also possible to use local("...") to compile against a locally installed IDE
// E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
// Or something like: intellijIdeaUltimate(ideaVersion)
create(ideaType, ideaVersion)
pluginVerifier()
zipSigner()
instrumentationTools()
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
// AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19")
}
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
// --------- Test dependencies ---------- // --------- Test dependencies ----------
testImplementation(testFixtures(project(":")))
testApi("com.squareup.okhttp3:okhttp:4.12.0") testApi("com.squareup.okhttp3:okhttp:4.12.0")
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
@ -144,7 +125,7 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
@ -152,12 +133,6 @@ dependencies {
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
} }
configurations { configurations {
@ -197,14 +172,7 @@ tasks {
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library // See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions // For the list of bundled versions
apiVersion = "1.9" apiVersion = "1.9"
freeCompilerArgs = listOf( freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
@ -213,76 +181,51 @@ tasks {
kotlinOptions { kotlinOptions {
jvmTarget = javaVersion jvmTarget = javaVersion
apiVersion = "1.9" apiVersion = "1.9"
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
freeCompilerArgs += listOf("-Xskip-prerelease-check", "-Xallow-unstable-dependencies")
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
// Note that this will run the plugin installed in the IDE specified in dependencies. To run in a different IDE, use
// a custom task (see below)
runIde { runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true) systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
} }
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies downloadRobotServerPlugin {
// Note that the version must be greater than the plugin's target version, for obvious reasons version.set(remoteRobotVersion)
// val runIdeCustom by intellijPlatformTesting.runIde.registering {
// type = IntelliJPlatformType.Rider
// version = "2024.1.2"
// }
// Uncomment to run the plugin in a locally installed IDE
// val runIdeLocal by intellijPlatformTesting.runIde.registering {
// localPath = file("/Users/{user}/Applications/WebStorm.app")
// }
val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Drobot-server.port=8082",
"-Dide.mac.message.dialogs.as.sheets=false",
"-Djb.privacy.policy.text=<!--999.999-->",
"-Djb.consents.confirmation.enabled=false",
"-Dide.show.tips.on.startup.default.value=false",
"-Doctopus.handler=" + (System.getProperty("octopus.handler") ?: true),
)
}
}
plugins {
robotServerPlugin(remoteRobotVersion)
}
} }
val runIdeSplitMode by intellijPlatformTesting.runIde.registering { runIdeForUiTests {
splitMode = true systemProperty("robot-server.port", "8082")
splitModeTarget = SplitModeAware.SplitModeTarget.FRONTEND systemProperty("ide.mac.message.dialogs.as.sheets", "false")
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.show.tips.on.startup.default.value", "false")
// Frontend split mode support requires 242+ systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
// TODO: Remove this once IdeaVim targets 242, as the task will naturally use the target version to run
version.set(splitModeVersion)
} }
// Add plugin open API sources to the plugin ZIP // Add plugin open API sources to the plugin ZIP
val sourcesJar by registering(Jar::class) { val createOpenApiSourceJar by registering(Jar::class) {
dependsOn(moduleSources) // Java sources
from(sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
from(project(":vim-engine").sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
// Kotlin sources
from(kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
destinationDirectory.set(layout.buildDirectory.dir("libs")) destinationDirectory.set(layout.buildDirectory.dir("libs"))
archiveClassifier.set(DocsType.SOURCES) archiveClassifier.set("src")
from(sourceSets.main.map { it.kotlin })
from(provider {
moduleSources.map {
it.map { jarFile -> zipTree(jarFile) }
}
})
} }
buildPlugin { buildPlugin {
dependsOn(sourcesJar) dependsOn(createOpenApiSourceJar)
from(sourcesJar) { into("lib/src") } from(createOpenApiSourceJar) { into("lib/src") }
} }
} }
@ -307,51 +250,44 @@ gradle.projectsEvaluated {
// --- Intellij plugin // --- Intellij plugin
intellijPlatform { intellij {
pluginConfiguration { version.set(ideaVersion)
name = "IdeaVim" type.set(ideaType)
changeNotes.set( pluginName.set("IdeaVim")
"""
Undo in IdeaVim now works like in Vim<br/>
Caret movement is no longer a separate undo step, and full insert is undoable in one step.<br/>
<a href="https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks">Share Feedback</a>
<br/>
<br/>
<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>
""".trimIndent()
)
ideaVersion { updateSinceUntilBuild.set(false)
// Let the Gradle plugin set the since-build version. It defaults to the version of the IDE we're building against
// specified as two components, `{branch}.{build}` (e.g., "241.15989"). There is no third component specified.
// The until-build version defaults to `{branch}.*`, but we want to support _all_ future versions, so we set it
// with a null provider (the provider is important).
// By letting the Gradle plugin handle this, the Plugin DevKit IntelliJ plugin cannot help us with the "Usage of
// IntelliJ API not available in older IDEs" inspection. However, since our since-build is the version we compile
// against, we can never get an API that's newer - it would be an unresolved symbol.
untilBuild.set(provider { null })
}
}
publishing { downloadSources.set(downloadIdeaSources.toBoolean())
instrumentCode.set(instrumentPluginCode.toBoolean())
intellijRepository.set("https://www.jetbrains.com/intellij-repository")
plugins.set(listOf("AceJump:3.8.11"))
}
tasks {
publishPlugin {
channels.set(publishChannels.split(",")) channels.set(publishChannels.split(","))
token.set(publishToken) token.set(publishToken)
} }
signing { signPlugin {
certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN")) certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN"))
privateKey.set(providers.environmentVariable("PRIVATE_KEY")) privateKey.set(providers.environmentVariable("PRIVATE_KEY"))
password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD")) password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
} }
verifyPlugin { runPluginVerifier {
teamCityOutputFormat = true downloadDir.set("${project.buildDir}/pluginVerifier/ides")
ides { teamCityOutputFormat.set(true)
recommended()
}
} }
instrumentCode.set(instrumentPluginCode.toBoolean()) patchPluginXml {
// Don't forget to update plugin.xml
sinceBuild.set("241.15989.150")
changeNotes.set(
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
)
}
} }
ksp { ksp {
@ -955,12 +891,12 @@ fun changes(): List<Change> {
println("Start changes processing") println("Start changes processing")
for (message in messages) { for (message in messages) {
println("Processing '$message'...") println("Processing '$message'...")
val lowercaseMessage = message.lowercase() val lowercaseMessage = message.toLowerCase()
val regex = "^fix\\((vim-\\d+)\\):".toRegex() val regex = "^fix\\((vim-\\d+)\\):".toRegex()
val findResult = regex.find(lowercaseMessage) val findResult = regex.find(lowercaseMessage)
if (findResult != null) { if (findResult != null) {
println("Message matches") println("Message matches")
val value = findResult.groups[1]!!.value.uppercase() val value = findResult.groups[1]!!.value.toUpperCase()
val shortMessage = message.drop(findResult.range.last + 1).trim() val shortMessage = message.drop(findResult.range.last + 1).trim()
newFixes += Change(value, shortMessage) newFixes += Change(value, shortMessage)
} else { } else {

View File

@ -5,7 +5,7 @@ Using actions from external plugins is the same, as tracking and reusing any IDE
1. Install the plugin via Marketplace 1. Install the plugin via Marketplace
2. Enable action tracking. You can enable it by one of the following ways: 2. Enable action tracking. You can enable it by one of the following ways:
* Execute `:set trackactionids` ex command or just `:set tai` * Execute `:set trackactionids` ex command or just `:set tai`
* Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action IDs" to find the toggle that enables and disables action tracking * Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action Ids" to find the toggle that enables and disables action tracking
3. Execute the plugin action the way intended by plugin author "See Edit menu or use ⇧ + ⌥ + U / Shift + Alt + U" or just find the `Toggle Camel Case` action in the "Find actions" window (`Ctrl-Shift-A`). If you action tracking is on, you will see this notification in your right bottom corner: 3. Execute the plugin action the way intended by plugin author "See Edit menu or use ⇧ + ⌥ + U / Shift + Alt + U" or just find the `Toggle Camel Case` action in the "Find actions" window (`Ctrl-Shift-A`). If you action tracking is on, you will see this notification in your right bottom corner:
<img alt="Notification" src="images/action-id-notification.png"/> <img alt="Notification" src="images/action-id-notification.png"/>

View File

@ -16,13 +16,14 @@
# https://data.services.jetbrains.com/products?code=IC # https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases # Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots # And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
ideaVersion=2024.2 ideaVersion=2024.1.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=IC ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-38 version=chylex-37
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.23 remoteRobotVersion=0.11.22
antlrVersion=4.10.1 antlrVersion=4.10.1
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2 # [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2
@ -34,7 +35,7 @@ splitModeVersion=242-EAP-SNAPSHOT
# Please don't forget to update kotlin version in buildscript section # Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version # Also update kotlinxSerializationVersion version
kotlinVersion=2.0.0 kotlinVersion=1.9.22
publishToken=token publishToken=token
publishChannels=eap publishChannels=eap

View File

@ -19,9 +19,9 @@ exclude:
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
- src/main/java/com/maddyhome/idea/vim/package-info.java - src/main/java/com/maddyhome/idea/vim/package-info.java
- vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated - vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
- vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated
- src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java - src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java
- tests/ui-fixtures - tests/ui-fixtures
dependencyIgnores: dependencyIgnores:

View File

@ -20,7 +20,7 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.25") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
implementation("io.ktor:ktor-client-core:2.3.12") implementation("io.ktor:ktor-client-core:2.3.12")
implementation("io.ktor:ktor-client-cio:2.3.10") implementation("io.ktor:ktor-client-cio:2.3.10")

View File

@ -8,19 +8,14 @@
package com.maddyhome.idea.vim package com.maddyhome.idea.vim
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.updateSettings.impl.UpdateSettings
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
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.ui.JoinEap
import com.maddyhome.idea.vim.ui.JoinEap.EAP_LINK
/** /**
* @author Alex Plate * @author Alex Plate
@ -33,11 +28,6 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
if (firstInitializationOccurred) return if (firstInitializationOccurred) return
firstInitializationOccurred = true firstInitializationOccurred = true
if (!VimPlugin.getVimState().wasSubscibedToEAPAutomatically && ApplicationManager.getApplication().isEAP && !JoinEap.eapActive()) {
VimPlugin.getVimState().wasSubscibedToEAPAutomatically = true
UpdateSettings.getInstance().storedPluginHosts += EAP_LINK
}
// This code should be executed once // This code should be executed once
VimPlugin.getInstance().initialize() VimPlugin.getInstance().initialize()
} }
@ -46,7 +36,6 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
// This is a temporal workaround for VIM-2487 // This is a temporal workaround for VIM-2487
internal class PyNotebooksCloseWorkaround : ProjectManagerListener { internal class PyNotebooksCloseWorkaround : ProjectManagerListener {
override fun projectClosingBeforeSave(project: Project) { override fun projectClosingBeforeSave(project: Project) {
initInjector()
// TODO: Confirm context in CWM scenario // TODO: Confirm context in CWM scenario
if (injector.globalIjOptions().closenotebooks) { if (injector.globalIjOptions().closenotebooks) {
injector.editorGroup.getEditors().forEach { vimEditor -> injector.editorGroup.getEditors().forEach { vimEditor ->

View File

@ -37,7 +37,6 @@ import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
import com.maddyhome.idea.vim.helper.MacKeyRepeat; import com.maddyhome.idea.vim.helper.MacKeyRepeat;
import com.maddyhome.idea.vim.listener.VimListenerManager; import com.maddyhome.idea.vim.listener.VimListenerManager;
import com.maddyhome.idea.vim.newapi.IjVimInjector; import com.maddyhome.idea.vim.newapi.IjVimInjector;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup; import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
import com.maddyhome.idea.vim.ui.StatusBarIconFactory; import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
import com.maddyhome.idea.vim.vimscript.services.VariableService; import com.maddyhome.idea.vim.vimscript.services.VariableService;
@ -68,7 +67,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
private static final Logger LOG = Logger.getInstance(VimPlugin.class); private static final Logger LOG = Logger.getInstance(VimPlugin.class);
static { static {
IjVimInjectorKt.initInjector(); VimInjectorKt.setInjector(new IjVimInjector());
} }
private final @NotNull VimState state = new VimState(); private final @NotNull VimState state = new VimState();

View File

@ -18,7 +18,7 @@ import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
internal class VimProjectService(val project: Project) : Disposable { internal class VimProjectService(val project: Project) : Disposable {
override fun dispose() { override fun dispose() {
// Not sure if this is a best solution // Not sure if this is a best solution
ExEntryPanel.getInstance().setEditor(null) ExEntryPanel.getInstance().editor = null
} }
companion object { companion object {

View File

@ -43,7 +43,6 @@ import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.listener.AceJumpService import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
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.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.ui.ex.ExTextField import com.maddyhome.idea.vim.ui.ex.ExTextField
@ -62,13 +61,10 @@ import javax.swing.KeyStroke
* way to get ideavim keys for this plugin. See VIM-3085 * way to get ideavim keys for this plugin. See VIM-3085
*/ */
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ { class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
init {
initInjector()
}
private val traceTime: Boolean private val traceTime: Boolean
get() { get() {
// Make sure the injector is initialized
VimPlugin.getInstance()
return injector.globalOptions().ideatracetime return injector.globalOptions().ideatracetime
} }
@ -261,7 +257,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
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) {
ExEntryPanel.getInstance().ijEditor ExEntryPanel.getInstance().editor
} else { } else {
null null
} }

View File

@ -79,7 +79,7 @@ internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(editor, IdeActions.ACTION_QUICK_JAVADOC, context) injector.actionExecutor.executeAction(IdeActions.ACTION_QUICK_JAVADOC, context)
return true return true
} }
} }

View File

@ -1,72 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.command
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.VimStateMachine
/**
* COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r
*/
@Deprecated("Use `injector.vimState`")
class CommandState(private val machine: VimStateMachine) {
val mode: Mode
get() {
val myMode = machine.mode
return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL
}
}
@Deprecated("Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.commandBuilder",
"com.maddyhome.idea.vim.KeyHandler"
)
)
val commandBuilder: CommandBuilder
get() = KeyHandler.getInstance().keyHandlerState.commandBuilder
@Deprecated("Use `KeyHandler.keyHandlerState.mappingState", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.mappingState",
"com.maddyhome.idea.vim.KeyHandler"
)
)
val mappingState: MappingState
get() = KeyHandler.getInstance().keyHandlerState.mappingState
enum class Mode {
// Basic modes
COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
// Additional modes
OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
}
enum class SubMode {
NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
}
companion object {
@JvmStatic
@Deprecated("Use `injector.vimState`")
fun getInstance(editor: Editor): CommandState {
return CommandState(injector.vimState)
}
}
}

View File

@ -12,17 +12,17 @@ import com.intellij.application.options.CodeStyle
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
import com.maddyhome.idea.vim.api.VimIndentConfig
internal class IndentConfig private constructor(indentOptions: IndentOptions): VimIndentConfig { internal class IndentConfig private constructor(indentOptions: IndentOptions) {
private val indentSize = indentOptions.INDENT_SIZE private val indentSize = indentOptions.INDENT_SIZE
private val tabSize = indentOptions.TAB_SIZE private val tabSize = indentOptions.TAB_SIZE
private val isUseTabs = indentOptions.USE_TAB_CHARACTER private val isUseTabs = indentOptions.USE_TAB_CHARACTER
override fun getIndentSize(depth: Int): Int = indentSize * depth fun getTotalIndent(count: Int): Int = indentSize * count
override fun createIndentByDepth(depth: Int): String = createIndentBySize(getIndentSize(depth))
override fun createIndentBySize(size: Int): String { fun createIndentByCount(count: Int): String = createIndentBySize(getTotalIndent(count))
fun createIndentBySize(size: Int): String {
val tabCount: Int val tabCount: Int
val spaceCount: Int val spaceCount: Int
if (isUseTabs) { if (isUseTabs) {

View File

@ -18,8 +18,6 @@ import kotlin.reflect.KProperty
internal class VimState { internal class VimState {
var isIdeaJoinNotified by StateProperty("idea-join") var isIdeaJoinNotified by StateProperty("idea-join")
var isIdeaPutNotified by StateProperty("idea-put") var isIdeaPutNotified by StateProperty("idea-put")
var isNewUndoNotified by StateProperty("idea-undo")
var wasSubscibedToEAPAutomatically by StateProperty("was-automatically-subscribed-to-eap")
fun readData(element: Element) { fun readData(element: Element) {
val notifications = element.getChild("notifications") val notifications = element.getChild("notifications")

View File

@ -9,15 +9,14 @@ package com.maddyhome.idea.vim.ex
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.maddyhome.idea.vim.api.VimOutputPanelBase import com.maddyhome.idea.vim.api.VimOutputPanel
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimExOutput import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel import com.maddyhome.idea.vim.ui.ExOutputPanel
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import javax.swing.KeyStroke
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing // TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanelBase() { class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanel {
private var isActiveInTestMode = false private var isActiveInTestMode = false
val editor get() = myEditor.get() val editor get() = myEditor.get()
@ -49,24 +48,6 @@ class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPane
} }
} }
override fun scrollPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollPage()
}
override fun scrollHalfPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollHalfPage()
}
override fun scrollLine() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollLine()
}
override var text: String = "" override var text: String = ""
get() = if (!ApplicationManager.getApplication().isUnitTestMode) { get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).text } ?: "" editor?.let { ExOutputPanel.getInstance(it).text } ?: ""
@ -85,17 +66,6 @@ class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPane
isActiveInTestMode = newValue.isNotEmpty() isActiveInTestMode = newValue.isNotEmpty()
} }
} }
override var label: String
get() {
val notNullEditor = editor ?: return ""
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return ""
return panel.myLabel.text
}
set(value) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.myLabel.text = value
}
fun output(text: String) { fun output(text: String) {
this.text = text this.text = text
@ -105,25 +75,6 @@ class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPane
text = "" text = ""
} }
override val atEnd: Boolean
get() {
val notNullEditor = editor ?: return false
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return false
return panel.isAtEnd()
}
override fun onBadKey() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.onBadKey()
}
override fun close(key: KeyStroke?) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.close(key)
}
override fun close() { override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) { if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).close() } editor?.let { ExOutputPanel.getInstance(it).close() }

View File

@ -31,7 +31,6 @@ import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.ui.ex.ExEntryPanelService
import com.maddyhome.idea.vim.vimscript.model.Executable import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimLContext import com.maddyhome.idea.vim.vimscript.model.VimLContext
@ -184,7 +183,7 @@ object VimExtensionFacade {
/** Returns a string typed in the input box similar to 'input()'. */ /** Returns a string typed in the input box similar to 'input()'. */
@JvmStatic @JvmStatic
fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String { fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
return (injector.commandLine as ExEntryPanelService).inputString(editor.vim, context.vim, prompt, finishOn) ?: "" return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
} }
/** Get the current contents of the given register similar to 'getreg()'. */ /** Get the current contents of the given register similar to 'getreg()'. */

View File

@ -79,12 +79,11 @@ internal class CommentaryExtension : VimExtension {
val project = editor.ij.project!! val project = editor.ij.project!!
val callback = { afterCommenting(mode, editor, resetCaret, range) } val callback = { afterCommenting(mode, editor, resetCaret, range) }
actions.any { executeActionWithCallbackOnSuccess(editor, it, project, context, callback) } actions.any { executeActionWithCallbackOnSuccess(it, project, context, callback) }
} }
} }
private fun executeActionWithCallbackOnSuccess( private fun executeActionWithCallbackOnSuccess(
editor: VimEditor,
action: String, action: String,
project: Project, project: Project,
context: ExecutionContext, context: ExecutionContext,
@ -93,7 +92,7 @@ internal class CommentaryExtension : VimExtension {
val res = Ref.create<Boolean>(false) val res = Ref.create<Boolean>(false)
AsyncActionExecutionService.getInstance(project).withExecutionAfterAction( AsyncActionExecutionService.getInstance(project).withExecutionAfterAction(
action, action,
{ res.set(injector.actionExecutor.executeAction(editor, name = action, context = context)) }, { res.set(injector.actionExecutor.executeAction(action, context)) },
{ if (res.get()) callback() }) { if (res.get()) callback() })
return res.get() return res.get()
} }

View File

@ -23,15 +23,13 @@ import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse import com.intellij.util.Alarm.ThreadToUse
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
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.common.ModeChangeListener
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.VimYankListener
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.VimNlsSafe import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.listener.VimYankListener
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
@ -77,7 +75,7 @@ internal class HighlightColorResetter : LafManagerListener {
* *
* When a new text is yanked or user starts editing, the old highlighting would be deleted. * When a new text is yanked or user starts editing, the old highlighting would be deleted.
*/ */
internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeListener { internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
private val highlightHandler = HighlightHandler() private val highlightHandler = HighlightHandler()
private var initialised = false private var initialised = false
@ -85,8 +83,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
override fun init() { override fun init() {
// Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called // Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called
injector.listenersNotifier.modeChangeListeners.add(this) VimPlugin.getYank().addListener(this)
injector.listenersNotifier.yankListeners.add(this) VimPlugin.getChange().addInsertListener(this)
// Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when // Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when
// IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the // IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the
@ -107,8 +105,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
override fun dispose() { override fun dispose() {
// Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads // Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads
injector.listenersNotifier.modeChangeListeners.remove(this) VimPlugin.getYank().removeListener(this)
injector.listenersNotifier.yankListeners.remove(this) VimPlugin.getChange().removeInsertListener(this)
highlightHandler.clearYankHighlighters() highlightHandler.clearYankHighlighters()
initialised = false initialised = false
@ -119,8 +117,7 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
highlightHandler.highlightYankRange(editor.ij, range) highlightHandler.highlightYankRange(editor.ij, range)
} }
override fun modeChanged(editor: VimEditor, oldMode: Mode) { override fun insertModeStarted(editor: Editor) {
if (editor.mode !is Mode.INSERT) return
ensureInitialised() ensureInitialised()
highlightHandler.clearYankHighlighters() highlightHandler.clearYankHighlighters()
} }

View File

@ -481,9 +481,6 @@ internal class NerdTree : VimExtension {
registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile")) registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile"))
registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir")) registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir"))
registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete")) registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete"))
registerCommand("NERDTreeMapCopy", "y", NerdAction.ToIj("\$Copy"))
registerCommand("NERDTreeMapPaste", "v", NerdAction.ToIj("\$Paste"))
registerCommand("NERDTreeMapRename", "<C-r>", NerdAction.ToIj("RenameElement"))
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize")) registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu")) registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow")) registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))

View File

@ -36,7 +36,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
@ -51,7 +50,6 @@ import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
import com.maddyhome.idea.vim.state.mode.returnTo
/** /**
* Port of vim-surround. * Port of vim-surround.
@ -117,9 +115,6 @@ internal class VimSurroundExtension : VimExtension {
// it.moveToOffset(lineStartOffset) // it.moveToOffset(lineStartOffset)
} }
// Jump back to start // Jump back to start
if (editor.mode !is Mode.NORMAL) {
editor.mode = Mode.NORMAL()
}
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
} }
@ -141,7 +136,7 @@ internal class VimSurroundExtension : VimExtension {
} }
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
editor.exitVisualMode() executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
} }
} }
} }
@ -311,10 +306,8 @@ internal class VimSurroundExtension : VimExtension {
private fun getSurroundRange(caret: VimCaret): TextRange? { private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor val editor = caret.editor
if (editor.mode is Mode.CMD_LINE) { val ijEditor = editor.ij
editor.mode = (editor.mode as Mode.CMD_LINE).returnTo() return when (ijEditor.vim.mode) {
}
return when (editor.mode) {
is Mode.NORMAL -> injector.markService.getChangeMarks(caret) is Mode.NORMAL -> injector.markService.getChangeMarks(caret)
is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) } is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) }
else -> null else -> null
@ -357,9 +350,6 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? { private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
val tagInput = inputString(editor, context, "<", '>') val tagInput = inputString(editor, context, "<", '>')
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput) val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) { return if (matcher.find()) {
val tagName = matcher.group(1) val tagName = matcher.group(1)
@ -376,9 +366,6 @@ private fun inputFunctionName(
withInternalSpaces: Boolean, withInternalSpaces: Boolean,
): Pair<String, String>? { ): Pair<String, String>? {
val functionNameInput = inputString(editor, context, "function: ", null) val functionNameInput = inputString(editor, context, "function: ", null)
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
if (functionNameInput.isEmpty()) return null if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")" return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
} }

View File

@ -15,35 +15,77 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actions.EnterAction import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.EditorMouseEvent import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener import com.intellij.openapi.editor.event.EditorMouseListener
import com.intellij.openapi.editor.impl.TextRangeInterval
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiUtilBase import com.intellij.psi.util.PsiUtilBase
import com.intellij.util.containers.ContainerUtil
import com.maddyhome.idea.vim.EventFacade import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroupBase import com.maddyhome.idea.vim.api.VimChangeGroupBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimMotionGroupBase
import com.maddyhome.idea.vim.api.anyNonWhitespace
import com.maddyhome.idea.vim.api.getLineEndForOffset import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.lineLength
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.group.MotionGroup.Companion.getMotionRange2
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.handler.commandContinuation import com.maddyhome.idea.vim.handler.commandContinuation
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.NumberType
import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.findNumberUnderCursor
import com.maddyhome.idea.vim.helper.findNumbersInRange
import com.maddyhome.idea.vim.helper.inInsertMode import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
import com.maddyhome.idea.vim.listener.VimInsertListener import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly
import java.math.BigInteger
import java.util.*
import java.util.function.Consumer
import kotlin.math.max
/** /**
* Provides all the insert/replace related functionality * Provides all the insert/replace related functionality
*/ */
class ChangeGroup : VimChangeGroupBase() { class ChangeGroup : VimChangeGroupBase() {
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
private val listener: EditorMouseListener = object : EditorMouseListener { private val listener: EditorMouseListener = object : EditorMouseListener {
override fun mouseClicked(event: EditorMouseEvent) { override fun mouseClicked(event: EditorMouseEvent) {
val editor = event.editor val editor = event.editor
@ -103,6 +145,163 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
override fun getDeleteRangeAndType2(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
isChange: Boolean,
operatorArguments: OperatorArguments,
): Pair<TextRange, SelectionType>? {
val range = getMotionRange2(
(editor as IjVimEditor).editor,
(caret as IjVimCaret).caret,
(context as IjEditorExecutionContext).context,
argument,
operatorArguments
)
?: return null
// Delete motion commands that are not linewise become linewise if all the following are true:
// 1) The range is across multiple lines
// 2) There is only whitespace before the start of the range
// 3) There is only whitespace after the end of the range
var type: SelectionType
type = if (argument.motion.isLinewiseMotion()) {
SelectionType.LINE_WISE
} else {
SelectionType.CHARACTER_WISE
}
val motion = argument.motion
if (!isChange && !motion.isLinewiseMotion()) {
val start = editor.offsetToBufferPosition(range.startOffset)
val end = editor.offsetToBufferPosition(range.endOffset)
if (start.line != end.line) {
val offset1 = range.startOffset
if (!editor.anyNonWhitespace(offset1, -1)) {
val offset = range.endOffset
if (!editor.anyNonWhitespace(offset, 1)) {
type = SelectionType.LINE_WISE
}
}
}
}
return Pair(range, type)
}
/**
* Toggles the case of count characters
*
* @param editor The editor to change
* @param caret The caret on which the operation is performed
* @param count The number of characters to change
* @return true if able to change count characters
*/
override fun changeCaseToggleCharacter(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
val allowWrap = injector.options(editor).whichwrap.contains("~")
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
if (motion is Motion.Error) return false
changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
motion = injector.motion.getHorizontalMotion(
editor,
caret,
count,
false,
allowWrap
) // same but without allow end because we can change till end, but can't move caret there
if (motion is AbsoluteOffset) {
caret.moveToOffset(editor.normalizeOffset(motion.offset, false))
}
return true
}
override fun blockInsert(
editor: VimEditor,
context: ExecutionContext,
range: TextRange,
append: Boolean,
operatorArguments: OperatorArguments,
): Boolean {
val lines = getLinesCountInVisualBlock(editor, range)
val startPosition = editor.offsetToBufferPosition(range.startOffset)
val mode = operatorArguments.mode
val visualBlockMode = mode is VISUAL && mode.selectionType === SelectionType.BLOCK_WISE
for (caret in editor.carets()) {
val line = startPosition.line
var column = startPosition.column
if (!visualBlockMode) {
column = 0
} else if (append) {
column += range.maxLength
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
column = VimMotionGroupBase.LAST_COLUMN
}
}
val lineLength = editor.lineLength(line)
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column)
val offset = editor.getLineEndOffset(line)
insertText(editor, caret, offset, pad)
}
if (visualBlockMode || !append) {
(caret as IjVimCaret).caret.moveToInlayAwareLogicalPosition(LogicalPosition(line, column))
}
if (visualBlockMode) {
setInsertRepeat(lines, column, append)
}
}
if (visualBlockMode || !append) {
insertBeforeCursor(editor, context)
} else {
insertAfterCursor(editor, context)
}
return true
}
/**
* Changes the case of all the characters in the range
*
* @param editor The editor to change
* @param caret The caret to be moved
* @param range The range to change
* @param type The case change type (TOGGLE, UPPER, LOWER)
* @return true if able to delete the text, false if not
*/
override fun changeCaseRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: Char): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
for (i in ends.indices.reversed()) {
changeCase(editor, caret, starts[i], ends[i], type)
}
caret.moveToOffset(range.startOffset)
return true
}
/**
* This performs the actual case change.
*
* @param editor The editor to change
* @param start The start offset to change
* @param end The end offset to change
* @param type The type of change (TOGGLE, UPPER, LOWER)
*/
private fun changeCase(editor: VimEditor, caret: VimCaret, start: Int, end: Int, type: Char) {
var start = start
var end = end
if (start > end) {
val t = end
end = start
start = t
}
end = editor.normalizeOffset(end, true)
val chars = editor.text()
val sb = StringBuilder()
for (i in start until end) {
sb.append(changeCase(chars[i], type))
}
replaceText(editor, caret, start, end, sb.toString())
}
private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) { private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) {
if (caret != editor.primaryCaret()) { if (caret != editor.primaryCaret()) {
(editor as IjVimEditor).editor.caretModel.addCaret( (editor as IjVimEditor).editor.caretModel.addCaret(
@ -111,13 +310,88 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
override fun reformatCode(editor: VimEditor, start: Int, end: Int) { /**
* Changes the case of all the character moved over by the motion argument.
*
* @param editor The editor to change
* @param caret The caret on which motion pretends to be performed
* @param context The data context
* @param type The case change type (TOGGLE, UPPER, LOWER)
* @param argument The motion command
* @return true if able to delete the text, false if not
*/
override fun changeCaseMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext?,
type: Char,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
val range = injector.motion.getMotionRange(
editor, caret, context!!, argument,
operatorArguments
)
return range != null && changeCaseRange(editor, caret, range, type)
}
override fun reformatCodeMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
val range = injector.motion.getMotionRange(
editor, caret, context, argument,
operatorArguments
)
return range != null && reformatCodeRange(editor, caret, range)
}
override fun reformatCodeSelection(editor: VimEditor, caret: VimCaret, range: VimSelection) {
val textRange = range.toVimTextRange(true)
reformatCodeRange(editor, caret, textRange)
}
private fun reformatCodeRange(editor: VimEditor, caret: VimCaret, range: TextRange): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
val firstLine = editor.offsetToBufferPosition(range.startOffset).line
for (i in ends.indices.reversed()) {
val startOffset = editor.getLineStartForOffset(starts[i])
val offset = ends[i] - if (startOffset == ends[i]) 0 else 1
val endOffset = editor.getLineEndForOffset(offset)
reformatCode(editor, startOffset, endOffset)
}
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
return true
}
private fun reformatCode(editor: VimEditor, start: Int, end: Int) {
val project = (editor as IjVimEditor).editor.project ?: return val project = (editor as IjVimEditor).editor.project ?: return
val file = PsiUtilBase.getPsiFileInEditor(editor.editor, project) ?: return val file = PsiUtilBase.getPsiFileInEditor(editor.editor, project) ?: return
val textRange = com.intellij.openapi.util.TextRange.create(start, end) val textRange = com.intellij.openapi.util.TextRange.create(start, end)
CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange)) CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange))
} }
override fun autoIndentMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
operatorArguments: OperatorArguments,
) {
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
if (range != null) {
autoIndentRange(
editor, caret, context,
TextRange(range.startOffset, range.endOffsetInclusive)
)
}
}
override fun autoIndentRange( override fun autoIndentRange(
editor: VimEditor, editor: VimEditor,
caret: VimCaret, caret: VimCaret,
@ -172,14 +446,361 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
@Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.add", imports = ["import com.maddyhome.idea.vim.api.injector"])) override fun indentLines(
fun addInsertListener(listener: VimInsertListener) { editor: VimEditor,
injector.listenersNotifier.modeChangeListeners.add(listener) caret: VimCaret,
context: ExecutionContext,
lines: Int,
dir: Int,
operatorArguments: OperatorArguments,
) {
val start = caret.offset
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
}
override fun indentMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
dir: Int,
operatorArguments: OperatorArguments,
) {
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
if (range != null) {
indentRange(editor, caret, context, range, 1, dir, operatorArguments)
}
}
override fun indentRange(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
range: TextRange,
count: Int,
dir: Int,
operatorArguments: OperatorArguments,
) {
if (logger.isDebugEnabled) {
logger.debug("count=$count")
}
// Remember the current caret column
val intendedColumn = caret.vimLastColumn
val indentConfig = create((editor as IjVimEditor).editor)
val sline = editor.offsetToBufferPosition(range.startOffset).line
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
.toInt() else endLogicalPosition.line
if (range.isMultiple) {
val from = editor.offsetToBufferPosition(range.startOffset).column
if (dir == 1) {
// Right shift blockwise selection
val indent = indentConfig.createIndentByCount(count)
for (l in sline..eline) {
val len = editor.lineLength(l)
if (len > from) {
val spos = BufferPosition(l, from, false)
insertText(editor, caret, spos, indent)
}
}
} else {
// Left shift blockwise selection
val chars = editor.text()
for (l in sline..eline) {
val len = editor.lineLength(l)
if (len > from) {
val spos = BufferPosition(l, from, false)
val epos = BufferPosition(l, from + indentConfig.getTotalIndent(count) - 1, false)
val wsoff = editor.bufferPositionToOffset(spos)
val weoff = editor.bufferPositionToOffset(epos)
var pos: Int
pos = wsoff
while (pos <= weoff) {
if (charType(editor, chars[pos], false) !== CharacterHelper.CharacterType.WHITESPACE) {
break
}
pos++
}
if (pos > wsoff) {
deleteText(editor, TextRange(wsoff, pos), null, caret, operatorArguments, true)
}
}
}
}
} else {
// Shift non-blockwise selection
for (l in sline..eline) {
val soff = editor.getLineStartOffset(l)
val eoff = editor.getLineEndOffset(l, true)
val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l)
val col = editor.offsetToBufferPosition(woff).column
val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble())
.toInt()
if (col > 0 || soff != eoff) {
val indent = indentConfig.createIndentBySize(limit)
replaceText(editor, caret, soff, woff, indent)
}
}
}
if (!editor.editor.inInsertMode) {
if (!range.isMultiple) {
// The caret has moved, so reset the intended column before trying to get the expected offset
val newCaret = caret.setVimLastColumnAndGetCaret(intendedColumn)
val offset = injector.motion.moveCaretToLineWithStartOfLineOption(editor, sline, caret)
newCaret.moveToOffset(offset)
} else {
caret.moveToOffset(range.startOffset)
}
}
}
/**
* Sort range of text with a given comparator
*
* @param editor The editor to replace text in
* @param range The range to sort
* @param lineComparator The comparator to use to sort
* @param sortOptions The option to sort the range
* @return true if able to sort the text, false if not
*/
override fun sortRange(
editor: VimEditor, caret: VimCaret, range: LineRange, lineComparator: Comparator<String>,
sortOptions: SortOption,
): Boolean {
val startLine = range.startLine
val endLine = range.endLine
val count = range.size
if (count < 2) {
return false
}
val startOffset = editor.getLineStartOffset(startLine)
val endOffset = editor.getLineEndOffset(endLine)
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(startOffset, endOffset))
val lines = selectedText.split("\n")
val modifiedLines = sortOptions.pattern?.let {
if (sortOptions.sortOnPattern) {
extractPatternFromLines(editor, lines, startLine, it)
} else {
deletePatternFromLines(editor, lines, startLine, it)
}
} ?: lines
val sortedLines = lines.zip(modifiedLines)
.sortedWith { l1, l2 -> lineComparator.compare(l1.second, l2.second) }
.map {it.first}
.toMutableList()
if (sortOptions.unique) {
val iterator = sortedLines.iterator()
var previous: String? = null
while (iterator.hasNext()) {
val current = iterator.next()
if (current == previous || sortOptions.ignoreCase && current.equals(previous, ignoreCase = true)) {
iterator.remove()
} else {
previous = current
}
}
}
if (sortedLines.isEmpty()) {
return false
}
replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n"))
return true
}
private fun extractPatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> result.value
is VimMatchResult.Failure -> line
}
}
}
private fun deletePatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> line.substring(result.value.length, line.length)
is VimMatchResult.Failure -> line
}
}
}
/**
* Perform increment and decrement for numbers in visual mode
*
*
* Flag [avalanche] marks if increment (or decrement) should be performed in avalanche mode
* (for v_g_Ctrl-A and v_g_Ctrl-X commands)
*
* @return true
*/
override fun changeNumberVisualMode(
editor: VimEditor,
caret: VimCaret,
selectedRange: TextRange,
count: Int,
avalanche: Boolean,
): Boolean {
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val numberRanges = findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
val newNumbers: MutableList<String?> = ArrayList()
for (i in numberRanges.indices) {
val numberRange = numberRanges[i]
val iCount = if (avalanche) (i + 1) * count else count
val newNumber = changeNumberInRange(editor, numberRange, iCount, alpha, hex, octal)
newNumbers.add(newNumber)
}
for (i in newNumbers.indices.reversed()) {
// Replace text bottom up. In other direction ranges will be desynchronized after inc numbers like 99
val (first) = numberRanges[i]
val newNumber = newNumbers[i]
replaceText(editor, caret, first.startOffset, first.endOffset, newNumber!!)
}
(caret as IjVimCaret).caret.moveToInlayAwareOffset(selectedRange.startOffset)
return true
}
override fun changeNumber(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val range = findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
if (range == null) {
logger.debug("no number on line")
return false
}
val newNumber = changeNumberInRange(editor, range, count, alpha, hex, octal)
return if (newNumber == null) {
false
} else {
replaceText(editor, caret, range.first.startOffset, range.first.endOffset, newNumber)
caret.caret.moveToInlayAwareOffset(range.first.startOffset + newNumber.length - 1)
true
}
}
override fun reset() {
strokes.clear()
repeatCharsCount = 0
if (lastStrokes != null) {
lastStrokes!!.clear()
}
}
override fun saveStrokes(newStrokes: String?) {
val chars = newStrokes!!.toCharArray()
strokes.add(chars)
}
private fun changeNumberInRange(
editor: VimEditor,
range: Pair<TextRange, NumberType>,
count: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): String? {
val text = editor.getText(range.first)
val numberType = range.second
if (logger.isDebugEnabled) {
logger.debug("found range $range")
logger.debug("text=$text")
}
var number = text
if (text.isEmpty()) {
return null
}
var ch = text[0]
if (hex && NumberType.HEX == numberType) {
if (!text.lowercase(Locale.getDefault()).startsWith(HEX_START)) {
throw RuntimeException("Hex number should start with 0x: $text")
}
for (i in text.length - 1 downTo 2) {
val index = "abcdefABCDEF".indexOf(text[i])
if (index >= 0) {
lastLower = index < 6
break
}
}
var num = BigInteger(text.substring(2), 16)
num = num.add(BigInteger.valueOf(count.toLong()))
if (num.compareTo(BigInteger.ZERO) < 0) {
num = BigInteger(MAX_HEX_INTEGER, 16).add(BigInteger.ONE).add(num)
}
number = num.toString(16)
number = number.padStart(text.length - 2, '0')
if (!lastLower) {
number = number.uppercase(Locale.getDefault())
}
number = text.substring(0, 2) + number
} else if (octal && NumberType.OCT == numberType && text.length > 1) {
if (!text.startsWith("0")) throw RuntimeException("Oct number should start with 0: $text")
var num = BigInteger(text, 8).add(BigInteger.valueOf(count.toLong()))
if (num.compareTo(BigInteger.ZERO) < 0) {
num = BigInteger("1777777777777777777777", 8).add(BigInteger.ONE).add(num)
}
number = num.toString(8)
number = "0" + number.padStart(text.length - 1, '0')
} else if (alpha && NumberType.ALPHA == numberType) {
if (!Character.isLetter(ch)) throw RuntimeException("Not alpha number : $text")
ch += count.toChar().code
if (Character.isLetter(ch)) {
number = ch.toString()
}
} else if (NumberType.DEC == numberType) {
if (ch != '-' && !Character.isDigit(ch)) throw RuntimeException("Not dec number : $text")
var pad = ch == '0'
var len = text.length
if (ch == '-' && text[1] == '0') {
pad = true
len--
}
var num = BigInteger(text)
num = num.add(BigInteger.valueOf(count.toLong()))
number = num.toString()
if (!octal && pad) {
var neg = false
if (number[0] == '-') {
neg = true
number = number.substring(1)
}
number = number.padStart(len, '0')
if (neg) {
number = "-$number"
}
}
}
return number
}
fun addInsertListener(listener: VimInsertListener) {
insertListeners.add(listener)
} }
@Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.remove", imports = ["import com.maddyhome.idea.vim.api.injector"]))
fun removeInsertListener(listener: VimInsertListener) { fun removeInsertListener(listener: VimInsertListener) {
injector.listenersNotifier.modeChangeListeners.remove(listener) insertListeners.remove(listener)
}
override fun notifyListeners(editor: VimEditor) {
insertListeners.forEach(Consumer { listener: VimInsertListener -> listener.insertModeStarted((editor as IjVimEditor).editor) })
}
@TestOnly
override fun resetRepeat() {
setInsertRepeat(0, 0, false)
} }
private companion object { private companion object {

View File

@ -8,7 +8,9 @@
package com.maddyhome.idea.vim.group; package com.maddyhome.idea.vim.group;
import com.intellij.execution.impl.ConsoleViewImpl;
import com.intellij.find.EditorSearchSession; import com.intellij.find.EditorSearchSession;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.client.ClientAppSession; import com.intellij.openapi.client.ClientAppSession;
import com.intellij.openapi.client.ClientKind; import com.intellij.openapi.client.ClientKind;
import com.intellij.openapi.client.ClientSessionsManager; import com.intellij.openapi.client.ClientSessionsManager;
@ -21,10 +23,12 @@ import com.intellij.openapi.editor.event.CaretListener;
import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx; import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.ex.ExOutputModel; import com.maddyhome.idea.vim.ex.ExOutputModel;
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt; import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt;
import com.maddyhome.idea.vim.helper.CommandStateHelper;
import com.maddyhome.idea.vim.helper.EditorHelper; import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.UserDataManager; import com.maddyhome.idea.vim.helper.UserDataManager;
import com.maddyhome.idea.vim.newapi.IjVimDocument; import com.maddyhome.idea.vim.newapi.IjVimDocument;
@ -214,9 +218,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE); editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE);
} }
if (injector.getApplication().isUnitTest()) { updateCaretsVisualAttributes(new IjVimEditor(editor));
updateCaretsVisualAttributes(new IjVimEditor(editor));
}
} }
public void editorDeinit(@NotNull Editor editor) { public void editorDeinit(@NotNull Editor editor) {
@ -238,8 +240,9 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
VimPlugin.getNotifications(project).notifyAboutIdeaJoin(editor); VimPlugin.getNotifications(project).notifyAboutIdeaJoin(editor);
} }
@Nullable
@Override @Override
public @Nullable Element getState() { public Element getState() {
Element element = new Element("editor"); Element element = new Element("editor");
saveData(element); saveData(element);
return element; return element;
@ -315,16 +318,18 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@NotNull
@Override @Override
public @NotNull Collection<VimEditor> getEditors() { public Collection<VimEditor> getEditors() {
return getLocalEditors() return getLocalEditors()
.filter(UserDataManager::getVimInitialised) .filter(UserDataManager::getVimInitialised)
.map(IjVimEditor::new) .map(IjVimEditor::new)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@NotNull
@Override @Override
public @NotNull Collection<VimEditor> getEditors(@NotNull VimDocument buffer) { public Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument(); final Document document = ((IjVimDocument)buffer).getDocument();
return getLocalEditors() return getLocalEditors()
.filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document)) .filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
@ -344,7 +349,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// events such as document change (to update search highlights), and these can come from CWM guests, and we'd get // events such as document change (to update search highlights), and these can come from CWM guests, and we'd get
// the remote editors. // the remote editors.
// This invocation will always get local editors, regardless of the current context. // This invocation will always get local editors, regardless of the current context.
List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.LOCAL); List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.ALL);
if (!appSessions.isEmpty()) { if (!appSessions.isEmpty()) {
ClientAppSession localSession = appSessions.get(0); ClientAppSession localSession = appSessions.get(0);
return localSession.getService(ClientEditorManager.class).editors(); return localSession.getService(ClientEditorManager.class).editors();
@ -372,16 +377,12 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// Note that IDE scale is handled by LafManager.lookAndFeelChanged // Note that IDE scale is handled by LafManager.lookAndFeelChanged
VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine(); VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine();
if (activeCommandLine != null) { if (activeCommandLine != null) {
activeCommandLine.close(true, false); injector.getProcessGroup().cancelExEntry(new IjVimEditor(editor), true, false);
} }
ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor); ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor);
if (exOutputModel != null) { if (exOutputModel != null) {
exOutputModel.close(); exOutputModel.close();
} }
VimModalInput modalInput = injector.getModalInput().getCurrentModalInput();
if (modalInput != null) {
modalInput.deactivate(true, false);
}
} }
} }
} }

View File

@ -173,20 +173,20 @@ class FileGroup : VimFileBase() {
/** /**
* Saves specific file in the project. * Saves specific file in the project.
*/ */
override fun saveFile(editor: VimEditor, context: ExecutionContext) { override fun saveFile(context: ExecutionContext) {
val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) { val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) {
injector.nativeActionManager.saveAll injector.nativeActionManager.saveAll
} else { } else {
injector.nativeActionManager.saveCurrent injector.nativeActionManager.saveCurrent
} }
action.execute(editor, context) action.execute(context)
} }
/** /**
* Saves all files in the project. * Saves all files in the project.
*/ */
override fun saveFiles(editor: VimEditor, context: ExecutionContext) { override fun saveFiles(context: ExecutionContext) {
injector.nativeActionManager.saveAll.execute(editor, context) injector.nativeActionManager.saveAll.execute(context)
} }
/** /**

View File

@ -26,7 +26,7 @@ class IjVimPsiService: VimPsiService {
val psiFile = PsiHelper.getFile(editor.ij) ?: return null val psiFile = PsiHelper.getFile(editor.ij) ?: return null
val psiElement = psiFile.findElementAt(pos) ?: return null val psiElement = psiFile.findElementAt(pos) ?: return null
val language = psiElement.language val language = psiElement.language
val commenter = LanguageCommenters.INSTANCE.forLanguage(language) ?: return null val commenter = LanguageCommenters.INSTANCE.forLanguage(language)
val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null
val commentText = psiComment.text val commentText = psiComment.text

View File

@ -47,14 +47,11 @@ import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.listener.AppCodeTemplates import com.maddyhome.idea.vim.listener.AppCodeTemplates
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.returnTo
import org.jetbrains.annotations.Range import org.jetbrains.annotations.Range
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -315,26 +312,13 @@ internal class MotionGroup : VimMotionGroupBase() {
KeyHandler.getInstance().reset(vimEditor) KeyHandler.getInstance().reset(vimEditor)
} }
is Mode.CMD_LINE -> { is Mode.CMD_LINE -> {
val commandLine = injector.commandLine.getActiveCommandLine() ?: return injector.processGroup.cancelExEntry(vimEditor, refocusOwningEditor = false, resetCaret = false)
commandLine.close(refocusOwningEditor = false, resetCaret = false)
ExOutputModel.tryGetInstance(editor)?.close() ExOutputModel.tryGetInstance(editor)?.close()
} }
else -> {} else -> {}
} }
} }
} }
} else {
val state = injector.vimState as VimStateMachineImpl
if (state.mode is Mode.VISUAL) {
val returnTo = state.mode.returnTo
when (returnTo) {
ReturnTo.INSERT -> state.mode = Mode.INSERT
ReturnTo.REPLACE -> state.mode = Mode.REPLACE
null -> state.mode = Mode.NORMAL()
}
}
val keyHandler = KeyHandler.getInstance()
KeyHandler.getInstance().reset(keyHandler.keyHandlerState, state.mode)
} }
} }
} }

View File

@ -62,25 +62,6 @@ internal class NotificationService(private val project: Project?) {
@Suppress("unused") @Suppress("unused")
constructor() : this(null) constructor() : this(null)
fun notifyAboutNewUndo() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
"Undo in IdeaVim now works like in Vim",
"""
Caret movement is no longer a separate undo step, and full insert is undoable in one step.
""".trimIndent(),
NotificationType.INFORMATION,
)
notification.addAction(object : DumbAwareAction("Share Feedback") {
override fun actionPerformed(p0: AnActionEvent) {
BrowserUtil.browse("https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks")
}
})
notification.notify(project)
}
fun notifyAboutIdeaPut() { fun notifyAboutIdeaPut() {
val notification = Notification( val notification = Notification(
IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_ID,
@ -201,8 +182,8 @@ internal class NotificationService(private val project: Project?) {
).notify(project) ).notify(project)
} }
fun notifyActionId(id: String?, candidates: List<String>? = null) { fun notifyActionId(id: String?) {
ActionIdNotifier.notifyActionId(id, project, candidates) ActionIdNotifier.notifyActionId(id, project)
} }
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) { fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
@ -278,31 +259,20 @@ internal class NotificationService(private val project: Project?) {
object ActionIdNotifier { object ActionIdNotifier {
private var notification: Notification? = null private var notification: Notification? = null
private const val NO_ID = "<i>Cannot detect action id</i>"
fun notifyActionId(id: String?, project: Project?, candidates: List<String>? = null) { fun notifyActionId(id: String?, project: Project?) {
notification?.expire() notification?.expire()
val possibleIDs = candidates?.distinct()?.sorted() val content = if (id != null) "Action id: $id" else NO_ID
val content = when { Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).let {
id != null -> "Action ID: <code>$id</code><br><br>" notification = it
possibleIDs.isNullOrEmpty() -> "<i>Cannot detect action ID</i><br><br>"
possibleIDs.size == 1 -> "Possible action ID: <code>${possibleIDs[0]}</code><br><br>"
else -> {
buildString {
append("<p>Multiple possible action IDs. Candidates include:<ul>")
possibleIDs.forEach { append("<li><code>$it</code></li>") }
append("</ul></p>")
}
}
} + "<small>See the ${ActionCenter.getToolwindowName()} tool window for previous IDs</small>"
notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also {
it.whenExpired { notification = null } it.whenExpired { notification = null }
it.setContent(it.content + "<br><br><small>Use ${ActionCenter.getToolwindowName()} to see previous ids</small>")
it.addAction(StopTracking()) it.addAction(StopTracking())
if (id != null || possibleIDs?.size == 1) { if (id != null) it.addAction(CopyActionId(id, project))
it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project))
}
it.notify(project) it.notify(project)
} }

View File

@ -95,6 +95,8 @@ class ProcessGroup : VimProcessGroupBase() {
val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
val output = handler.runProcessWithProgressIndicator(progressIndicator) val output = handler.runProcessWithProgressIndicator(progressIndicator)
lastCommand = command
if (output.isCancelled) { if (output.isCancelled) {
// TODO: Vim will use whatever text has already been written to stdout // TODO: Vim will use whatever text has already been written to stdout
// For whatever reason, we're not getting any here, so just throw an exception // For whatever reason, we're not getting any here, so just throw an exception

View File

@ -14,8 +14,6 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.register.Register; import com.maddyhome.idea.vim.register.Register;
import com.maddyhome.idea.vim.register.VimRegisterGroupBase; import com.maddyhome.idea.vim.register.VimRegisterGroupBase;
import com.maddyhome.idea.vim.state.mode.SelectionType; import com.maddyhome.idea.vim.state.mode.SelectionType;
@ -37,10 +35,6 @@ import java.util.List;
}) })
public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> { public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> {
static {
IjVimInjectorKt.initInjector();
}
private static final Logger logger = Logger.getInstance(RegisterGroup.class); private static final Logger logger = Logger.getInstance(RegisterGroup.class);
public RegisterGroup() { public RegisterGroup() {

View File

@ -25,7 +25,6 @@ import com.maddyhome.idea.vim.mark.Jump
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.initInjector
import org.jdom.Element import org.jdom.Element
@State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)]) @State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
@ -66,7 +65,6 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
} }
override fun loadState(state: Element) { override fun loadState(state: Element) {
initInjector()
val projectElements = state.getChildren("project") val projectElements = state.getChildren("project")
for (projectElement in projectElements) { for (projectElement in projectElements) {
val jumps = mutableListOf<Jump>() val jumps = mutableListOf<Jump>()
@ -91,7 +89,6 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
internal class JumpsListener(val project: Project) : RecentPlacesListener { internal class JumpsListener(val project: Project) : RecentPlacesListener {
override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) { override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) {
initInjector()
if (!injector.globalIjOptions().unifyjumps) return if (!injector.globalIjOptions().unifyjumps) return
val jumpService = injector.jumpService val jumpService = injector.jumpService

View File

@ -46,7 +46,6 @@ import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.put.VimPasteProvider import com.maddyhome.idea.vim.put.VimPasteProvider
import com.maddyhome.idea.vim.put.VimPutBase import com.maddyhome.idea.vim.put.VimPutBase
import com.maddyhome.idea.vim.register.RegisterConstants import com.maddyhome.idea.vim.register.RegisterConstants
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.state.mode.isBlock import com.maddyhome.idea.vim.state.mode.isBlock
import com.maddyhome.idea.vim.state.mode.isChar import com.maddyhome.idea.vim.state.mode.isChar
@ -84,7 +83,7 @@ internal class PutGroup : VimPutBase() {
val editor = (vimEditor as IjVimEditor).editor val editor = (vimEditor as IjVimEditor).editor
val context = vimContext.context as DataContext val context = vimContext.context as DataContext
val carets: MutableMap<Caret, RangeMarker> = mutableMapOf() val carets: MutableMap<Caret, RangeMarker> = mutableMapOf()
if (injector.vimState.mode is Mode.INSERT) { if (editor.isInsertMode) {
val undo = injector.undo val undo = injector.undo
val nanoTime = System.nanoTime() val nanoTime = System.nanoTime()
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) } vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }

View File

@ -16,7 +16,6 @@ import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.key import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.newapi.initInjector
/** /**
* Logs the chain of handlers for esc and enter * Logs the chain of handlers for esc and enter
@ -35,8 +34,6 @@ internal class EditorHandlersChainLogger : ProjectActivity {
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler") private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
override suspend fun execute(project: Project) { override suspend fun execute(project: Project) {
initInjector()
if (!enableOctopus) return if (!enableOctopus) return
val escHandlers = editorHandlers.extensionList val escHandlers = editorHandlers.extensionList

View File

@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
*/ */
internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() { internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(editor, name = actionName, context = context) injector.actionExecutor.executeAction(actionName, context)
injector.scroll.scrollCaretIntoView(editor) injector.scroll.scrollCaretIntoView(editor)
return true return true
} }

View File

@ -19,7 +19,6 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.common.IsReplaceCharListener import com.maddyhome.idea.vim.common.IsReplaceCharListener
import com.maddyhome.idea.vim.common.ModeChangeListener import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
@ -87,7 +86,6 @@ private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance()
private fun Editor.updatePrimaryCaretVisualAttributes() { private fun Editor.updatePrimaryCaretVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled") if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (isIdeaVimDisabledHere) return
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this) caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
@ -101,7 +99,6 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
private fun Editor.updateSecondaryCaretsVisualAttributes() { private fun Editor.updateSecondaryCaretsVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled") if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (isIdeaVimDisabledHere) return
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them // IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this) val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
this.caretModel.allCarets.forEach { this.caretModel.allCarets.forEach {
@ -147,28 +144,24 @@ private object AttributesCache {
@TestOnly @TestOnly
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode() internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener, EditorListener { class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
override fun isReplaceCharChanged(editor: VimEditor) { override fun isReplaceCharChanged(editor: VimEditor) {
updateCaretsVisual(editor) updateCaretsVisual()
} }
override fun modeChanged(editor: VimEditor, oldMode: Mode) { override fun modeChanged(editor: VimEditor, oldMode: Mode) {
updateCaretsVisual(editor) updateCaretsVisual()
} }
override fun focusGained(editor: VimEditor) { private fun updateCaretsVisual() {
updateCaretsVisual(editor) updateAllEditorsCaretsVisual()
}
private fun updateCaretsVisual(editor: VimEditor) {
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
} }
fun updateAllEditorsCaretsVisual() { fun updateAllEditorsCaretsVisual() {
injector.editorGroup.getEditors().forEach { editor -> injector.editorGroup.getEditors().forEach { editor ->
updateCaretsVisual(editor) val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
} }
} }
} }

View File

@ -13,23 +13,23 @@ import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult
import com.intellij.openapi.actionSystem.DataContextWrapper import com.intellij.openapi.actionSystem.DataContextWrapper
import com.intellij.openapi.actionSystem.EmptyAction import com.intellij.openapi.actionSystem.EmptyAction
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.actionSystem.impl.Utils import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
import com.intellij.openapi.progress.util.ProgressIndicatorUtils import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.util.NlsContexts
import com.intellij.util.SlowOperations
import com.maddyhome.idea.vim.RegisterActions import com.maddyhome.idea.vim.RegisterActions
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction import com.maddyhome.idea.vim.api.NativeAction
@ -69,12 +69,6 @@ internal class IjActionExecutor : VimActionExecutor {
* @param context The context to run it in * @param context The context to run it in
*/ */
override fun executeAction(editor: VimEditor?, action: NativeAction, context: ExecutionContext): Boolean { override fun executeAction(editor: VimEditor?, action: NativeAction, context: ExecutionContext): Boolean {
val applicationEx = ApplicationManagerEx.getApplicationEx()
if (ProgressIndicatorUtils.isWriteActionRunningOrPending(applicationEx)) {
// This is needed for VIM-3376 and it should turn into error at soeme moment
thisLogger().warn(RuntimeException("Actions cannot be updated when write-action is running or pending", ))
}
val ijAction = (action as IjNativeAction).action val ijAction = (action as IjNativeAction).action
/** /**
@ -127,15 +121,51 @@ internal class IjActionExecutor : VimActionExecutor {
} }
} }
// This is taken directly from ActionUtil.performActionDumbAwareWithCallbacks
// But with one check removed. With this check some actions (like `:w` doesn't work)
// https://youtrack.jetbrains.com/issue/VIM-2691/File-is-not-saved-on-w
private fun performDumbAwareWithCallbacks(
action: AnAction,
event: AnActionEvent,
performRunnable: Runnable,
) {
val project = event.project
var indexError: IndexNotReadyException? = null
val manager = ActionManagerEx.getInstanceEx()
manager.fireBeforeActionPerformed(action, event)
var result: AnActionResult? = null
try {
SlowOperations.allowSlowOperations(SlowOperations.ACTION_PERFORM).use {
performRunnable.run()
result = AnActionResult.PERFORMED
}
} catch (ex: IndexNotReadyException) {
indexError = ex
result = AnActionResult.failed(ex)
} catch (ex: RuntimeException) {
result = AnActionResult.failed(ex)
throw ex
} catch (ex: Error) {
result = AnActionResult.failed(ex)
throw ex
} finally {
if (result == null) result = AnActionResult.failed(Throwable())
manager.fireAfterActionPerformed(action, event, result!!)
}
if (indexError != null) {
ActionUtil.showDumbModeWarning(project, action, event)
}
}
/** /**
* Execute an action by name * Execute an action by name
* *
* @param name The name of the action to execute * @param name The name of the action to execute
* @param context The context to run it in * @param context The context to run it in
*/ */
override fun executeAction(editor: VimEditor, name: @NonNls String, context: ExecutionContext): Boolean { override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
val action = getAction(name, context) val action = getAction(name, context)
return action != null && executeAction(editor, IjNativeAction(action), context) return action != null && executeAction(null, IjNativeAction(action), context)
} }
private fun getAction(name: String, context: ExecutionContext): AnAction? { private fun getAction(name: String, context: ExecutionContext): AnAction? {
@ -181,8 +211,8 @@ internal class IjActionExecutor : VimActionExecutor {
CommandProcessor.getInstance().executeCommand(editor?.ij?.project, runnable, name, groupId) CommandProcessor.getInstance().executeCommand(editor?.ij?.project, runnable, name, groupId)
} }
override fun executeEsc(editor: VimEditor, context: ExecutionContext): Boolean { override fun executeEsc(context: ExecutionContext): Boolean {
return executeAction(editor, IdeActions.ACTION_EDITOR_ESCAPE, context) return executeAction(IdeActions.ACTION_EDITOR_ESCAPE, context)
} }
override fun executeVimAction( override fun executeVimAction(

View File

@ -14,7 +14,6 @@ import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.actionSystem.EditorActionManager import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.maddyhome.idea.vim.api.EngineEditorHelper import com.maddyhome.idea.vim.api.EngineEditorHelper
import com.maddyhome.idea.vim.api.EngineEditorHelperBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimRangeMarker import com.maddyhome.idea.vim.api.VimRangeMarker
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
@ -23,7 +22,7 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@Service @Service
internal class IjEditorHelper : EngineEditorHelperBase() { internal class IjEditorHelper : EngineEditorHelper {
override fun amountOfInlaysBeforeVisualPosition(editor: VimEditor, pos: VimVisualPosition): Int { override fun amountOfInlaysBeforeVisualPosition(editor: VimEditor, pos: VimVisualPosition): Int {
return (editor as IjVimEditor).editor.amountOfInlaysBeforeVisualPosition( return (editor as IjVimEditor).editor.amountOfInlaysBeforeVisualPosition(
VisualPosition( VisualPosition(
@ -52,6 +51,10 @@ internal class IjEditorHelper : EngineEditorHelperBase() {
return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij) return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij)
} }
override fun pad(editor: VimEditor, line: Int, to: Int): String {
return EditorHelper.pad(editor.ij, line, to)
}
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition { override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
return EditorUtil.inlayAwareOffsetToVisualPosition(editor.ij, offset).vim return EditorUtil.inlayAwareOffsetToVisualPosition(editor.ij, offset).vim
} }

View File

@ -16,15 +16,18 @@ import com.intellij.openapi.editor.Editor
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.CharacterHelper.charType import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import it.unimi.dsi.fastutil.ints.IntComparator import it.unimi.dsi.fastutil.ints.IntComparator
import it.unimi.dsi.fastutil.ints.IntIterator import it.unimi.dsi.fastutil.ints.IntIterator
import it.unimi.dsi.fastutil.ints.IntRBTreeSet import it.unimi.dsi.fastutil.ints.IntRBTreeSet
import it.unimi.dsi.fastutil.ints.IntSortedSet import it.unimi.dsi.fastutil.ints.IntSortedSet
import java.util.*
/** /**
* Check ignorecase and smartcase options to see if a case insensitive search should be performed with the given pattern. * Check ignorecase and smartcase options to see if a case insensitive search should be performed with the given pattern.
@ -94,6 +97,210 @@ fun countWords(
return CountPosition(count, position) return CountPosition(count, position)
} }
fun findNumbersInRange(
editor: Editor,
textRange: TextRange,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): List<Pair<TextRange, NumberType>> {
val result: MutableList<Pair<TextRange, NumberType>> = ArrayList()
for (i in 0 until textRange.size()) {
val startOffset = textRange.startOffsets[i]
val end = textRange.endOffsets[i]
val text: String = editor.vim.getText(startOffset, end)
val textChunks = text.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var chunkStart = 0
for (chunk in textChunks) {
val number = findNumberInText(chunk, 0, alpha, hex, octal)
if (number != null) {
result.add(
Pair(
TextRange(
number.first.startOffset + startOffset + chunkStart,
number.first.endOffset + startOffset + chunkStart
),
number.second
)
)
}
chunkStart += 1 + chunk.length
}
}
return result
}
fun findNumberUnderCursor(
editor: Editor,
caret: Caret,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): Pair<TextRange, NumberType>? {
val lline = caret.logicalPosition.line
val text = IjVimEditor(editor).getLineText(lline).lowercase(Locale.getDefault())
val startLineOffset = IjVimEditor(editor).getLineStartOffset(lline)
val posOnLine = caret.offset - startLineOffset
val numberTextRange = findNumberInText(text, posOnLine, alpha, hex, octal) ?: return null
return Pair(
TextRange(
numberTextRange.first.startOffset + startLineOffset,
numberTextRange.first.endOffset + startLineOffset
),
numberTextRange.second
)
}
/**
* Search for number in given text from start position
*
* @param textInRange - text to search in
* @param startPosOnLine - start offset to search
* @return - text range with number
*/
fun findNumberInText(
textInRange: String,
startPosOnLine: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): Pair<TextRange, NumberType>? {
if (logger.isDebugEnabled) {
logger.debug("text=$textInRange")
}
var pos = startPosOnLine
val lineEndOffset = textInRange.length
while (true) {
// Skip over current whitespace if any
while (pos < lineEndOffset && !isNumberChar(textInRange[pos], alpha, hex, octal, true)) {
pos++
}
if (logger.isDebugEnabled) logger.debug("pos=$pos")
if (pos >= lineEndOffset) {
logger.debug("no number char on line")
return null
}
val isHexChar = "abcdefABCDEF".indexOf(textInRange[pos]) >= 0
if (hex) {
// Ox and OX handling
if (textInRange[pos] == '0' && pos < lineEndOffset - 1 && "xX".indexOf(textInRange[pos + 1]) >= 0) {
pos += 2
} else if ("xX".indexOf(textInRange[pos]) >= 0 && pos > 0 && textInRange[pos - 1] == '0') {
pos++
}
logger.debug("checking hex")
val range = findRange(textInRange, pos, false, true, false, false)
val start = range.first
val end = range.second
// Ox and OX
if (start >= 2 && textInRange.substring(start - 2, start).equals("0x", ignoreCase = true)) {
logger.debug("found hex")
return Pair(TextRange(start - 2, end), NumberType.HEX)
}
if (!isHexChar || alpha) {
break
} else {
pos++
}
} else {
break
}
}
if (octal) {
logger.debug("checking octal")
val range = findRange(textInRange, pos, false, false, true, false)
val start = range.first
val end = range.second
if (end - start == 1 && textInRange[start] == '0') {
return Pair(TextRange(start, end), NumberType.DEC)
}
if (textInRange[start] == '0' && end > start &&
!(start > 0 && isNumberChar(textInRange[start - 1], false, false, false, true))
) {
logger.debug("found octal")
return Pair(TextRange(start, end), NumberType.OCT)
}
}
if (alpha) {
if (logger.isDebugEnabled) logger.debug("checking alpha for " + textInRange[pos])
if (isNumberChar(textInRange[pos], true, false, false, false)) {
if (logger.isDebugEnabled) logger.debug("found alpha at $pos")
return Pair(TextRange(pos, pos + 1), NumberType.ALPHA)
}
}
val range = findRange(textInRange, pos, false, false, false, true)
var start = range.first
val end = range.second
if (start > 0 && textInRange[start - 1] == '-') {
start--
}
return Pair(TextRange(start, end), NumberType.DEC)
}
/**
* Searches for digits block that matches parameters
*/
private fun findRange(
text: String,
pos: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
decimal: Boolean,
): Pair<Int, Int> {
var end = pos
while (end < text.length && isNumberChar(text[end], alpha, hex, octal, decimal || octal)) {
end++
}
var start = pos
while (start >= 0 && isNumberChar(text[start], alpha, hex, octal, decimal || octal)) {
start--
}
if (start < end &&
(start == -1 ||
0 <= start && start < text.length &&
!isNumberChar(text[start], alpha, hex, octal, decimal || octal))
) {
start++
}
if (octal) {
for (i in start until end) {
if (!isNumberChar(text[i], false, false, true, false)) return Pair(0, 0)
}
}
return Pair(start, end)
}
private fun isNumberChar(ch: Char, alpha: Boolean, hex: Boolean, octal: Boolean, decimal: Boolean): Boolean {
return if (alpha && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {
true
} else if (octal && (ch >= '0' && ch <= '7')) {
true
} else if (hex && ((ch >= '0' && ch <= '9') || "abcdefABCDEF".indexOf(ch) >= 0)) {
true
} else {
decimal && (ch >= '0' && ch <= '9')
}
}
/** /**
* Find the word under the cursor or the next word to the right of the cursor on the current line. * Find the word under the cursor or the next word to the right of the cursor on the current line.
* *

View File

@ -16,7 +16,6 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.util.registry.Registry
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
@ -24,8 +23,8 @@ 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.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ChangesListener import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
@ -71,7 +70,6 @@ internal class UndoRedoHelper : UndoRedoBase() {
restoreVisualMode(editor) restoreVisualMode(editor)
} }
} else { } else {
notifyAboutNewUndo(editor.ij.project)
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) { runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
var nextUndoNanoTime = undoManager.getNextUndoNanoTime(fileEditor) var nextUndoNanoTime = undoManager.getNextUndoNanoTime(fileEditor)
val insertInfo = (editor.primaryCaret() as IjVimCaret).getInsertSequenceForTime(nextUndoNanoTime) val insertInfo = (editor.primaryCaret() as IjVimCaret).getInsertSequenceForTime(nextUndoNanoTime)
@ -92,13 +90,6 @@ internal class UndoRedoHelper : UndoRedoBase() {
} }
} }
private fun notifyAboutNewUndo(project: Project?) {
if (VimPlugin.getVimState().isNewUndoNotified) return
VimPlugin.getVimState().isNewUndoNotified = true
VimPlugin.getNotifications(project).notifyAboutNewUndo()
}
private fun hasSelection(editor: VimEditor): Boolean { private fun hasSelection(editor: VimEditor): Boolean {
return editor.primaryCaret().ij.hasSelection() return editor.primaryCaret().ij.hasSelection()
} }

View File

@ -10,16 +10,16 @@ package com.maddyhome.idea.vim.listener
import com.intellij.execution.impl.ConsoleViewImpl import com.intellij.execution.impl.ConsoleViewImpl
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.EditorKind
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
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
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.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.EditorListener import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.inInsertMode import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
/** /**
* This listener is similar to the one we introduce in vim-engine to handle focus change, * This listener is similar to the one we introduce in vim-engine to handle focus change,
@ -29,11 +29,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
*/ */
class IJEditorFocusListener : EditorListener { class IJEditorFocusListener : EditorListener {
override fun focusGained(editor: VimEditor) { override fun focusGained(editor: VimEditor) {
val editorInFocus = KeyHandler.getInstance().editorInFocus
if (editorInFocus != null && editorInFocus.ij == editor.ij) return
KeyHandler.getInstance().editorInFocus = editor
// We add Vim bindings to all opened editors, including editors used as UI controls rather than just project file // We add Vim bindings to all opened editors, including editors used as UI controls rather than just project file
// editors. This includes editors used as part of the UI, such as the VCS commit message, or used as read-only // editors. This includes editors used as part of the UI, such as the VCS commit message, or used as read-only
// viewers for text output, such as log files in run configurations or the Git Console tab. And editors are used for // viewers for text output, such as log files in run configurations or the Git Console tab. And editors are used for
@ -53,21 +48,17 @@ class IJEditorFocusListener : EditorListener {
// not file based, is writable, and not a viewer, but we don't want to treat this as an interactive editor. // not file based, is writable, and not a viewer, but we don't want to treat this as an interactive editor.
// Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need // Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable. // to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
val ijEditor = editor.ij
val switchToInsertMode = Runnable { val switchToInsertMode = Runnable {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor) val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
VimPlugin.getChange().insertBeforeCursor(editor, context) VimPlugin.getChange().insertBeforeCursor(editor, context)
} }
if (!ijEditor.document.isWritable) { val ijEditor = editor.ij
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor) if (!ijEditor.isViewer &&
val mode = injector.vimState.mode !EditorHelper.isFileEditor(ijEditor) &&
when (mode) { ijEditor.document.isWritable &&
is Mode.INSERT -> editor.exitInsertMode(context, OperatorArguments(false, 0, mode)) !ijEditor.inInsertMode && ijEditor.editorKind != EditorKind.DIFF
else -> {} ) {
} switchToInsertMode.run()
} }
ApplicationManager.getApplication().invokeLater { ApplicationManager.getApplication().invokeLater {
if (ijEditor.isDisposed) return@invokeLater if (ijEditor.isDisposed) return@invokeLater

View File

@ -29,7 +29,6 @@ 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.impl.ScrollingModelImpl import com.intellij.openapi.editor.impl.ScrollingModelImpl
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
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
@ -39,7 +38,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.NotificationService import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
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.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inNormalMode import com.maddyhome.idea.vim.state.mode.inNormalMode
@ -62,7 +60,6 @@ internal object IdeaSpecifics {
private var caretOffset = -1 private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null private var completionPrevDocumentOffset: Int? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) { override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
if (VimPlugin.isNotEnabled()) return if (VimPlugin.isNotEnabled()) return
@ -76,24 +73,7 @@ internal object IdeaSpecifics {
if (!isVimAction && injector.globalIjOptions().trackactionids) { if (!isVimAction && injector.globalIjOptions().trackactionids) {
if (action !is NotificationService.ActionIdNotifier.CopyActionId && action !is NotificationService.ActionIdNotifier.StopTracking) { if (action !is NotificationService.ActionIdNotifier.CopyActionId && action !is NotificationService.ActionIdNotifier.StopTracking) {
val id: String? = ActionManager.getInstance().getId(action) ?: (action.shortcutSet as? ProxyShortcutSet)?.actionId val id: String? = ActionManager.getInstance().getId(action) ?: (action.shortcutSet as? ProxyShortcutSet)?.actionId
val candidates = if (id == null) { VimPlugin.getNotifications(event.dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id)
// Some actions are specific to the component they're registered for, and are copies of a global action,
// reusing the action ID and shortcuts (e.g. `NextTab` is different for editor tabs and tool window tabs).
// Unfortunately, ActionManager doesn't know about these "local" actions, so can't return the action ID.
// However, the new "local" action does copy the shortcuts of the global template action, so we can look up
// all actions with matching shortcuts. We might return more action IDs than expected, so this is a list of
// candidates, not a definite match of the action being executed, but the list should include our target
// action. Note that we might return duplicate IDs because the keymap might have multiple shortcuts mapped
// to the same action. The notifier will handle de-duplication and sorting as a presentation detail.
action.shortcutSet.shortcuts.flatMap { KeymapManager.getInstance().activeKeymap.getActionIdList(it) }
}
else {
emptyList()
}
// We can still get empty ID and empty candidates. Notably, for the tool window toggle buttons on the new UI.
// We could filter out action events with `place == ActionPlaces.TOOLWINDOW_TOOLBAR_BAR`
VimPlugin.getNotifications(event.dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id, candidates)
} }
} }
@ -245,13 +225,9 @@ internal object IdeaSpecifics {
//region Find action ID //region Find action ID
internal class FindActionIdAction : DumbAwareToggleAction() { internal class FindActionIdAction : DumbAwareToggleAction() {
override fun isSelected(e: AnActionEvent): Boolean { override fun isSelected(e: AnActionEvent): Boolean = injector.globalIjOptions().trackactionids
initInjector()
return injector.globalIjOptions().trackactionids
}
override fun setSelected(e: AnActionEvent, state: Boolean) { override fun setSelected(e: AnActionEvent, state: Boolean) {
initInjector()
injector.globalIjOptions().trackactionids = !injector.globalIjOptions().trackactionids injector.globalIjOptions().trackactionids = !injector.globalIjOptions().trackactionids
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2003-2024 The IdeaVim authors * Copyright 2003-2023 The IdeaVim authors
* *
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at * license that can be found in the LICENSE.txt file or at
@ -9,18 +9,7 @@
package com.maddyhome.idea.vim.listener package com.maddyhome.idea.vim.listener
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
@Deprecated(message = "Please use ModeChangeListener", replaceWith = ReplaceWith("ModeChangeListener", imports = ["import com.maddyhome.idea.vim.common.ModeChangeListener"]))
interface VimInsertListener : ModeChangeListener {
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
if (editor.mode == Mode.INSERT) {
insertModeStarted(editor.ij)
}
}
interface VimInsertListener {
fun insertModeStarted(editor: Editor) fun insertModeStarted(editor: Editor)
} }

View File

@ -94,8 +94,8 @@ import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.vimDisabled import com.maddyhome.idea.vim.helper.vimDisabled
import com.maddyhome.idea.vim.helper.vimInitialised import com.maddyhome.idea.vim.helper.vimInitialised
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
@ -161,7 +161,6 @@ internal object VimListenerManager {
check(keyCheckRequests.tryEmit(Unit)) check(keyCheckRequests.tryEmit(Unit))
val caretVisualAttributesListener = CaretVisualAttributesListener() val caretVisualAttributesListener = CaretVisualAttributesListener()
injector.listenersNotifier.myEditorListeners.add(caretVisualAttributesListener)
injector.listenersNotifier.modeChangeListeners.add(caretVisualAttributesListener) injector.listenersNotifier.modeChangeListeners.add(caretVisualAttributesListener)
injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener) injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
caretVisualAttributesListener.updateAllEditorsCaretsVisual() caretVisualAttributesListener.updateAllEditorsCaretsVisual()
@ -395,7 +394,6 @@ internal object VimListenerManager {
editor.vim.mode = Mode.NORMAL() editor.vim.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim) KeyHandler.getInstance().reset(editor.vim)
} }
injector.scroll.scrollCaretIntoView(editor.vim)
} }
MotionGroup.fileEditorManagerSelectionChangedCallback(event) MotionGroup.fileEditorManagerSelectionChangedCallback(event)
@ -745,8 +743,10 @@ internal object VimListenerManager {
if (event.area == EditorMouseEventArea.EDITING_AREA) { if (event.area == EditorMouseEventArea.EDITING_AREA) {
val editor = event.editor val editor = event.editor
injector.commandLine.getActiveCommandLine()?.close(refocusOwningEditor = true, resetCaret = false) val commandLine = injector.commandLine.getActiveCommandLine()
injector.modalInput.getCurrentModalInput()?.deactivate(refocusOwningEditor = true, resetCaret = false) if (commandLine != null) {
injector.processGroup.cancelExEntry(editor.vim, refocusOwningEditor = true, resetCaret = false)
}
ExOutputModel.tryGetInstance(editor)?.close() ExOutputModel.tryGetInstance(editor)?.close()
@ -774,8 +774,10 @@ internal object VimListenerManager {
event.area != EditorMouseEventArea.FOLDING_OUTLINE_AREA && event.area != EditorMouseEventArea.FOLDING_OUTLINE_AREA &&
event.mouseEvent.button != MouseEvent.BUTTON3 event.mouseEvent.button != MouseEvent.BUTTON3
) { ) {
injector.commandLine.getActiveCommandLine()?.close(refocusOwningEditor = true, resetCaret = false) val commandLine = injector.commandLine.getActiveCommandLine()
injector.modalInput.getCurrentModalInput()?.deactivate(refocusOwningEditor = true, resetCaret = false) if (commandLine != null) {
injector.processGroup.cancelExEntry(event.editor.vim, refocusOwningEditor = true, resetCaret = false)
}
ExOutputModel.getInstance(event.editor).close() ExOutputModel.getInstance(event.editor).close()
} }

View File

@ -11,13 +11,12 @@ package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.VisualPosition
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction import com.maddyhome.idea.vim.api.NativeAction
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
internal fun NativeAction?.execute(editor: VimEditor, context: ExecutionContext) { internal fun NativeAction?.execute(context: ExecutionContext) {
if (this == null) return if (this == null) return
injector.actionExecutor.executeAction(editor, this, context) injector.actionExecutor.executeAction(null, this, context)
} }
internal val VisualPosition.vim: VimVisualPosition internal val VisualPosition.vim: VimVisualPosition

View File

@ -30,9 +30,7 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimCaretListener import com.maddyhome.idea.vim.api.VimCaretListener
import com.maddyhome.idea.vim.api.VimDocument import com.maddyhome.idea.vim.api.VimDocument
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimEditorBase
import com.maddyhome.idea.vim.api.VimFoldRegion import com.maddyhome.idea.vim.api.VimFoldRegion
import com.maddyhome.idea.vim.api.VimIndentConfig
import com.maddyhome.idea.vim.api.VimScrollingModel import com.maddyhome.idea.vim.api.VimScrollingModel
import com.maddyhome.idea.vim.api.VimSelectionModel import com.maddyhome.idea.vim.api.VimSelectionModel
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
@ -40,7 +38,6 @@ import com.maddyhome.idea.vim.api.VirtualFile
import com.maddyhome.idea.vim.api.injector 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.common.IndentConfig import com.maddyhome.idea.vim.common.IndentConfig
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
import com.maddyhome.idea.vim.common.LiveRange import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.common.ModeChangeListener import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
@ -55,7 +52,6 @@ import com.maddyhome.idea.vim.helper.inExMode
import com.maddyhome.idea.vim.helper.isTemplateActive import com.maddyhome.idea.vim.helper.isTemplateActive
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
import com.maddyhome.idea.vim.helper.vimLastSelectionType import com.maddyhome.idea.vim.helper.vimLastSelectionType
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
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.state.mode.inBlockSelection import com.maddyhome.idea.vim.state.mode.inBlockSelection
@ -63,7 +59,7 @@ import org.jetbrains.annotations.ApiStatus
import java.lang.System.identityHashCode import java.lang.System.identityHashCode
@ApiStatus.Internal @ApiStatus.Internal
internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase() { internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
companion object { companion object {
// For cases where Editor does not have a project (for some reason) // For cases where Editor does not have a project (for some reason)
// It's something IJ Platform related and stored here because of this reason // It's something IJ Platform related and stored here because of this reason
@ -76,22 +72,12 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
val editor = editor.getTopLevelEditor() val editor = editor.getTopLevelEditor()
val originalEditor = editor val originalEditor = editor
override fun updateMode(mode: Mode) {
(injector.vimState as VimStateMachineImpl).mode = mode
}
override fun updateIsReplaceCharacter(isReplaceCharacter: Boolean) {
(injector.vimState as VimStateMachineImpl).isReplaceCharacter = isReplaceCharacter
}
override val lfMakesNewLine: Boolean = true override val lfMakesNewLine: Boolean = true
override var vimChangeActionSwitchMode: Mode? override var vimChangeActionSwitchMode: Mode?
get() = editor.vimChangeActionSwitchMode get() = editor.vimChangeActionSwitchMode
set(value) { set(value) {
editor.vimChangeActionSwitchMode = value editor.vimChangeActionSwitchMode = value
} }
override val indentConfig: VimIndentConfig
get() = create(editor)
override fun fileSize(): Long = editor.fileSize.toLong() override fun fileSize(): Long = editor.fileSize.toLong()
@ -133,7 +119,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
} }
override fun insertText(caret: VimCaret, atPosition: Int, text: CharSequence) { override fun insertText(caret: VimCaret, atPosition: Int, text: CharSequence) {
if (injector.vimState.mode is Mode.INSERT) { if (editor.isInsertMode) {
injector.undo.startInsertSequence(caret, atPosition, System.nanoTime()) injector.undo.startInsertSequence(caret, atPosition, System.nanoTime())
} }
editor.document.insertString(atPosition, text) editor.document.insertString(atPosition, text)
@ -464,7 +450,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
// This is faster than simply calling Editor#logicalToVisualPosition // This is faster than simply calling Editor#logicalToVisualPosition
return editor.offsetToVisualLine(editor.document.getLineStartOffset(line)) return editor.offsetToVisualLine(editor.document.getLineStartOffset(line))
} }
return super<MutableLinearEditor>.bufferLineToVisualLine(line) return super.bufferLineToVisualLine(line)
} }
override var insertMode: Boolean override var insertMode: Boolean

View File

@ -22,7 +22,6 @@ import com.maddyhome.idea.vim.api.VimApplication
import com.maddyhome.idea.vim.api.VimChangeGroup import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimClipboardManager import com.maddyhome.idea.vim.api.VimClipboardManager
import com.maddyhome.idea.vim.api.VimCommandGroup import com.maddyhome.idea.vim.api.VimCommandGroup
import com.maddyhome.idea.vim.api.VimCommandLine
import com.maddyhome.idea.vim.api.VimCommandLineService import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimDigraphGroup import com.maddyhome.idea.vim.api.VimDigraphGroup
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -37,7 +36,6 @@ import com.maddyhome.idea.vim.api.VimKeyGroup
import com.maddyhome.idea.vim.api.VimLookupManager import com.maddyhome.idea.vim.api.VimLookupManager
import com.maddyhome.idea.vim.api.VimMarkService import com.maddyhome.idea.vim.api.VimMarkService
import com.maddyhome.idea.vim.api.VimMessages import com.maddyhome.idea.vim.api.VimMessages
import com.maddyhome.idea.vim.api.VimModalInputService
import com.maddyhome.idea.vim.api.VimMotionGroup import com.maddyhome.idea.vim.api.VimMotionGroup
import com.maddyhome.idea.vim.api.VimOptionGroup import com.maddyhome.idea.vim.api.VimOptionGroup
import com.maddyhome.idea.vim.api.VimOutputPanelService import com.maddyhome.idea.vim.api.VimOutputPanelService
@ -58,9 +56,8 @@ import com.maddyhome.idea.vim.api.VimrcFileState
import com.maddyhome.idea.vim.api.VimscriptExecutor import com.maddyhome.idea.vim.api.VimscriptExecutor
import com.maddyhome.idea.vim.api.VimscriptFunctionService import com.maddyhome.idea.vim.api.VimscriptFunctionService
import com.maddyhome.idea.vim.api.VimscriptParser import com.maddyhome.idea.vim.api.VimscriptParser
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.isInjectorInitialized
import com.maddyhome.idea.vim.diagnostic.VimLogger import com.maddyhome.idea.vim.diagnostic.VimLogger
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar import com.maddyhome.idea.vim.extension.VimExtensionRegistrar
import com.maddyhome.idea.vim.group.CommandGroup import com.maddyhome.idea.vim.group.CommandGroup
import com.maddyhome.idea.vim.group.EditorGroup import com.maddyhome.idea.vim.group.EditorGroup
@ -86,7 +83,6 @@ import com.maddyhome.idea.vim.put.VimPut
import com.maddyhome.idea.vim.register.VimRegisterGroup import com.maddyhome.idea.vim.register.VimRegisterGroup
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.ui.VimRcFileState import com.maddyhome.idea.vim.ui.VimRcFileState
import com.maddyhome.idea.vim.ui.ex.ExEntryPanelService
import com.maddyhome.idea.vim.undo.VimUndoRedo import com.maddyhome.idea.vim.undo.VimUndoRedo
import com.maddyhome.idea.vim.vimscript.Executor import com.maddyhome.idea.vim.vimscript.Executor
import com.maddyhome.idea.vim.vimscript.services.VariableService import com.maddyhome.idea.vim.vimscript.services.VariableService
@ -94,16 +90,6 @@ import com.maddyhome.idea.vim.yank.VimYankGroup
import com.maddyhome.idea.vim.yank.YankGroupBase import com.maddyhome.idea.vim.yank.YankGroupBase
import javax.swing.JTextArea import javax.swing.JTextArea
/**
* Currently, injector has to be initialized in all "entry points" from the IJ platform.
* This means Project Activities, listeners, status bar widgets, statistic collectors, etc.
* This is a bad pattern and we need to find a solution where the plugin doesn't have an "uninitialized state"
*/
internal fun initInjector() {
if (isInjectorInitialized()) return
injector = IjVimInjector()
}
internal class IjVimInjector : VimInjectorBase() { internal class IjVimInjector : VimInjectorBase() {
override fun <T : Any> getLogger(clazz: Class<T>): VimLogger = IjVimLogger(Logger.getInstance(clazz)) override fun <T : Any> getLogger(clazz: Class<T>): VimLogger = IjVimLogger(Logger.getInstance(clazz))
@ -197,8 +183,6 @@ internal class IjVimInjector : VimInjectorBase() {
get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
override val commandLine: VimCommandLineService override val commandLine: VimCommandLineService
get() = service() get() = service()
override val modalInput: VimModalInputService
get() = commandLine as ExEntryPanelService
override val outputPanel: VimOutputPanelService override val outputPanel: VimOutputPanelService
get() = service() get() = service()

View File

@ -97,6 +97,59 @@ open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Ele
updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, true) updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, true)
} }
override fun confirmChoice(
editor: VimEditor,
context: ExecutionContext,
match: String,
caret: VimCaret,
startOffset: Int,
): ReplaceConfirmationChoice {
val result: Ref<ReplaceConfirmationChoice> = Ref.create(ReplaceConfirmationChoice.QUIT)
val keyStrokeProcessor: Function1<KeyStroke, Boolean> = label@{ key: KeyStroke ->
val choice: ReplaceConfirmationChoice
val c = key.keyChar
choice = if (key.isCloseKeyStroke() || c == 'q') {
ReplaceConfirmationChoice.QUIT
} else if (c == 'y') {
ReplaceConfirmationChoice.SUBSTITUTE_THIS
} else if (c == 'l') {
ReplaceConfirmationChoice.SUBSTITUTE_LAST
} else if (c == 'n') {
ReplaceConfirmationChoice.SKIP
} else if (c == 'a') {
ReplaceConfirmationChoice.SUBSTITUTE_ALL
} else {
return@label true
}
// TODO: Handle <C-E> and <C-Y>
result.set(choice)
false
}
if (ApplicationManager.getApplication().isUnitTestMode) {
caret.moveToOffset(startOffset)
val inputModel = getInstance(editor.ij)
var key = inputModel.nextKeyStroke()
while (key != null) {
if (!keyStrokeProcessor.invoke(key)) {
break
}
key = inputModel.nextKeyStroke()
}
} else {
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
val exEntryPanel = injector.commandLine.createWithoutShortcuts(
editor,
context,
MessageHelper.message("replace.with.0", match),
"",
)
caret.moveToOffset(startOffset)
ModalEntry.activate(editor, keyStrokeProcessor)
exEntryPanel.deactivate(refocusOwningEditor = true, resetCaret = false)
}
return result.get()
}
override fun addSubstitutionConfirmationHighlight( override fun addSubstitutionConfirmationHighlight(
editor: VimEditor, editor: VimEditor,
startOffset: Int, startOffset: Int,

View File

@ -19,16 +19,11 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.group.IjOptions import com.maddyhome.idea.vim.group.IjOptions
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.options.OptionAccessScope import com.maddyhome.idea.vim.options.OptionAccessScope
import com.maddyhome.idea.vim.options.OptionConstants import com.maddyhome.idea.vim.options.OptionConstants
internal class OptionsState : ApplicationUsagesCollector() { internal class OptionsState : ApplicationUsagesCollector() {
init {
initInjector()
}
override fun getGroup(): EventLogGroup = GROUP override fun getGroup(): EventLogGroup = GROUP
override fun getMetrics(): Set<MetricEvent> { override fun getMetrics(): Set<MetricEvent> {

View File

@ -13,16 +13,11 @@ import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.eventLog.events.VarargEventId import com.intellij.internal.statistic.eventLog.events.VarargEventId
import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.statistic.PluginState.Util.extensionNames import com.maddyhome.idea.vim.statistic.PluginState.Util.extensionNames
import com.maddyhome.idea.vim.vimscript.services.VimRcService import com.maddyhome.idea.vim.vimscript.services.VimRcService
internal class VimscriptState : ApplicationUsagesCollector() { internal class VimscriptState : ApplicationUsagesCollector() {
init {
initInjector()
}
override fun getGroup(): EventLogGroup = GROUP override fun getGroup(): EventLogGroup = GROUP
override fun getMetrics(): Set<MetricEvent> { override fun getMetrics(): Set<MetricEvent> {

View File

@ -44,7 +44,7 @@ internal class Troubleshooter {
} }
companion object { companion object {
fun getInstance() = service<Troubleshooter>() val instance = service<Troubleshooter>()
} }
} }

View File

@ -19,7 +19,6 @@ import com.maddyhome.idea.vim.KeyHandler;
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;
import com.maddyhome.idea.vim.api.VimEditor; import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimOutputPanel;
import com.maddyhome.idea.vim.diagnostic.VimLogger; import com.maddyhome.idea.vim.diagnostic.VimLogger;
import com.maddyhome.idea.vim.helper.MessageHelper; import com.maddyhome.idea.vim.helper.MessageHelper;
import com.maddyhome.idea.vim.helper.UiHelper; import com.maddyhome.idea.vim.helper.UiHelper;
@ -47,11 +46,12 @@ import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
public class ExOutputPanel extends JPanel { public class ExOutputPanel extends JPanel {
private final @NotNull Editor myEditor; private final @NotNull Editor myEditor;
public final @NotNull JLabel myLabel = new JLabel("more"); private final @NotNull JLabel myLabel = new JLabel("more");
private final @NotNull JTextArea myText = new JTextArea(); private final @NotNull JTextArea myText = new JTextArea();
private final @NotNull JScrollPane myScrollPane = private final @NotNull JScrollPane myScrollPane =
new JBScrollPane(myText, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); new JBScrollPane(myText, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
private final @NotNull ComponentAdapter myAdapter; private final @NotNull ComponentAdapter myAdapter;
private boolean myAtEnd = false;
private int myLineHeight = 0; private int myLineHeight = 0;
private @Nullable JComponent myOldGlass = null; private @Nullable JComponent myOldGlass = null;
@ -224,31 +224,42 @@ public class ExOutputPanel extends JPanel {
myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText())); myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
} }
public void scrollLine() { private void scrollLine() {
scrollOffset(myLineHeight); scrollOffset(myLineHeight);
} }
public void scrollPage() { private void scrollPage() {
scrollOffset(myScrollPane.getVerticalScrollBar().getVisibleAmount()); scrollOffset(myScrollPane.getVerticalScrollBar().getVisibleAmount());
} }
public void scrollHalfPage() { private void scrollHalfPage() {
double sa = myScrollPane.getVerticalScrollBar().getVisibleAmount() / 2.0; double sa = myScrollPane.getVerticalScrollBar().getVisibleAmount() / 2.0;
double offset = Math.ceil(sa / myLineHeight) * myLineHeight; double offset = Math.ceil(sa / myLineHeight) * myLineHeight;
scrollOffset((int)offset); scrollOffset((int)offset);
} }
public void onBadKey() { private void handleEnter() {
if (myAtEnd) {
close();
}
else {
scrollLine();
}
}
private void badKey() {
myLabel.setText(MessageHelper.message("more.ret.line.space.page.d.half.page.q.quit")); myLabel.setText(MessageHelper.message("more.ret.line.space.page.d.half.page.q.quit"));
myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText())); myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
} }
private void scrollOffset(int more) { private void scrollOffset(int more) {
myAtEnd = false;
int val = myScrollPane.getVerticalScrollBar().getValue(); int val = myScrollPane.getVerticalScrollBar().getValue();
myScrollPane.getVerticalScrollBar().setValue(val + more); myScrollPane.getVerticalScrollBar().setValue(val + more);
myScrollPane.getHorizontalScrollBar().setValue(0); myScrollPane.getHorizontalScrollBar().setValue(0);
if (val + more >= if (val + more >=
myScrollPane.getVerticalScrollBar().getMaximum() - myScrollPane.getVerticalScrollBar().getVisibleAmount()) { myScrollPane.getVerticalScrollBar().getMaximum() - myScrollPane.getVerticalScrollBar().getVisibleAmount()) {
myAtEnd = true;
myLabel.setText(MessageHelper.message("hit.enter.or.type.command.to.continue")); myLabel.setText(MessageHelper.message("hit.enter.or.type.command.to.continue"));
} }
else { else {
@ -257,11 +268,6 @@ public class ExOutputPanel extends JPanel {
myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText())); myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
} }
public boolean isAtEnd() {
int val = myScrollPane.getVerticalScrollBar().getValue();
return val >= myScrollPane.getVerticalScrollBar().getMaximum() - myScrollPane.getVerticalScrollBar().getVisibleAmount();
}
private void positionPanel() { private void positionPanel() {
final JComponent contentComponent = myEditor.getContentComponent(); final JComponent contentComponent = myEditor.getContentComponent();
Container scroll = SwingUtilities.getAncestorOfClass(JScrollPane.class, contentComponent); Container scroll = SwingUtilities.getAncestorOfClass(JScrollPane.class, contentComponent);
@ -302,13 +308,14 @@ public class ExOutputPanel extends JPanel {
close(null); close(null);
} }
public void close(final @Nullable KeyStroke key) { private void close(final @Nullable KeyEvent e) {
ApplicationManager.getApplication().invokeLater(() -> { ApplicationManager.getApplication().invokeLater(() -> {
deactivate(true); deactivate(true);
final Project project = myEditor.getProject(); final Project project = myEditor.getProject();
if (project != null && key != null && key.getKeyChar() != '\n') { if (project != null && e != null && e.getKeyChar() != '\n') {
final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
final List<KeyStroke> keys = new ArrayList<>(1); final List<KeyStroke> keys = new ArrayList<>(1);
keys.add(key); keys.add(key);
if (LOG.isTrace()) { if (LOG.isTrace()) {
@ -317,9 +324,7 @@ public class ExOutputPanel extends JPanel {
} }
KeyHandler.getInstance().getKeyStack().addKeys(keys); KeyHandler.getInstance().getKeyStack().addKeys(keys);
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(myEditor)); ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(myEditor));
injector.getApplication().runWriteAction(() -> { VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1); VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
return null;
});
} }
}); });
} }
@ -336,14 +341,40 @@ public class ExOutputPanel extends JPanel {
*/ */
@Override @Override
public void keyTyped(@NotNull KeyEvent e) { public void keyTyped(@NotNull KeyEvent e) {
VimOutputPanel currentPanel = injector.getOutputPanel().getCurrentOutputPanel(); if (myExOutputPanel.myAtEnd) {
if (currentPanel == null) return; myExOutputPanel.close(e);
}
int keyCode = e.getKeyCode(); else {
Character keyChar = e.getKeyChar(); switch (e.getKeyChar()) {
int modifiers = e.getModifiersEx(); case ' ':
KeyStroke keyStroke = (keyChar == KeyEvent.CHAR_UNDEFINED) ? KeyStroke.getKeyStroke(keyCode, modifiers) : KeyStroke.getKeyStroke(keyChar, modifiers); myExOutputPanel.scrollPage();
currentPanel.handleKey(keyStroke); break;
case 'd':
myExOutputPanel.scrollHalfPage();
break;
case 'q':
case '\u001b':
myExOutputPanel.close();
break;
case '\n':
myExOutputPanel.handleEnter();
break;
case KeyEvent.CHAR_UNDEFINED: {
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
myExOutputPanel.handleEnter();
break;
case KeyEvent.VK_ESCAPE:
myExOutputPanel.close();
break;
default:
myExOutputPanel.badKey();
}
}
default:
myExOutputPanel.badKey();
}
}
} }
} }

View File

@ -150,7 +150,7 @@ internal class ReloadVimRc : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(PlatformDataKeys.EDITOR) ?: return val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
injector.keyGroup.removeKeyMapping(MappingOwner.IdeaVim.InitScript) injector.keyGroup.removeKeyMapping(MappingOwner.IdeaVim.InitScript)
Troubleshooter.getInstance().removeByType("old-action-notation-in-mappings") Troubleshooter.instance.removeByType("old-action-notation-in-mappings")
// Reload the ideavimrc in the context of the current window, as though we had called `:source ~/.ideavimrc` // Reload the ideavimrc in the context of the current window, as though we had called `:source ~/.ideavimrc`
executeIdeaVimRc(editor.vim) executeIdeaVimRc(editor.vim)

View File

@ -20,6 +20,7 @@ import com.intellij.openapi.wm.WindowManager
import com.intellij.openapi.wm.impl.status.EditorBasedWidget import com.intellij.openapi.wm.impl.status.EditorBasedWidget
import com.intellij.util.Consumer import com.intellij.util.Consumer
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
@ -27,7 +28,6 @@ import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.helper.EngineStringHelper import com.maddyhome.idea.vim.helper.EngineStringHelper
import com.maddyhome.idea.vim.helper.VimNlsSafe import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.Component import java.awt.Component
@ -72,11 +72,6 @@ internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
} }
internal class ShowCmdStatusBarWidgetFactory : StatusBarWidgetFactory/*, LightEditCompatible*/ { internal class ShowCmdStatusBarWidgetFactory : StatusBarWidgetFactory/*, LightEditCompatible*/ {
init {
initInjector()
}
override fun getId() = ShowCmd.ID override fun getId() = ShowCmd.ID
override fun getDisplayName(): String = ShowCmd.displayName override fun getDisplayName(): String = ShowCmd.displayName
@ -86,6 +81,7 @@ internal class ShowCmdStatusBarWidgetFactory : StatusBarWidgetFactory/*, LightEd
} }
override fun isAvailable(project: Project): Boolean { override fun isAvailable(project: Project): Boolean {
VimPlugin.getInstance()
return injector.globalOptions().showcmd return injector.globalOptions().showcmd
} }

View File

@ -267,7 +267,7 @@ private object ShortcutConflictsSettings : DumbAwareAction(MessageHelper.message
} }
internal object JoinEap : DumbAwareAction()/*, LightEditCompatible*/ { internal object JoinEap : DumbAwareAction()/*, LightEditCompatible*/ {
const val EAP_LINK = "https://plugins.jetbrains.com/plugins/eap/ideavim" private const val EAP_LINK = "https://plugins.jetbrains.com/plugins/eap/ideavim"
fun eapActive() = EAP_LINK in UpdateSettings.getInstance().storedPluginHosts fun eapActive() = EAP_LINK in UpdateSettings.getInstance().storedPluginHosts

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.ui.ex
import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.ActionEvent import java.awt.event.ActionEvent
@ -120,8 +119,7 @@ internal object ExEditorKit : DefaultEditorKit() {
val c = key.keyChar val c = key.keyChar
if (c.code > 0) { if (c.code > 0) {
if (target.useHandleKeyFromEx) { if (target.useHandleKeyFromEx) {
val panel = ((injector.commandLine.getActiveCommandLine() as? ExEntryPanel) ?: (injector.modalInput.getCurrentModalInput() as? WrappedAsModalInputExEntryPanel)?.exEntryPanel) ?: return val entry = ExEntryPanel.getInstance().entry
val entry = panel.entry
val editor = entry.editor val editor = entry.editor
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor!!.vim, key, entry.context.vim, keyHandler.keyHandlerState) keyHandler.handleKey(editor!!.vim, key, entry.context.vim, keyHandler.keyHandlerState)

View File

@ -23,11 +23,12 @@ import com.maddyhome.idea.vim.EventFacade;
import com.maddyhome.idea.vim.KeyHandler; import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction; import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.VimCommandLine;
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
import com.maddyhome.idea.vim.ex.ranges.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.SearchHighlightsHelper; import com.maddyhome.idea.vim.helper.SearchHighlightsHelper;
import com.maddyhome.idea.vim.helper.UiHelper; import com.maddyhome.idea.vim.helper.UiHelper;
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;
@ -35,8 +36,6 @@ 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;
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser; import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -63,10 +62,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
private boolean isReplaceMode = false; private boolean isReplaceMode = false;
private WeakReference<Editor> weakEditor = null; private WeakReference<Editor> weakEditor = null;
private VimInputInterceptor myInputInterceptor = null;
public Function1<String, Unit> inputProcessing = null;
public Character finishOn = null;
private ExEntryPanel(boolean enableShortcuts) { private ExEntryPanel(boolean enableShortcuts) {
label = new JLabel(" "); label = new JLabel(" ");
entry = new ExTextField(); entry = new ExTextField();
@ -84,13 +79,15 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
layout.setConstraints(entry, gbc); layout.setConstraints(entry, gbc);
add(entry); add(entry);
// This does not need to be unregistered, it's registered as a custom UI property on this if (enableShortcuts) {
EventFacade.getInstance().registerCustomShortcutSet( // This does not need to be unregistered, it's registered as a custom UI property on this
VimShortcutKeyAction.getInstance(), EventFacade.getInstance().registerCustomShortcutSet(
toShortcutSet(((VimKeyGroupBase) injector.getKeyGroup()).getRequiredShortcutKeys()), VimShortcutKeyAction.getInstance(),
entry toShortcutSet(((VimKeyGroupBase) injector.getKeyGroup()).getRequiredShortcutKeys()),
); entry
new ExShortcutKeyAction(this).registerCustomShortcutSet(); );
new ExShortcutKeyAction(this).registerCustomShortcutSet();
}
updateUI(); updateUI();
} }
@ -130,18 +127,10 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
} }
} }
public @Nullable Editor getIjEditor() { public @Nullable Editor getEditor() {
return weakEditor != null ? weakEditor.get() : null; return weakEditor != null ? weakEditor.get() : null;
} }
public @NotNull VimEditor getEditor() {
Editor editor = getIjEditor();
if (editor == null) {
throw new RuntimeException("Editor was disposed for active command line");
}
return new IjVimEditor(editor);
}
public void setEditor(@Nullable Editor editor) { public void setEditor(@Nullable Editor editor) {
if (editor == null) { if (editor == null) {
weakEditor = null; weakEditor = null;
@ -150,14 +139,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
} }
} }
/**
* @deprecated use {@link ExEntryPanel#activate(Editor, DataContext, String, String)}
*/
@Deprecated(forRemoval = true)
public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) {
activate(editor, context, label, initText);
}
/** /**
* Turns on the ex entry field for the given editor * Turns on the ex entry field for the given editor
* *
@ -268,9 +249,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
// We have this in the end, because `entry.deactivate()` communicates with active panel during deactivation // We have this in the end, because `entry.deactivate()` communicates with active panel during deactivation
active = false; active = false;
finishOn = null;
myInputInterceptor = null;
inputProcessing = null;
} }
private void reset() { private void reset() {
@ -295,7 +273,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
@Override @Override
protected void textChanged(@NotNull DocumentEvent e) { protected void textChanged(@NotNull DocumentEvent e) {
String text = entry.getText(); String text = entry.getText();
Font newFont = UiHelper.selectEditorFont(getIjEditor(), text); Font newFont = UiHelper.selectEditorFont(getEditor(), text);
if (newFont != entry.getFont()) { if (newFont != entry.getFont()) {
entry.setFont(newFont); entry.setFont(newFont);
} }
@ -414,14 +392,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
return active; return active;
} }
/**
* @deprecated Use getVisibleText()
*/
@Deprecated(forRemoval = true)
public @NotNull String getText() {
return entry.getText();
}
@Override @Override
public @NotNull String getVisibleText() { public @NotNull String getVisibleText() {
return entry.getText(); return entry.getText();
@ -439,10 +409,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
@Override @Override
public void handleKey(@NotNull KeyStroke stroke) { public void handleKey(@NotNull KeyStroke stroke) {
entry.handleKey(stroke); entry.handleKey(stroke);
if (finishOn != null && stroke.getKeyChar() == finishOn && inputProcessing != null) {
inputProcessing.invoke(getActualText());
close(true, true);
}
} }
// Called automatically when the LAF is changed and the component is visible, and manually by the LAF listener handler // Called automatically when the LAF is changed and the component is visible, and manually by the LAF listener handler
@ -480,8 +446,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
} }
private void setFontForElements() { private void setFontForElements() {
label.setFont(UiHelper.selectEditorFont(getIjEditor(), label.getText())); label.setFont(UiHelper.selectEditorFont(getEditor(), label.getText()));
entry.setFont(UiHelper.selectEditorFont(getIjEditor(), getVisibleText())); entry.setFont(UiHelper.selectEditorFont(getEditor(), getVisibleText()));
} }
private void positionPanel() { private void positionPanel() {
@ -569,32 +535,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
IdeFocusManager.findInstance().requestFocus(entry, true); IdeFocusManager.findInstance().requestFocus(entry, true);
} }
@Nullable
public VimInputInterceptor<?> getInputInterceptor() {
return myInputInterceptor;
}
@Override
public void insertText(int offset, @NotNull String string) {
VimCommandLine.super.insertText(offset, string);
}
public void setInputInterceptor(@Nullable VimInputInterceptor<?> vimInputInterceptor) {
myInputInterceptor = vimInputInterceptor;
}
@Nullable
@Override
public Function1<String, Unit> getInputProcessing() {
return inputProcessing;
}
@Nullable
@Override
public Character getFinishOn() {
return finishOn;
}
public static class LafListener implements LafManagerListener { public static class LafListener implements LafManagerListener {
@Override @Override
public void lookAndFeelChanged(@NotNull LafManager source) { public void lookAndFeelChanged(@NotNull LafManager source) {

View File

@ -11,33 +11,25 @@ import com.intellij.openapi.application.ApplicationManager
import com.maddyhome.idea.vim.action.change.Extension import com.maddyhome.idea.vim.action.change.Extension
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCommandLine import com.maddyhome.idea.vim.api.VimCommandLine
import com.maddyhome.idea.vim.api.VimCommandLineCaret import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimCommandLineServiceBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimModalInput
import com.maddyhome.idea.vim.api.VimModalInputBase
import com.maddyhome.idea.vim.api.VimModalInputService
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.TestInputModel import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.inRepeatMode import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.helper.isCloseKeyStroke import com.maddyhome.idea.vim.helper.isCloseKeyStroke
import com.maddyhome.idea.vim.key.interceptors.VimInputInterceptor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
class ExEntryPanelService : VimCommandLineServiceBase(), VimModalInputService { class ExEntryPanelService : VimCommandLineService {
override fun getActiveCommandLine(): VimCommandLine? { override fun getActiveCommandLine(): VimCommandLine? {
val instance = ExEntryPanel.instance ?: return null val instance = ExEntryPanel.instance ?: ExEntryPanel.instanceWithoutShortcuts ?: return null
return if (instance.isActive) instance else null return if (instance.isActive) instance else null
} }
@Deprecated("Please use readInputAndProcess") override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
val editor = vimEditor.ij val editor = vimEditor.ij
if (vimEditor.inRepeatMode) { if (vimEditor.inRepeatMode) {
val input = Extension.consumeString() val input = Extension.consumeString()
@ -66,7 +58,7 @@ class ExEntryPanelService : VimCommandLineServiceBase(), VimModalInputService {
} else { } else {
var text: String? = null var text: String? = null
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input() // XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
val commandLine = injector.commandLine.createSearchPrompt(vimEditor, context, prompt.ifEmpty { " " }, "") val commandLine = injector.commandLine.create(vimEditor, context, prompt.ifEmpty { " " }, "")
ModalEntry.activate(editor.vim) { key: KeyStroke -> ModalEntry.activate(editor.vim) { key: KeyStroke ->
return@activate when { return@activate when {
key.isCloseKeyStroke() -> { key.isCloseKeyStroke() -> {
@ -97,40 +89,7 @@ class ExEntryPanelService : VimCommandLineServiceBase(), VimModalInputService {
} }
} }
override fun readInputAndProcess( override fun create(editor: VimEditor, context: ExecutionContext, label: String, initText: String): VimCommandLine {
editor: VimEditor,
context: ExecutionContext,
prompt: String,
finishOn: Char?,
processing: (String) -> Unit,
) {
val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) {
"Cannot enable cmd mode from current mode $currentMode"
}
// Make sure the Visual selection marks are up to date before we use them.
injector.markService.setVisualSelectionMarks(editor)
// Note that we should remove selection and reset caret offset before we switch back to Normal mode and then enter
// Command-line mode. However, some IdeaVim commands can handle multiple carets, including multiple carets with
// selection (which might or might not be a block selection). Unfortunately, because we're just entering
// Command-line mode, we don't know which command is going to be entered, so we can't remove selection here.
// Therefore, we switch to Normal and then Command-line even though we might still have a Visual selection...
// On the plus side, it means we still show selection while editing the command line, which Vim also does
// (Normal then Command-line is not strictly necessary, but done for completeness and autocmd)
// Caret selection is finally handled in Command.execute
editor.mode = Mode.NORMAL()
editor.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel as ExEntryPanel
panel.finishOn = finishOn
panel.inputProcessing = processing
panel.activate(editor.ij, context.ij, prompt, "")
}
override fun createPanel(editor: VimEditor, context: ExecutionContext, label: String, initText: String): VimCommandLine {
val panel = ExEntryPanel.getInstance() val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, label, initText) panel.activate(editor.ij, context.ij, label, initText)
return panel return panel
@ -145,31 +104,4 @@ class ExEntryPanelService : VimCommandLineServiceBase(), VimModalInputService {
override fun fullReset() { override fun fullReset() {
ExEntryPanel.fullReset() ExEntryPanel.fullReset()
} }
override fun getCurrentModalInput(): VimModalInput? {
return ExEntryPanel.getInstanceWithoutShortcuts()?.takeIf { it.isActive && it.inputInterceptor != null }?.let { WrappedAsModalInputExEntryPanel(it) }
}
override fun create(editor: VimEditor, context: ExecutionContext, label: String, inputInterceptor: VimInputInterceptor<*>): VimModalInput {
val panel = ExEntryPanel.getInstanceWithoutShortcuts()
panel.inputInterceptor = inputInterceptor
panel.activate(editor.ij, context.ij, label, "")
return WrappedAsModalInputExEntryPanel(panel)
}
}
internal class WrappedAsModalInputExEntryPanel(internal val exEntryPanel: ExEntryPanel) : VimModalInputBase() {
override var inputInterceptor: VimInputInterceptor<*>
get() = exEntryPanel.inputInterceptor!!
set(value) { exEntryPanel.inputInterceptor = value }
override val caret: VimCommandLineCaret = exEntryPanel.caret
override val label: String = exEntryPanel.label
override fun deactivate(refocusOwningEditor: Boolean, resetCaret: Boolean) {
exEntryPanel.deactivate(refocusOwningEditor, resetCaret)
}
override fun focus() {
exEntryPanel.focus()
}
} }

View File

@ -16,7 +16,6 @@ import com.intellij.util.ui.JBUI;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.VimCommandLine; import com.maddyhome.idea.vim.api.VimCommandLine;
import com.maddyhome.idea.vim.api.VimCommandLineCaret; import com.maddyhome.idea.vim.api.VimCommandLineCaret;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.helper.UiHelper; import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.history.HistoryConstants; import com.maddyhome.idea.vim.history.HistoryConstants;
import com.maddyhome.idea.vim.history.HistoryEntry; import com.maddyhome.idea.vim.history.HistoryEntry;
@ -212,7 +211,7 @@ public class ExTextField extends JTextField {
} }
public @Nullable Editor getEditor() { public @Nullable Editor getEditor() {
return ExEntryPanel.getInstance().getIjEditor(); return ExEntryPanel.getInstance().getEditor();
} }
public DataContext getContext() { public DataContext getContext() {
@ -292,9 +291,9 @@ public class ExTextField extends JTextField {
*/ */
void cancel() { void cancel() {
clearCurrentAction(); clearCurrentAction();
VimCommandLine commandLine = injector.getCommandLine().getActiveCommandLine(); Editor editor = ExEntryPanel.instance.getEditor();
if (commandLine != null) { if (editor != null) {
commandLine.close(true, true); VimPlugin.getProcess().cancelExEntry(new IjVimEditor(editor), true, true);
} }
} }

View File

@ -352,8 +352,7 @@ class ModeWidgetPopup : AnAction() {
enum class ModeWidgetTheme(private var value: String) { enum class ModeWidgetTheme(private var value: String) {
TERM("Term"), TERM("Term"),
COLORLESS("Colorless"), COLORLESS("Colorless");
DRACULA("Dracula");
override fun toString(): String { override fun toString(): String {
return value return value

View File

@ -31,16 +31,6 @@ fun getModeBackground(mode: Mode?): Color {
is Mode.OP_PENDING, null -> UIUtil.getPanelBackground() is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
} }
} }
ModeWidgetTheme.DRACULA -> {
return when (mode) {
Mode.INSERT -> Color.decode("#50FA7B")
Mode.REPLACE -> Color.decode("#FF5555")
is Mode.NORMAL -> Color.decode("#BD93F9")
is Mode.CMD_LINE -> Color.decode("#FFB86C")
is Mode.VISUAL, is Mode.SELECT -> Color.decode("#F1FA8C")
is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
}
}
ModeWidgetTheme.COLORLESS -> { ModeWidgetTheme.COLORLESS -> {
return UIUtil.getPanelBackground() return UIUtil.getPanelBackground()
} }
@ -93,7 +83,6 @@ fun getModeForeground(mode: Mode?): Color {
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme() val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
return when (theme) { return when (theme) {
ModeWidgetTheme.TERM -> if (isLight) Color.WHITE else Color.BLACK ModeWidgetTheme.TERM -> if (isLight) Color.WHITE else Color.BLACK
ModeWidgetTheme.DRACULA -> Color.BLACK
ModeWidgetTheme.COLORLESS -> UIUtil.getLabelForeground() ModeWidgetTheme.COLORLESS -> UIUtil.getLabelForeground()
} }
} else { } else {

View File

@ -39,7 +39,7 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
argument.forEach { c -> argument.forEach { c ->
when { when {
!inBackslash && c == '!' -> { !inBackslash && c == '!' -> {
val last = lastCommand val last = VimPlugin.getProcess().lastCommand
if (last.isNullOrEmpty()) { if (last.isNullOrEmpty()) {
VimPlugin.showMessage(MessageHelper.message("e_noprev")) VimPlugin.showMessage(MessageHelper.message("e_noprev"))
return ExecutionResult.Error return ExecutionResult.Error
@ -76,7 +76,6 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
VimPlugin.getProcess().executeCommand(editor, command, null, workingDirectory)?.let { VimPlugin.getProcess().executeCommand(editor, command, null, workingDirectory)?.let {
ExOutputModel.getInstance(editor.ij).output(it) ExOutputModel.getInstance(editor.ij).output(it)
} }
lastCommand = command
ExecutionResult.Success ExecutionResult.Success
} else { } else {
// Filter // Filter
@ -93,7 +92,6 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
} }
} }
} }
lastCommand = command
ExecutionResult.Success ExecutionResult.Success
} }
} catch (e: ProcessCanceledException) { } catch (e: ProcessCanceledException) {
@ -105,6 +103,5 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
companion object { companion object {
private val logger = Logger.getInstance(CmdFilterCommand::class.java.name) private val logger = Logger.getInstance(CmdFilterCommand::class.java.name)
private var lastCommand: String? = null
} }
} }

View File

@ -100,8 +100,8 @@ action.not.found.0=Action not found: {0}
action.CustomizeModeWidget.text=Mode Widget Settings action.CustomizeModeWidget.text=Mode Widget Settings
action.VimFindActionIdAction.text=IdeaVim: Track Action IDs action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
action.VimFindActionIdAction.description=Starts tracking IDs of executed actions action.VimFindActionIdAction.description=Starts tracking ids of executed actions
ex.show.all.actions.0.1=--- Actions ---{0}{1} ex.show.all.actions.0.1=--- Actions ---{0}{1}

View File

@ -62,7 +62,7 @@ class ChangeActionTest : VimTestCase() {
listOf("i", "<C-O>", "v", "<esc>"), listOf("i", "<C-O>", "v", "<esc>"),
"12${c}345", "12${c}345",
"12${c}345", "12${c}345",
Mode.INSERT, Mode.INSERT,
) )
} }
@ -73,7 +73,7 @@ class ChangeActionTest : VimTestCase() {
listOf("i", "<C-O>", "v", "d"), listOf("i", "<C-O>", "v", "d"),
"12${c}345", "12${c}345",
"12${c}45", "12${c}45",
Mode.INSERT, Mode.INSERT,
) )
} }
@ -109,7 +109,7 @@ class ChangeActionTest : VimTestCase() {
listOf("i", "<C-O>", "gh", "<esc>"), listOf("i", "<C-O>", "gh", "<esc>"),
"12${c}345", "12${c}345",
"123${c}45", "123${c}45",
Mode.INSERT, Mode.INSERT,
) )
} }
@ -122,7 +122,7 @@ class ChangeActionTest : VimTestCase() {
listOf("i", "<C-O>", "gh", "d"), listOf("i", "<C-O>", "gh", "d"),
"12${c}345", "12${c}345",
"12d${c}45", "12d${c}45",
Mode.INSERT, Mode.INSERT,
) )
} }
@ -133,7 +133,7 @@ class ChangeActionTest : VimTestCase() {
listOf("i", "def", "<C-O>", "d2h", "x"), listOf("i", "def", "<C-O>", "d2h", "x"),
"abc$c.\n", "abc$c.\n",
"abcdx.\n", "abcdx.\n",
Mode.INSERT, Mode.INSERT,
) )
} }
@ -222,7 +222,7 @@ class ChangeActionTest : VimTestCase() {
four four
""".trimIndent(), """.trimIndent(),
Mode.INSERT, Mode.INSERT,
) )
} }
@ -405,7 +405,7 @@ class ChangeActionTest : VimTestCase() {
listOf("A", ", ", "<C-R>", "a", "!"), listOf("A", ", ", "<C-R>", "a", "!"),
"${c}Hello\n", "${c}Hello\n",
"Hello, World!\n", "Hello, World!\n",
Mode.INSERT, Mode.INSERT,
) )
} }
@ -416,7 +416,7 @@ class ChangeActionTest : VimTestCase() {
listOf("O", "bar"), listOf("O", "bar"),
"fo${c}o\n", "fo${c}o\n",
"bar\nfoo\n", "bar\nfoo\n",
Mode.INSERT, Mode.INSERT,
) )
} }

View File

@ -738,7 +738,7 @@ class MotionActionTest : VimTestCase() {
@Test @Test
fun testDeleteToEndOfSearchResultInclusive() { fun testDeleteToEndOfSearchResultInclusive() {
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit" val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
val after = "Lorem $c amet, consectetur adipiscing elit" val after = "Lorem ${c} amet, consectetur adipiscing elit"
doTest("d/sit/e<CR>", before, after) doTest("d/sit/e<CR>", before, after)
} }
@ -766,11 +766,6 @@ class MotionActionTest : VimTestCase() {
doTest(keys, before, after, Mode.NORMAL()) doTest(keys, before, after, Mode.NORMAL())
} }
@Test
fun testDeleteToLiteral() {
doTest("d/<C-V>333<CR>", "ab${c}cdōef", "abōef")
}
@Test @Test
fun testDeleteBackwardsToSearchResult() { fun testDeleteBackwardsToSearchResult() {
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit" val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"

View File

@ -9,11 +9,8 @@
package org.jetbrains.plugins.ideavim.action.change package org.jetbrains.plugins.ideavim.action.change
import com.intellij.idea.TestFor import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class UndoActionTest : VimTestCase() { class UndoActionTest : VimTestCase() {
@ -112,7 +109,6 @@ class UndoActionTest : VimTestCase() {
@Test @Test
@TestFor(issues = ["VIM-547"]) @TestFor(issues = ["VIM-547"])
fun `test typed text requires one undo`() { fun `test typed text requires one undo`() {
assumeTrue(!injector.globalIjOptions().oldundo)
configureByText("Lorem ipsu${c}m") configureByText("Lorem ipsu${c}m")
typeText("a dolor sit amet,<CR>consectetur adipiscing elit<Esc>") typeText("a dolor sit amet,<CR>consectetur adipiscing elit<Esc>")
assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit") assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit")
@ -123,7 +119,6 @@ class UndoActionTest : VimTestCase() {
@Test @Test
@TestFor(issues = ["VIM-547"]) @TestFor(issues = ["VIM-547"])
fun `test breaking insert sequence`() { fun `test breaking insert sequence`() {
assumeTrue(!injector.globalIjOptions().oldundo)
configureByText("Lorem ipsu${c}m") configureByText("Lorem ipsu${c}m")
typeText("a dolor sit amet,<CR>consectetur <C-G>uadipiscing elit<Esc>") typeText("a dolor sit amet,<CR>consectetur <C-G>uadipiscing elit<Esc>")
assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit") assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit")
@ -134,7 +129,6 @@ class UndoActionTest : VimTestCase() {
@Test @Test
@TestFor(issues = ["VIM-547"]) @TestFor(issues = ["VIM-547"])
fun `test moving caret breaks insert sequence`() { fun `test moving caret breaks insert sequence`() {
assumeTrue(!injector.globalIjOptions().oldundo)
configureByText("Lorem ipsu${c}m") configureByText("Lorem ipsu${c}m")
typeText("a dolor sit amet,<CR>consectetur <Left>adipiscing elit<Esc>") typeText("a dolor sit amet,<CR>consectetur <Left>adipiscing elit<Esc>")
assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit ") assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit ")
@ -146,17 +140,7 @@ class UndoActionTest : VimTestCase() {
@TestFor(issues = ["VIM-547"]) @TestFor(issues = ["VIM-547"])
fun `test change action is included in insert sequence`() { fun `test change action is included in insert sequence`() {
configureByText("Lorem ${c}ipsum dolor sit amet") configureByText("Lorem ${c}ipsum dolor sit amet")
typeText("celorem<Esc>") typeText("celorem")
assertState("Lorem lore${c}m dolor sit amet")
typeText("u")
assertState("Lorem ${c}ipsum dolor sit amet")
}
@Test
@TestFor(issues = ["VIM-3552"])
fun `test paste is not part of undo sequence`() {
configureByText("test")
typeText("yypoxxxu")
configureByText("Lorem lorem${c} dolor sit amet") configureByText("Lorem lorem${c} dolor sit amet")
typeText("u") typeText("u")
configureByText("Lorem ${c}ipsum dolor sit amet") configureByText("Lorem ${c}ipsum dolor sit amet")

View File

@ -174,8 +174,8 @@ class PutTestAfterCursorActionTest : VimTestCase() {
override fun collectTransferableData( override fun collectTransferableData(
file: PsiFile, file: PsiFile,
editor: Editor, editor: Editor,
startOffsets: IntArray, startOffsets: IntArray?,
endOffsets: IntArray, endOffsets: IntArray?,
): List<TextBlockTransferableData> { ): List<TextBlockTransferableData> {
return emptyList() return emptyList()
} }

View File

@ -18,6 +18,7 @@ import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.annotations.TestWithoutPrimaryClipboard import org.jetbrains.plugins.ideavim.annotations.TestWithoutPrimaryClipboard
import org.junit.Ignore
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -260,9 +261,9 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
assertState(after) assertState(after)
} }
@Disabled @Suppress("unused")
@Test @Ignore
fun `test put visual block visual line mode`() { fun `ingoretest put visual block visual line mode`() {
val before = """ val before = """
qw${c}e qw${c}e
asd asd
@ -314,6 +315,8 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
assertState(after) assertState(after)
} }
@Suppress("unused")
@Ignore
@Test @Test
@Disabled @Disabled
fun `test put visual text multicaret`() { fun `test put visual text multicaret`() {

View File

@ -1,29 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action.motion.updown
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class MotionUpCtrlPAction : VimTestCase() {
@Test
@Disabled("This one should be fixed")
fun `test last column empty`() {
val keys = "o<Esc><End><C-P>"
val before = """
${c}Lorem Ipsumm dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.
""".trimIndent()
val after = """
Lorem Ipsumm dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat${c}.
""".trimIndent()
doTest(keys, before, after)
}
}

View File

@ -298,15 +298,6 @@ class MapCommandTest : VimTestCase() {
assertState("I found it in ${c}a legendary land") assertState("I found it in ${c}a legendary land")
} }
@Test
@TestFor(issues = ["VIM-3569"])
fun `test bar in mapping`() {
configureByText("${c}I found it in a legendary land")
typeText(commandToKeys("nmap <leader>\\| dw"))
typeText("<leader>|")
assertState("${c}found it in a legendary land")
}
// VIM-676 |:map| // VIM-676 |:map|
@TestWithoutNeovim(reason = SkipNeovimReason.VIM_SCRIPT) @TestWithoutNeovim(reason = SkipNeovimReason.VIM_SCRIPT)
@Test @Test

View File

@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.extension.replacewithregister package org.jetbrains.plugins.ideavim.extension.replacewithregister
import com.intellij.testFramework.UsefulTestCase.assertContainsElements
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@ -23,7 +24,6 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo import org.junit.jupiter.api.TestInfo
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue
class ReplaceWithRegisterTest : VimTestCase() { class ReplaceWithRegisterTest : VimTestCase() {
@ -525,7 +525,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
fun `test multiple carets`() { fun `test multiple carets`() {
// Behaviour of pasting a full line with multiple carets is undefined in Vim and has different implementation in // Behaviour of pasting a full line with multiple carets is undefined in Vim and has different implementation in
// IdeaVim depending on if ideaput is specified in 'clipboard' or not // IdeaVim depending on if ideaput is specified in 'clipboard' or not
assertTrue(OptionConstants.clipboard_ideaput in optionsNoEditor().clipboard) assertContainsElements(optionsNoEditor().clipboard, OptionConstants.clipboard_ideaput)
enableExtensions("multiple-cursors") enableExtensions("multiple-cursors")
val text = """ val text = """

View File

@ -321,9 +321,8 @@ class CaretVisualAttributesHelperTest : VimTestCase() {
""".trimMargin(), """.trimMargin(),
) )
injector.actionExecutor.executeAction( injector.actionExecutor.executeAction(
fixture.editor.vim, "EditorCloneCaretBelow",
name = "EditorCloneCaretBelow", injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim),
context = injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim),
) )
kotlin.test.assertEquals(2, fixture.editor.caretModel.caretCount) kotlin.test.assertEquals(2, fixture.editor.caretModel.caretCount)
assertCaretVisualAttributes(CaretVisualAttributes.Shape.BLOCK, 0f) assertCaretVisualAttributes(CaretVisualAttributes.Shape.BLOCK, 0f)

View File

@ -7,6 +7,7 @@
*/ */
package org.jetbrains.plugins.ideavim.option package org.jetbrains.plugins.ideavim.option
import com.intellij.testFramework.UsefulTestCase.assertDoesntContain
import com.maddyhome.idea.vim.helper.CharacterHelper import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.CharacterHelper.charType import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@ -91,7 +92,14 @@ class KeywordOptionTest : VimTestCase() {
setKeyword("b-a") setKeyword("b-a")
assertPluginError(true) assertPluginError(true)
assertPluginErrorMessageContains("E474: Invalid argument: iskeyword=b-a") assertPluginErrorMessageContains("E474: Invalid argument: iskeyword=b-a")
assertTrue("b-a" !in values) assertDoesntContain(
values,
object : ArrayList<String?>() {
init {
add("b-a")
}
},
)
} }
@Test @Test
@ -99,7 +107,14 @@ class KeywordOptionTest : VimTestCase() {
setKeyword("ab") setKeyword("ab")
assertPluginError(true) assertPluginError(true)
assertPluginErrorMessageContains("E474: Invalid argument: iskeyword=ab") assertPluginErrorMessageContains("E474: Invalid argument: iskeyword=ab")
assertTrue("ab" !in values) assertDoesntContain(
values,
object : ArrayList<String?>() {
init {
add("ab")
}
},
)
} }
@Test @Test

View File

@ -27,8 +27,8 @@ import com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER
import com.maddyhome.idea.vim.register.RegisterConstants.VALID_REGISTERS import com.maddyhome.idea.vim.register.RegisterConstants.VALID_REGISTERS
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.toVimNotation import com.maddyhome.idea.vim.state.mode.toVimNotation
import org.junit.Assert.assertEquals
import org.junit.jupiter.api.TestInfo import org.junit.jupiter.api.TestInfo
import kotlin.test.assertEquals
object NeovimTesting { object NeovimTesting {
private lateinit var neovimApi: NeovimApi private lateinit var neovimApi: NeovimApi
@ -103,7 +103,7 @@ object NeovimTesting {
singleCaret singleCaret
} }
private fun isNeovimTestingEnabled(): Boolean { fun isNeovimTestingEnabled(): Boolean {
val property = System.getProperty("ideavim.nvim.test", "false") val property = System.getProperty("ideavim.nvim.test", "false")
val neovimTestingEnabled = if (property.isBlank()) true else property.toBoolean() val neovimTestingEnabled = if (property.isBlank()) true else property.toBoolean()
return neovimTestingEnabled return neovimTestingEnabled
@ -188,7 +188,7 @@ object NeovimTesting {
val neovimRegister = getRegister(register) val neovimRegister = getRegister(register)
val vimPluginRegister = VimPlugin.getRegister().getRegister(register) val vimPluginRegister = VimPlugin.getRegister().getRegister(register)
val ideavimRegister = vimPluginRegister?.text ?: "" val ideavimRegister = vimPluginRegister?.text ?: ""
assertEquals(neovimRegister, ideavimRegister, "Register '$register'") assertEquals("Register '$register'", neovimRegister, ideavimRegister)
if (neovimRegister.isNotEmpty()) { if (neovimRegister.isNotEmpty()) {
val neovimRegisterType = neovimApi.callFunction("getregtype", listOf(register)).get().toString() val neovimRegisterType = neovimApi.callFunction("getregtype", listOf(register)).get().toString()
@ -201,7 +201,7 @@ object NeovimTesting {
// We take only the first char because neovim returns width for block selection // We take only the first char because neovim returns width for block selection
val neovimChar = neovimRegisterType.getOrNull(0)?.toString() ?: "" val neovimChar = neovimRegisterType.getOrNull(0)?.toString() ?: ""
assertEquals(expectedType, neovimChar, "Register '$register'") assertEquals("Register '$register'", expectedType, neovimChar)
} }
} }
} }

View File

@ -88,6 +88,7 @@ import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
import com.maddyhome.idea.vim.vimscript.parser.errors.IdeavimErrorListener import com.maddyhome.idea.vim.vimscript.parser.errors.IdeavimErrorListener
import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.ApiStatus
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestInfo import org.junit.jupiter.api.TestInfo
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
@ -222,7 +223,6 @@ abstract class VimTestCase {
VimPlugin.getKey().savedShortcutConflicts.clear() VimPlugin.getKey().savedShortcutConflicts.clear()
assertTrue(KeyHandler.getInstance().keyStack.isEmpty()) assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
injector.outputPanel.getCurrentOutputPanel()?.close() injector.outputPanel.getCurrentOutputPanel()?.close()
injector.modalInput.getCurrentModalInput()?.deactivate(refocusOwningEditor = false, resetCaret = false)
// Tear down neovim // Tear down neovim
NeovimTesting.tearDown(testInfo) NeovimTesting.tearDown(testInfo)
@ -889,6 +889,12 @@ abstract class VimTestCase {
const val s = EditorTestUtil.SELECTION_START_TAG const val s = EditorTestUtil.SELECTION_START_TAG
const val se = EditorTestUtil.SELECTION_END_TAG const val se = EditorTestUtil.SELECTION_END_TAG
@BeforeAll
@JvmStatic
fun beforeAll() {
println("Neovim testing: ${NeovimTesting.isNeovimTestingEnabled()}")
}
fun typeText(keys: List<KeyStroke?>, editor: Editor, project: Project?) { fun typeText(keys: List<KeyStroke?>, editor: Editor, project: Project?) {
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
val dataContext = injector.executionContextManager.getEditorExecutionContext(editor.vim) val dataContext = injector.executionContextManager.getEditorExecutionContext(editor.vim)

View File

@ -27,9 +27,6 @@ class TestInjector(val injector: VimInjector) : VimInjector by injector {
tracers[key] = collector tracers[key] = collector
} }
override val modalInput
get() = injector.modalInput
override val vimState override val vimState
get() = injector.vimState get() = injector.vimState

View File

@ -1,5 +1,3 @@
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
/* /*
* Copyright 2003-2024 The IdeaVim authors * Copyright 2003-2024 The IdeaVim authors
* *
@ -11,20 +9,16 @@ import org.jetbrains.intellij.platform.gradle.TestFrameworkType
plugins { plugins {
id("java") id("java")
kotlin("jvm") kotlin("jvm")
id("org.jetbrains.intellij.platform.module") id("org.jetbrains.intellij")
} }
val kotlinVersion: String by project val kotlinVersion: String by project
val ideaType: String by project
val ideaVersion: String by project val ideaVersion: String by project
val javaVersion: String by project val javaVersion: String by project
repositories { repositories {
mavenCentral() mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
intellijPlatform {
defaultRepositories()
}
} }
dependencies { dependencies {
@ -32,25 +26,35 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
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:5.10.3")
intellijPlatform {
create(ideaType, ideaVersion)
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
bundledPlugins("com.intellij.java", "org.jetbrains.plugins.yaml")
instrumentationTools()
}
}
intellijPlatform {
buildSearchableOptions = false
} }
tasks { tasks {
test { test {
useJUnitPlatform() useJUnitPlatform()
} }
verifyPlugin {
enabled = false
}
publishPlugin {
enabled = false
}
runIde {
enabled = false
}
runPluginVerifier {
enabled = false
}
}
intellij {
version.set(ideaVersion)
type.set("IC")
// Yaml is only used for testing. It's part of the IdeaIC distribution, but needs to be included as a reference
plugins.set(listOf("java", "yaml"))
} }
java { java {

View File

@ -161,31 +161,4 @@ class MotionPercentOrMatchActionJavaTest : VimJavaTestCase() {
} }
""".trimIndent()) """.trimIndent())
} }
@Test
@TestFor(issues = ["VIM-3530"])
@TestWithoutNeovim(SkipNeovimReason.PSI)
fun `test block in comment`() {
configureByJavaText("""
/**
* Compose Multiplatform Gradle Plugin
* [gradle portal](https://plugins${c}.gradle.org/plugin/org.jetbrains.compose)
* [github](https://github.com/JetBrains/compose-multiplatform/releases)
* [maven runtime](https://maven.pkg.jetbrains.space/public/p/compose/dev/org/jetbrains/compose/runtime/)
* [maven runtime](https://maven.pkg.jetbrains.space/public/p/compose/dev/org/jetbrains/compose/runtime/runtime/)
* [maven ui-js](https://maven.pkg.jetbrains.space/public/p/compose/dev/org/jetbrains/compose/ui/ui-js/)
*/
""".trimIndent())
typeText("di)")
assertState("""
/**
* Compose Multiplatform Gradle Plugin
* [gradle portal]()
* [github](https://github.com/JetBrains/compose-multiplatform/releases)
* [maven runtime](https://maven.pkg.jetbrains.space/public/p/compose/dev/org/jetbrains/compose/runtime/)
* [maven runtime](https://maven.pkg.jetbrains.space/public/p/compose/dev/org/jetbrains/compose/runtime/runtime/)
* [maven ui-js](https://maven.pkg.jetbrains.space/public/p/compose/dev/org/jetbrains/compose/ui/ui-js/)
*/
""".trimIndent())
}
} }

View File

@ -1,22 +1,15 @@
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.extensions.intellijPlatform
plugins { plugins {
java java
kotlin("jvm") kotlin("jvm")
id("org.jetbrains.intellij.platform.module") id("org.jetbrains.intellij")
} }
repositories { repositories {
mavenCentral() mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
intellijPlatform {
defaultRepositories()
}
} }
val kotlinVersion: String by project val kotlinVersion: String by project
val ideaType: String by project
val ideaVersion: String by project val ideaVersion: String by project
val javaVersion: String by project val javaVersion: String by project
@ -25,36 +18,42 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
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:5.10.3")
intellijPlatform {
create(ideaType, ideaVersion)
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
instrumentationTools()
}
}
intellijPlatform {
buildSearchableOptions = false
} }
tasks { tasks {
// This task is disabled because it should be excluded from `gradle test` run (because it's slow) // This task is disabled because it should be excluded from `gradle test` run (because it's slow)
// I didn't find a better way to exclude except disabling and defining a new task with a different name // I didn't find a better way to exclude except disabling and defining a new task with a different name
// Note that useJUnitTestPlatform() is required to prevent red code
test { test {
enabled = false enabled = false
useJUnitPlatform() useJUnitPlatform()
} }
// The `test` task is automatically set up with IntelliJ goodness. A custom test task needs to be configured for it register<Test>("testLongRunning") {
val testLongRunning by intellijPlatformTesting.testIde.registering { group = "verification"
task { useJUnitPlatform()
group = "verification"
useJUnitPlatform()
}
} }
verifyPlugin {
enabled = false
}
publishPlugin {
enabled = false
}
runIde {
enabled = false
}
runPluginVerifier {
enabled = false
}
}
intellij {
version.set(ideaVersion)
type.set("IC")
plugins.set(listOf("java"))
} }
java { java {

View File

@ -1,22 +1,15 @@
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.extensions.intellijPlatform
plugins { plugins {
java java
kotlin("jvm") kotlin("jvm")
id("org.jetbrains.intellij.platform.module") id("org.jetbrains.intellij")
} }
repositories { repositories {
mavenCentral() mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
intellijPlatform {
defaultRepositories()
}
} }
val kotlinVersion: String by project val kotlinVersion: String by project
val ideaType: String by project
val ideaVersion: String by project val ideaVersion: String by project
val javaVersion: String by project val javaVersion: String by project
@ -25,19 +18,6 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
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:5.10.3")
intellijPlatform {
create(ideaType, ideaVersion)
bundledPlugins("com.intellij.java")
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
instrumentationTools()
}
}
intellijPlatform {
buildSearchableOptions = false
} }
tasks { tasks {
@ -48,13 +28,32 @@ tasks {
enabled = false enabled = false
} }
// The `test` task is automatically set up with IntelliJ goodness. A custom test task needs to be configured for it register<Test>("testPropertyBased") {
val testPropertyBased by intellijPlatformTesting.testIde.registering { group = "verification"
task { useJUnitPlatform()
group = "verification"
useJUnitPlatform()
}
} }
verifyPlugin {
enabled = false
}
publishPlugin {
enabled = false
}
runIde {
enabled = false
}
runPluginVerifier {
enabled = false
}
}
intellij {
version.set(ideaVersion)
type.set("IC")
plugins.set(listOf("java"))
} }
java { java {

View File

@ -13,9 +13,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.PlatformTestUtil
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.CommandNode
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.jetCheck.Generator import org.jetbrains.jetCheck.Generator
import org.jetbrains.jetCheck.ImperativeCommand import org.jetbrains.jetCheck.ImperativeCommand
import org.jetbrains.jetCheck.PropertyChecker import org.jetbrains.jetCheck.PropertyChecker
@ -37,7 +35,6 @@ class RandomActionsPropertyTest : VimPropertyTestBase() {
PropertyChecker.checkScenarios { PropertyChecker.checkScenarios {
ImperativeCommand { env -> ImperativeCommand { env ->
val editor = configureByText(text) val editor = configureByText(text)
KeyHandler.getInstance().fullReset(editor.vim)
try { try {
moveCaretToRandomPlace(env, editor) moveCaretToRandomPlace(env, editor)
env.executeCommands(Generator.sampledFrom(AvailableActions(editor))) env.executeCommands(Generator.sampledFrom(AvailableActions(editor)))
@ -53,7 +50,6 @@ class RandomActionsPropertyTest : VimPropertyTestBase() {
PropertyChecker.checkScenarios { PropertyChecker.checkScenarios {
ImperativeCommand { env -> ImperativeCommand { env ->
val editor = configureByText(loremText) val editor = configureByText(loremText)
KeyHandler.getInstance().fullReset(editor.vim)
try { try {
moveCaretToRandomPlace(env, editor) moveCaretToRandomPlace(env, editor)
env.executeCommands(Generator.sampledFrom(AvailableActions(editor))) env.executeCommands(Generator.sampledFrom(AvailableActions(editor)))
@ -69,7 +65,6 @@ class RandomActionsPropertyTest : VimPropertyTestBase() {
PropertyChecker.checkScenarios { PropertyChecker.checkScenarios {
ImperativeCommand { env -> ImperativeCommand { env ->
val editor = configureByJavaText(javaText) val editor = configureByJavaText(javaText)
KeyHandler.getInstance().fullReset(editor.vim)
try { try {
moveCaretToRandomPlace(env, editor) moveCaretToRandomPlace(env, editor)
env.executeCommands(Generator.sampledFrom(AvailableActions(editor))) env.executeCommands(Generator.sampledFrom(AvailableActions(editor)))
@ -94,9 +89,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
override fun performCommand(env: ImperativeCommand.Environment) { override fun performCommand(env: ImperativeCommand.Environment) {
val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie() val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie()
// Note: esc is always an option val possibleKeys = currentNode.keys.toList().sortedBy { injector.parser.toKeyNotation(it) }
val possibleKeys = (currentNode.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) }
println("Keys: ${possibleKeys.joinToString(", ")}")
val keyGenerator = Generator.integers(0, possibleKeys.lastIndex) val keyGenerator = Generator.integers(0, possibleKeys.lastIndex)
.suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList } .suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList }
.map { possibleKeys[it] } .map { possibleKeys[it] }
@ -110,8 +103,6 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
IdeEventQueue.getInstance().flushQueue() IdeEventQueue.getInstance().flushQueue()
PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue()
} }
private val esc = key("<Esc>")
} }
private val stinkyKeysList = arrayListOf( private val stinkyKeysList = arrayListOf(

View File

@ -16,17 +16,17 @@ import ui.pages.Editor
import java.awt.Point import java.awt.Point
fun RemoteText.doubleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) { fun RemoteText.doubleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) {
fixture.runJs(""" val updatedPoint = Point(this.point.x + shiftX, this.point.y)
const updatedPoint = new Point(${this.point.x + shiftX}, ${this.point.y}); fixture.remoteRobot.execute(fixture) {
robot.click(component, updatedPoint, MouseButton.$button, 2) robot.click(component, updatedPoint, button, 2)
""".trimIndent()) }
} }
fun RemoteText.tripleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) { fun RemoteText.tripleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) {
fixture.runJs(""" val updatedPoint = Point(this.point.x + shiftX, this.point.y)
const updatedPoint = new Point(${this.point.x + shiftX}, ${this.point.y}); fixture.remoteRobot.execute(fixture) {
robot.click(component, updatedPoint, MouseButton.$button, 3) robot.click(component, updatedPoint, button, 3)
""".trimIndent()) }
} }
fun RemoteText.moveMouseTo(goal: RemoteText, editor: Editor): Boolean { fun RemoteText.moveMouseTo(goal: RemoteText, editor: Editor): Boolean {
@ -66,12 +66,7 @@ fun RemoteText.moveMouseInGutterTo(goal: RemoteText, fixture: Fixture) {
fun Point.moveMouseTo(point: Point, fixture: Fixture) { fun Point.moveMouseTo(point: Point, fixture: Fixture) {
val _point = this val _point = this
fixture.runJs( fixture.execute { robot.moveMouse(component, _point) }
"""
const point = new java.awt.Point(${_point.x}, ${_point.y});
robot.moveMouse(component, point)
""".trimIndent()
)
fixture.runJs( fixture.runJs(
""" """

View File

@ -10,18 +10,12 @@ plugins {
java java
kotlin("jvm") kotlin("jvm")
// id("org.jlleitschuh.gradle.ktlint") // id("org.jlleitschuh.gradle.ktlint")
id("com.google.devtools.ksp") version "2.0.0-1.0.23" id("com.google.devtools.ksp") version "1.9.22-1.0.17"
kotlin("plugin.serialization") version "2.0.0" kotlin("plugin.serialization") version "1.9.22"
`maven-publish` `maven-publish`
antlr antlr
} }
val sourcesJarArtifacts by configurations.registering {
attributes {
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
}
}
val kotlinVersion: String by project val kotlinVersion: String by project
val kotlinxSerializationVersion: String by project val kotlinxSerializationVersion: String by project
@ -48,19 +42,14 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.3") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.3")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.3")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.1.0") compileOnly("org.jetbrains:annotations:24.1.0")
runtimeOnly("org.antlr:antlr4-runtime:4.13.2") runtimeOnly("org.antlr:antlr4-runtime:4.13.1")
antlr("org.antlr:antlr4:4.13.2") antlr("org.antlr:antlr4:4.13.1")
ksp(project(":annotation-processors")) ksp(project(":annotation-processors"))
compileOnly(project(":annotation-processors")) compileOnly(project(":annotation-processors"))
@ -68,7 +57,7 @@ dependencies {
compileOnly(kotlin("reflect")) compileOnly(kotlin("reflect"))
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
} }
tasks { tasks {
@ -108,8 +97,6 @@ java {
withJavadocJar() withJavadocJar()
} }
artifacts.add(sourcesJarArtifacts.name, tasks.named("sourcesJar"))
val spaceUsername: String by project val spaceUsername: String by project
val spacePassword: String by project val spacePassword: String by project
val engineVersion: String by project val engineVersion: String by project

View File

@ -31,7 +31,6 @@ import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer
import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer
import com.maddyhome.idea.vim.key.consumers.DigraphConsumer import com.maddyhome.idea.vim.key.consumers.DigraphConsumer
import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer
import com.maddyhome.idea.vim.key.consumers.ModalInputConsumer
import com.maddyhome.idea.vim.key.consumers.ModeInputConsumer import com.maddyhome.idea.vim.key.consumers.ModeInputConsumer
import com.maddyhome.idea.vim.key.consumers.RegisterConsumer import com.maddyhome.idea.vim.key.consumers.RegisterConsumer
import com.maddyhome.idea.vim.key.consumers.SelectRegisterConsumer import com.maddyhome.idea.vim.key.consumers.SelectRegisterConsumer
@ -40,7 +39,6 @@ import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.returnTo import com.maddyhome.idea.vim.state.mode.returnTo
import java.lang.ref.WeakReference
import javax.swing.KeyStroke import javax.swing.KeyStroke
/** /**
@ -51,8 +49,7 @@ import javax.swing.KeyStroke
// 1. avoid using handleKeyRecursionCount & shouldRecord // 1. avoid using handleKeyRecursionCount & shouldRecord
// 2. maybe we can live without allowKeyMappings: Boolean & mappingCompleted: Boolean // 2. maybe we can live without allowKeyMappings: Boolean & mappingCompleted: Boolean
class KeyHandler { class KeyHandler {
private var editorInFocusReference: WeakReference<VimEditor>? = null private val keyConsumers: List<KeyConsumer> = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer(), SelectRegisterConsumer(), ModeInputConsumer())
private val keyConsumers: List<KeyConsumer> = listOf(ModalInputConsumer(), MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer(), SelectRegisterConsumer(), ModeInputConsumer())
private var handleKeyRecursionCount = 0 private var handleKeyRecursionCount = 0
var keyHandlerState: KeyHandlerState = KeyHandlerState() var keyHandlerState: KeyHandlerState = KeyHandlerState()
@ -61,12 +58,6 @@ class KeyHandler {
val keyStack: KeyStack = KeyStack() val keyStack: KeyStack = KeyStack()
val modalEntryKeys: MutableList<KeyStroke> = ArrayList() val modalEntryKeys: MutableList<KeyStroke> = ArrayList()
var editorInFocus: VimEditor?
get() = editorInFocusReference?.get()
set(value) {
editorInFocusReference = WeakReference(value)
}
/** /**
* This is the main key handler for the Vim plugin. Every keystroke not handled directly by Idea is sent here for * This is the main key handler for the Vim plugin. Every keystroke not handled directly by Idea is sent here for
* processing. * processing.
@ -135,12 +126,12 @@ class KeyHandler {
injector.messages.clearError() injector.messages.clearError()
// We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything. // We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything.
val shouldRecord = handleKeyRecursionCount == 0 && injector.registerGroup.isRecording val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording)
handleKeyRecursionCount++ handleKeyRecursionCount++
try { try {
val isProcessed = keyConsumers.any { val isProcessed = keyConsumers.any {
it.consumeKey(key, editor, allowKeyMappings, mappingCompleted, processBuilder) it.consumeKey(key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord)
} }
if (isProcessed) { if (isProcessed) {
logger.trace { "Key was successfully caught by consumer" } logger.trace { "Key was successfully caught by consumer" }
@ -166,7 +157,7 @@ class KeyHandler {
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
key: KeyStroke?, key: KeyStroke?,
shouldRecord: Boolean, shouldRecord: MutableBoolean,
keyState: KeyHandlerState, keyState: KeyHandlerState,
) { ) {
// Do we have a fully entered command at this point? If so, let's execute it. // Do we have a fully entered command at this point? If so, let's execute it.
@ -178,7 +169,7 @@ class KeyHandler {
} }
// Don't record the keystroke that stops the recording (unmapped this is `q`) // Don't record the keystroke that stops the recording (unmapped this is `q`)
if (shouldRecord && injector.registerGroup.isRecording && key != null) { if (shouldRecord.value && injector.registerGroup.isRecording && key != null) {
injector.registerGroup.recordKeyStroke(key) injector.registerGroup.recordKeyStroke(key)
modalEntryKeys.forEach { injector.registerGroup.recordKeyStroke(it) } modalEntryKeys.forEach { injector.registerGroup.recordKeyStroke(it) }
modalEntryKeys.clear() modalEntryKeys.clear()
@ -274,13 +265,11 @@ class KeyHandler {
*/ */
fun reset(editor: VimEditor) { fun reset(editor: VimEditor) {
logger.trace { "Reset is executed" } logger.trace { "Reset is executed" }
editor.isReplaceCharacter = false
editor.resetOpPending() editor.resetOpPending()
keyHandlerState.partialReset(editor.mode) keyHandlerState.partialReset(editor.mode)
keyHandlerState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode())) keyHandlerState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode()))
} }
// TODO we should have a single reset method
fun reset(keyState: KeyHandlerState, mode: Mode) { fun reset(keyState: KeyHandlerState, mode: Mode) {
logger.trace { "Reset is executed" } logger.trace { "Reset is executed" }
keyHandlerState.partialReset(mode) keyHandlerState.partialReset(mode)

View File

@ -11,7 +11,6 @@ import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
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.api.injector
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
@ -42,7 +41,7 @@ class ChangeCaseLowerMotionAction : ChangeEditorActionHandler.ForEachCaret(), Du
editor, editor,
caret, caret,
context, context,
VimChangeGroup.ChangeCaseType.LOWER, CharacterHelper.CASE_LOWER,
argument, argument,
operatorArguments, operatorArguments,
) )

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
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.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
@ -35,6 +34,6 @@ class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
return injector.changeGroup return injector.changeGroup
.changeCaseRange(editor, caret, range.toVimTextRange(false), VimChangeGroup.ChangeCaseType.LOWER) .changeCaseRange(editor, caret, range.toVimTextRange(false), CharacterHelper.CASE_LOWER)
} }
} }

View File

@ -11,7 +11,6 @@ import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
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.api.injector
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
@ -19,6 +18,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.DuplicableOperatorAction import com.maddyhome.idea.vim.command.DuplicableOperatorAction
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.helper.CharacterHelper
@CommandOrMotion(keys = ["g~"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["g~"], modes = [Mode.NORMAL])
class ChangeCaseToggleMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction { class ChangeCaseToggleMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction {
@ -41,7 +41,7 @@ class ChangeCaseToggleMotionAction : ChangeEditorActionHandler.ForEachCaret(), D
editor, editor,
caret, caret,
context, context,
VimChangeGroup.ChangeCaseType.TOGGLE, CharacterHelper.CASE_TOGGLE,
argument, argument,
operatorArguments, operatorArguments,
) )

View File

@ -11,7 +11,6 @@ import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
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.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
@ -36,6 +35,6 @@ class ChangeCaseToggleVisualAction : VisualOperatorActionHandler.ForEachCaret()
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
return injector.changeGroup return injector.changeGroup
.changeCaseRange(editor, caret, range.toVimTextRange(false), VimChangeGroup.ChangeCaseType.TOGGLE) .changeCaseRange(editor, caret, range.toVimTextRange(false), CharacterHelper.CASE_TOGGLE)
} }
} }

View File

@ -11,7 +11,6 @@ import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
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.api.injector
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
@ -42,7 +41,7 @@ class ChangeCaseUpperMotionAction : ChangeEditorActionHandler.ForEachCaret(), Du
editor, editor,
caret, caret,
context, context,
VimChangeGroup.ChangeCaseType.UPPER, CharacterHelper.CASE_UPPER,
argument, argument,
operatorArguments, operatorArguments,
) )

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
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.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
@ -35,6 +34,6 @@ class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
return injector.changeGroup return injector.changeGroup
.changeCaseRange(editor, caret, range.toVimTextRange(false), VimChangeGroup.ChangeCaseType.UPPER) .changeCaseRange(editor, caret, range.toVimTextRange(false), CharacterHelper.CASE_UPPER)
} }
} }

View File

@ -23,7 +23,6 @@ import com.maddyhome.idea.vim.diagnostic.debug
import com.maddyhome.idea.vim.diagnostic.vimLogger import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.KeyHandlerState
import java.util.* import java.util.*
@CommandOrMotion(keys = ["r"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["r"], modes = [Mode.NORMAL])
@ -34,10 +33,6 @@ class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
editor.isReplaceCharacter = true
}
override fun execute( override fun execute(
editor: VimEditor, editor: VimEditor,
caret: VimCaret, caret: VimCaret,

View File

@ -23,7 +23,6 @@ import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.KeyHandlerState
import java.util.* import java.util.*
/** /**
@ -37,10 +36,6 @@ class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
editor.isReplaceCharacter = true
}
override fun executeAction( override fun executeAction(
editor: VimEditor, editor: VimEditor,
caret: VimCaret, caret: VimCaret,
@ -73,7 +68,7 @@ private fun changeCharacterRange(editor: VimEditor, caret: VimCaret, range: Text
for (j in ends.indices.reversed()) { for (j in ends.indices.reversed()) {
for (i in starts[j] until ends[j]) { for (i in starts[j] until ends[j]) {
if (i < chars.length && '\n' != chars[i]) { if (i < chars.length && '\n' != chars[i]) {
injector.changeGroup.replaceText(editor, caret, i, i + 1, ch.toString()) injector.changeGroup.replaceText(editor, caret, i, i + 1, Character.toString(ch))
} }
} }
} }

View File

@ -70,6 +70,6 @@ class FilterMotionAction : VimActionHandler.SingleExecution(), FilterCommand, Du
interface FilterCommand { interface FilterCommand {
fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) { fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
injector.commandLine.createCommandPrompt(editor, context, cmd, initialText = "!") injector.processGroup.startExEntry(editor, context, cmd, initialCommandText = "!")
} }
} }

Some files were not shown because too many files have changed in this diff Show More