1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-07-05 15:04:33 +02:00

Compare commits

..

24 Commits

Author SHA1 Message Date
295f0b6c53
Set plugin version to chylex-45 2025-03-27 11:30:21 +01:00
2643301ea9
Actions and Enter are broken in Rider... 2025-03-27 11:30:08 +01:00
51f15782af
Make camelCase motions adjust based on direction of visual selection 2025-03-27 11:30:08 +01:00
de930ed57c
Make search highlights temporary 2025-03-25 09:25:25 +01:00
590d5bd22d
Exit insert mode after refactoring 2025-03-25 09:25:25 +01:00
69c748d881
Add action to run last macro in all opened files 2025-03-25 09:25:25 +01:00
144cc5c3fc
Stop macro execution after a failed search 2025-03-25 09:25:24 +01:00
fb270cdbc5
Revert per-caret registers 2025-03-25 09:25:24 +01:00
ce6a296233
Fix(VIM-3364): Exception with mapped Generate action 2025-03-25 09:25:24 +01:00
949f359b98
Apply scrolloff after executing native IDEA actions 2025-03-25 09:25:24 +01:00
c922426e02
Stay on same line after reindenting 2025-03-25 09:25:24 +01:00
9240e82f2d
Update search register when using f/t 2025-03-25 09:25:24 +01:00
a1639d80b0
Automatically add unambiguous imports after running a macro 2025-03-25 09:25:24 +01:00
7860b98107
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2025-03-25 09:25:24 +01:00
7157f9c5a5
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2025-03-25 09:25:24 +01:00
1522618cd6
Add support for count for visual and line motion surround 2025-03-25 09:25:24 +01:00
09862c8356
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2025-03-25 09:25:24 +01:00
c6ef3f286f
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2025-03-25 09:25:24 +01:00
b358e63444
Respect count with <Action> mappings 2025-03-25 09:25:24 +01:00
7ac743c604
Change matchit plugin to use HTML patterns in unrecognized files 2025-03-25 09:25:24 +01:00
db3d3fc608
Reset insert mode when switching active editor 2025-03-25 09:25:24 +01:00
14d313907b
Remove notifications about configuration options 2025-03-25 09:25:24 +01:00
fe37a69544
Remove update checker 2025-03-25 09:25:24 +01:00
54de3dac25
Set custom plugin version 2025-03-25 09:25:24 +01:00
227 changed files with 3135 additions and 4011 deletions

View File

@ -28,12 +28,10 @@ jobs:
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
url: http://127.0.0.1:8082 url: http://127.0.0.1:8082
max-attempts: 100 max-attempts: 20
retry-delay: 10s retry-delay: 10s
- name: Tests - name: Tests
run: gradle :tests:ui-rd-tests:testUi run: gradle :tests:ui-rd-tests:testUi
env:
RIDER_LICENSE: ${{ secrets.RIDER_LICENSE }}
- name: Move video - name: Move video
if: always() if: always()
run: mv tests/ui-rd-tests/video build/reports run: mv tests/ui-rd-tests/video build/reports

View File

@ -5,11 +5,11 @@ object Constants {
const val EAP_CHANNEL = "eap" const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev" const val DEV_CHANNEL = "Dev"
const val NVIM_TESTS = "2025.1" const val NVIM_TESTS = "2024.3.3"
const val PROPERTY_TESTS = "2025.1" const val PROPERTY_TESTS = "2024.3.3"
const val LONG_RUNNING_TESTS = "2025.1" const val LONG_RUNNING_TESTS = "2024.3.3"
const val RELEASE = "2025.1" const val RELEASE = "2024.3.3"
const val RELEASE_DEV = "2025.1" const val RELEASE_DEV = "2024.3.3"
const val RELEASE_EAP = "2025.1" const val RELEASE_EAP = "2024.3.3"
} }

View File

@ -23,8 +23,8 @@ object Project : Project({
vcsRoot(ReleasesVcsRoot) vcsRoot(ReleasesVcsRoot)
// Active tests // Active tests
buildType(TestingBuildType("Latest EAP", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2025.1")) buildType(TestingBuildType("2024.3.3", "<default>"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased) buildType(PropertyBased)

View File

@ -43,8 +43,6 @@ object Compatibility : IdeaVimBuildType({
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.peekaboo' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.peekaboo' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.switch' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.switch' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.functiontextobj' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.functiontextobj' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.miksuki.HighlightCursor' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.ugarosa.idea.edgemotion' [latest-IU] -team-city
""".trimIndent() """.trimIndent()
} }
} }

View File

@ -24,7 +24,6 @@ object PropertyBased : IdeaVimBuildType({
steps { steps {
gradle { gradle {
clearConditions()
tasks = "clean :tests:property-tests:testPropertyBased" tasks = "clean :tests:property-tests:testPropertyBased"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true

View File

@ -12,7 +12,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs
open class TestingBuildType( open class TestingBuildType(
private val testName: String, private val testName: String,
private val branch: String = "<default>", private val branch: String,
private val version: String = testName, private val version: String = testName,
private val javaVersion: String? = null, private val javaVersion: String? = null,
private val javaPlugin: Boolean = true, private val javaPlugin: Boolean = true,

View File

@ -26,13 +26,6 @@ Previous maintainers:
&nbsp; &nbsp;
Andrey Vlasovskikh Andrey Vlasovskikh
Previous support members:
* [![icon][mail]](mailto:lejia.chen@jetbrains.com)
[![icon][github-off]](#)
&nbsp;
Lejia Chen
Contributors: Contributors:
* [![icon][mail]](mailto:yole@jetbrains.com) * [![icon][mail]](mailto:yole@jetbrains.com)
@ -594,22 +587,6 @@ Contributors:
[![icon][github]](https://github.com/Iliya-usov) [![icon][github]](https://github.com/Iliya-usov)
&nbsp; &nbsp;
Ilya Usov Ilya Usov
* [![icon][mail]](mailto:peterHoburg@users.noreply.github.com)
[![icon][github]](https://github.com/peterHoburg)
&nbsp;
Peter Hoburg
* [![icon][mail]](mailto:erotourtes@gmail.com)
[![icon][github]](https://github.com/erotourtes)
&nbsp;
Max Siryk
* [![icon][mail]](mailto:ivan.yarkov@jetbrains.com)
[![icon][github]](https://github.com/MToolMakerJB)
&nbsp;
Ivan Yarkov
* [![icon][mail]](mailto:mia.vucinic@jetbrains.com)
[![icon][github]](https://github.com/vumi19)
&nbsp;
Mia Vucinic
Previous contributors: Previous contributors:

View File

@ -92,26 +92,20 @@ Here are some examples of supported vim features and commands:
[IdeaVim plugins](https://github.com/JetBrains/ideavim/wiki/IdeaVim-Plugins): [IdeaVim plugins](https://github.com/JetBrains/ideavim/wiki/IdeaVim-Plugins):
* argtextobj * vim-easymotion
* commentary
* easymotion
* exchange
* FunctionTextObj
* highlightedyank
* indent-object
* matchit.vim
* Mini.ai
* multiple-cursors
* NERDTree * NERDTree
* paragraph-motion * vim-surround
* Peekaboo * vim-multiple-cursors
* quick-scope * vim-commentary
* argtextobj.vim
* vim-textobj-entire
* ReplaceWithRegister * ReplaceWithRegister
* sneak * vim-exchange
* surround * vim-highlightedyank
* Switch * vim-paragraph-motion
* textobj-entire * vim-indent-object
* Which-Key * match.it
etc
See also: See also:

View File

@ -21,7 +21,7 @@ repositories {
} }
dependencies { dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.21-2.0.1") compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.10-1.0.29")
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

@ -31,7 +31,6 @@ import kotlinx.serialization.json.putJsonObject
import org.eclipse.jgit.api.Git 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.intellij.markdown.ast.impl.ListCompositeNode
import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
@ -51,14 +50,14 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.0.202503040940-r") classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r")
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:3.1.3") classpath("io.ktor:ktor-client-core:3.1.1")
classpath("io.ktor:ktor-client-cio:3.1.3") classpath("io.ktor:ktor-client-cio:3.1.1")
classpath("io.ktor:ktor-client-auth:3.1.3") classpath("io.ktor:ktor-client-auth:3.1.1")
classpath("io.ktor:ktor-client-content-negotiation:3.1.3") classpath("io.ktor:ktor-client-content-negotiation:3.1.1")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.1.3") classpath("io.ktor:ktor-serialization-kotlinx-json:3.1.1")
// This comes from the changelog plugin // This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1") // classpath("org.jetbrains:markdown:0.3.1")
@ -70,12 +69,7 @@ plugins {
kotlin("jvm") version "2.0.21" kotlin("jvm") version "2.0.21"
application application
id("java-test-fixtures") id("java-test-fixtures")
id("org.jetbrains.intellij.platform") version "2.3.0"
// NOTE: Unignore "test block comment falls back to line comment when not available" test
// After changing this version. It supposed to work on the next version of the gradle plugin
// Or go report to the devs that this test still fails.
id("org.jetbrains.intellij.platform") version "2.5.0"
id("org.jetbrains.changelog") version "2.2.1" id("org.jetbrains.changelog") version "2.2.1"
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.1" id("com.dorongold.task-tree") version "4.0.1"
@ -118,11 +112,7 @@ dependencies {
intellijPlatform { intellijPlatform {
// Snapshots don't use installers // Snapshots don't use installers
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers // https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers
var useInstaller = "EAP-SNAPSHOT" !in ideaVersion val useInstaller = "EAP-SNAPSHOT" !in ideaVersion
if (ideaType == "RD") {
// Using Rider as a target IntelliJ Platform with `useInstaller = true` is currently not supported, please set `useInstaller = false` instead. See: https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1852
useInstaller = false
}
// Note that it is also possible to use local("...") to compile against a locally installed IDE // 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") // E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
@ -137,7 +127,7 @@ dependencies {
// AceJump is an optional dependency. We use their SessionManager class to check if it's active // AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19") plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "251.23774.318") plugin("com.intellij.classic.ui", "243.21565.122")
bundledPlugins("org.jetbrains.plugins.terminal", "com.intellij.modules.json") bundledPlugins("org.jetbrains.plugins.terminal", "com.intellij.modules.json")
} }
@ -161,17 +151,17 @@ dependencies {
// 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.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.12.2") testImplementation("org.junit.jupiter:junit-jupiter-params:5.12.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.12.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2") testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.12.2") testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.12.0")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4 // 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 // Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2") // testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.2") testImplementation("org.junit.vintage:junit-vintage-engine:5.12.0")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3") // testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
} }
@ -260,7 +250,6 @@ tasks {
// a custom task (see below) // a custom task (see below)
runIde { runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true) systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
systemProperty("idea.trust.all.projects", "true")
} }
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies // Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies
@ -833,9 +822,7 @@ fun updateAuthors(uncheckedEmails: Set<String>) {
org.intellij.markdown.parser.MarkdownParser(org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor()) org.intellij.markdown.parser.MarkdownParser(org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor())
val tree = parser.buildMarkdownTreeFromString(authors) val tree = parser.buildMarkdownTreeFromString(authors)
val contributorsSection = tree.children val contributorsSection = tree.children[24]
.filter { it is ListCompositeNode }
.single { it.getTextInNode(authors).contains("yole") }
val existingEmails = mutableSetOf<String>() val existingEmails = mutableSetOf<String>()
for (child in contributorsSection.children) { for (child in contributorsSection.children) {
if (child.children.size > 1) { if (child.children.size > 1) {

View File

@ -16,95 +16,10 @@ in `~/.ideavimrc`. E.g. `set nosurround`.
Available plugins: Available plugins:
<details> <details>
<summary><h2>argtextobj: Provides a text-object 'a' argument</h2></summary> <summary><h2>easymotion</h2></summary>
Original plugin: [argtextobj.vim](https://www.vim.org/scripts/script.php?script_id=2699).
### Summary:
This plugin provides a text-object 'a' (argument).
You can d(elete), c(hange), v(select)... an argument or inner argument in familiar ways.
That is, such as 'daa'(delete-an-argument) 'cia'(change-inner-argument) 'via'(select-inner-argument).
What this script does is more than just typing
F,dt,
because it recognizes inclusion relationship of parentheses.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'vim-scripts/argtextobj.vim'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'vim-scripts/argtextobj.vim'</code>
<br/>
<code>Plug 'https://github.com/vim-scripts/argtextobj.vim'</code>
<br/>
<code>Plug 'argtextobj.vim'</code>
<br/>
<code>Plug 'https://www.vim.org/scripts/script.php?script_id=2699'</code>
<br/>
<code>set argtextobj</code>
</details>
### Instructions
By default, only the arguments inside parenthesis are considered. To extend the functionality
to other types of brackets, set `g:argtextobj_pairs` variable to a comma-separated
list of colon-separated pairs (same as VIM's `matchpairs` option), like
`let g:argtextobj_pairs="(:),{:},<:>"`. The order of pairs matters when
handling symbols that can also be operators: `func(x << 5, 20) >> 17`. To handle
this syntax parenthesis, must come before angle brackets in the list.
https://www.vim.org/scripts/script.php?script_id=2699
</details>
<details>
<summary><h2>commentary: Adds mapping for quickly commenting stuff out</h2></summary>
By [Daniel Leong](https://github.com/dhleong)
Original plugin: [commentary.vim](https://github.com/tpope/vim-commentary).
### Summary:
Comment stuff out.
Use gcc to comment out a line (takes a count), gc to comment out the target of a motion
(for example, gcap to comment out a paragraph), gc in visual mode to comment out the selection,
and gc in operator pending mode to target a comment.
You can also use it as a command, either with a range like :7,17Commentary,
or as part of a :global invocation like with :g/TODO/Commentary.
That's it.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'tpope/vim-commentary'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'tpope/vim-commentary'</code>
<br/>
<code>Plug 'https://github.com/tpope/vim-commentary'</code>
<br/>
<code>Plug 'vim-commentary'</code>
<br/>
<code>Plug 'tcomment_vim'</code>
<br/>
<code>set commentary</code>
</details>
### Instructions
https://github.com/tpope/vim-commentary/blob/master/doc/commentary.txt
</details>
<details>
<summary><h2>easymotion: Simplifies some motions</h2></summary>
Original plugin: [vim-easymotion](https://github.com/easymotion/vim-easymotion). Original plugin: [vim-easymotion](https://github.com/easymotion/vim-easymotion).
### Summary:
EasyMotion provides a much simpler way to use some motions in vim.
It takes the \<number> out of \<number>w or \<number>f{char} by highlighting all possible choices
and allowing you to press one key to jump directly to the target.
### Setup: ### Setup:
- Install [IdeaVim-EasyMotion](https://plugins.jetbrains.com/plugin/13360-ideavim-easymotion/) - Install [IdeaVim-EasyMotion](https://plugins.jetbrains.com/plugin/13360-ideavim-easymotion/)
and [AceJump](https://plugins.jetbrains.com/plugin/7086-acejump/) plugins. and [AceJump](https://plugins.jetbrains.com/plugin/7086-acejump/) plugins.
@ -126,173 +41,77 @@ All commands with the mappings are supported. See the [full list of supported co
</details> </details>
<details> <details>
<summary><h2>exchange: Easy text exchange operator</h2></summary> <summary><h2>sneak</h2></summary>
By [fan-tom](https://github.com/fan-tom) <img src="images/sneakIcon.svg" width="80" height="80" alt="icon"/>
Original plugin: [vim-exchange](https://github.com/tommcdo/vim-exchange).
### Summary: By [Mikhail Levchenko](https://github.com/Mishkun)
Easy text exchange operator for Vim. Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
Original plugin: [vim-sneak](https://github.com/justinmk/vim-sneak).
### Setup: ### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'tommcdo/vim-exchange'` - Add the following command to `~/.ideavimrc`: `Plug 'justinmk/vim-sneak'`
### Instructions
* Type `s` and two chars to start sneaking in forward direction
* Type `S` and two chars to start sneaking in backward direction
* Type `;` or `,` to proceed with sneaking just as if you were using `f` or `t` commands
</details>
<details>
<summary><h2>NERDTree</h2></summary>
Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'preservim/nerdtree'`
<details> <details>
<summary>Alternative syntax</summary> <summary>Alternative syntax</summary>
<code>Plugin 'tommcdo/vim-exchange'</code> <code>Plugin 'preservim/nerdtree'</code>
<br/> <br/>
<code>Plug 'https://github.com/tommcdo/vim-exchange'</code> <code>Plug 'https://github.com/preservim/nerdtree'</code>
<br/> <br/>
<code>Plug 'vim-exchange'</code> <code>Plug 'nerdtree'</code>
<br/> <br/>
<code>set exchange</code> <code>set NERDTree</code>
</details> </details>
### Instructions ### Instructions
https://github.com/tommcdo/vim-exchange/blob/master/doc/exchange.txt [See here](NERDTree-support.md).
</details> </details>
<details> <details>
<summary><h2>FunctionTextObj: Adds text objects for manipulating functions/methods</h2></summary> <summary><h2>surround</h2></summary>
By Julien Phalip Original plugin: [vim-surround](https://github.com/tpope/vim-surround).
### Summary:
An extension for IdeaVim that adds text objects for manipulating functions/methods in your code.
Similar to how iw operates on words or i" operates on quoted strings,
this plugin provides if and af to operate on functions
### Setup
Add `set functiontextobj` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
</details>
<details>
<summary><h2>highlightedyank: Highlights the yanked region</h2></summary>
By [KostkaBrukowa](https://github.com/KostkaBrukowa)
Original plugin: [vim-highlightedyank](https://github.com/machakann/vim-highlightedyank).
### Summary:
Make the yanked region apparent!
### Setup: ### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'machakann/vim-highlightedyank'` - Add the following command to `~/.ideavimrc`: `Plug 'tpope/vim-surround'`
<details> <details>
<summary>Alternative syntax</summary> <summary>Alternative syntax</summary>
<code>Plugin 'machakann/vim-highlightedyank'</code> <code>Plugin 'tpope/vim-surround'</code>
<br/> <br/>
<code>Plug 'https://github.com/machakann/vim-highlightedyank'</code> <code>Plug 'https://www.vim.org/scripts/script.php?script_id=1697'</code>
<br/> <br/>
<code>Plug 'vim-highlightedyank'</code> <code>Plug 'vim-surround'</code>
<br/> <br/>
<code>set highlightedyank</code> <code>set surround</code>
</details> </details>
### Instructions ### Instructions
If you want to optimize highlight duration, assign a time in milliseconds: https://github.com/tpope/vim-surround/blob/master/doc/surround.txt
`let g:highlightedyank_highlight_duration = "1000"`
A negative number makes the highlight persistent.
If you want to change background color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_color = "rgba(160, 160, 160, 155)"`
If you want to change text color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_foreground_color = "rgba(0, 0, 0, 255)"`
https://github.com/machakann/vim-highlightedyank/blob/master/doc/highlightedyank.txt
</details> </details>
<details> <details>
<summary><h2>indent-object: Adds text objects for manipulating sentences/paragraphs/etc...</h2></summary> <summary><h2>multiple-cursors</h2></summary>
By [Shrikant Sharat Kandula](https://github.com/sharat87)
Original plugin: [vim-indent-object](https://github.com/michaeljsmith/vim-indent-object).
### Summary:
Vim text objects provide a convenient way to select and operate on various types of objects.
These objects include regions surrounded by various types of brackets and various parts of language
(ie sentences, paragraphs, etc).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'michaeljsmith/vim-indent-object'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'michaeljsmith/vim-indent-object'</code>
<br/>
<code>Plug 'https://github.com/michaeljsmith/vim-indent-object'</code>
<br/>
<code>Plug 'vim-indent-object'</code>
<br/>
<code>set textobj-indent</code>
</details>
### Instructions
https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt
</details>
<details>
<summary><h2>matchit.vim: Extends the % key functionality</h2></summary>
By [Martin Yzeiri](https://github.com/myzeiri)
Original plugin: [matchit.vim](https://github.com/chrisbra/matchit).
### Summary:
In Vim, as in plain vi, the percent key, |%|, jumps the cursor from a brace, bracket, or paren to its match.
This can be configured with the 'matchpairs' option.
The matchit plugin extends this in several ways...
### Setup:
- Add the following command to `~/.ideavimrc`: `packadd matchit`
<details>
<summary>Alternative syntax</summary>
<code>Plug 'vim-matchit'</code>
<br/>
<code>Plug 'chrisbra/matchit'</code>
<br/>
<code>set matchit</code>
</details>
### Instructions
https://github.com/adelarsq/vim-matchit/blob/master/doc/matchit.txt
</details>
<details>
<summary><h2>Mini.ai: Extend and create a/i textobjects (IMPORTANT: The plugin is not related with artificial intelligence)</h2></summary>
### Summary:
Extend and create a/i textobjects
### Features:
Provides additional text object motions for handling quotes and brackets. The following motions are included:
- aq: Around any quotes.
- iq: Inside any quotes.
- ab: Around any parentheses, curly braces, and square brackets.
- ib: Inside any parentheses, curly braces, and square brackets.
Original plugin: [mini.ai](https://github.com/echasnovski/mini.ai).
### Setup:
- Add the following command to `~/.ideavimrc`: `set mini-ai`
</details>
<details>
<summary><h2>multiple-cursors: Extends multicursor support</h2></summary>
Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-cursors). Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-cursors).
@ -334,118 +153,38 @@ xmap <leader>g<C-n> <Plug>AllOccurrences
</details> </details>
<details> <details>
<summary><h2>NERDTree: Adds NERDTree navigation to the project panel</h2></summary> <summary><h2>commentary</h2></summary>
Original plugin: [NERDTree](https://github.com/preservim/nerdtree). By [Daniel Leong](https://github.com/dhleong)
Original plugin: [commentary.vim](https://github.com/tpope/vim-commentary).
### Summary:
Adds NERDTree navigation to the project panel.
### Setup: ### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'preservim/nerdtree'` - Add the following command to `~/.ideavimrc`: `Plug 'tpope/vim-commentary'`
<details> <details>
<summary>Alternative syntax</summary> <summary>Alternative syntax</summary>
<code>Plugin 'preservim/nerdtree'</code> <code>Plugin 'tpope/vim-commentary'</code>
<br/> <br/>
<code>Plug 'https://github.com/preservim/nerdtree'</code> <code>Plug 'https://github.com/tpope/vim-commentary'</code>
<br/> <br/>
<code>Plug 'nerdtree'</code> <code>Plug 'vim-commentary'</code>
<br/> <br/>
<code>set NERDTree</code> <code>Plug 'tcomment_vim'</code>
<br/>
<code>set commentary</code>
</details> </details>
### Instructions ### Instructions
[See here](NERDTree-support.md). https://github.com/tpope/vim-commentary/blob/master/doc/commentary.txt
</details> </details>
<details> <details>
<summary><h2>paragraph-motion: Extends the { and } motions to ignore whitespace on otherwise empty lines</h2></summary> <summary><h2>ReplaceWithRegister</h2></summary>
Original plugin: [vim-paragraph-motion](https://github.com/dbakker/vim-paragraph-motion).
### Summary:
Normally the { and } motions only match completely empty lines.
With this plugin lines that only contain whitespace are also matched.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'dbakker/vim-paragraph-motion'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'dbakker/vim-paragraph-motion'</code>
<br/>
<code>Plug 'https://github.com/dbakker/vim-paragraph-motion'</code>
<br/>
<code>Plug 'vim-paragraph-motion'</code>
<br/>
<code>Plug 'https://github.com/vim-scripts/Improved-paragraph-motion'</code>
<br/>
<code>Plug 'vim-scripts/Improved-paragraph-motion'</code>
<br/>
<code>Plug 'Improved-paragraph-motion'</code>
<br/>
<code>set vim-paragraph-motion</code>
</details>
### Instructions
https://github.com/dbakker/vim-paragraph-motion#vim-paragraph-motion
</details>
<details>
<summary><h2>Peekaboo: Extends " @ CTRL-r to show a popup of the register contents</h2></summary>
By Julien Phalip
Original plugin: [vim-peekaboo](https://github.com/junegunn/vim-peekaboo).
### Summary:
Peekaboo extends " and @ in normal mode and <CTRL-R> in insert mode so you can see the contents of the registers.
### Setup
Add `set peekaboo` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
</details>
<details>
<summary><h2>quick-scope: Always-on highlight for a unique character in every word on a line to help use f, F, etc.</h2></summary>
Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
### Summary:
An always-on highlight for a unique character in every word on a line to help you use f, F and family.
This plugin should help you get to any word on a line in two or three keystrokes with Vim's built-in f<char>
(which moves your cursor to <char>).
### Setup:
- Install [IdeaVim-Quickscope](https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope) plugin.
- Add the following command to `~/.ideavimrc`: `set quickscope`
### Instructions
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
</details>
<details>
<summary><h2>ReplaceWithRegister: Adds two-in-one command that replaces text with the contents of a register.</h2></summary>
By [igrekster](https://github.com/igrekster) By [igrekster](https://github.com/igrekster)
Original plugin: [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister). Original plugin: [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister).
### Summary:
This plugin offers a two-in-one command that replaces text covered by a
{motion}, entire line(s) or the current selection with the contents of a
register; the old text is deleted into the black-hole register, i.e. it's
gone. (But of course, the command can be easily undone.)
### Setup: ### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'vim-scripts/ReplaceWithRegister'` - Add the following command to `~/.ideavimrc`: `Plug 'vim-scripts/ReplaceWithRegister'`
<details> <details>
@ -472,91 +211,70 @@ https://github.com/vim-scripts/ReplaceWithRegister/blob/master/doc/ReplaceWithRe
</details> </details>
<details> <details>
<summary><h2>sneak: Jump to any location specified by two characters</h2></summary> <summary><h2>argtextobj</h2></summary>
<img src="images/sneakIcon.svg" width="80" height="80" alt="icon"/> Original plugin: [argtextobj.vim](https://www.vim.org/scripts/script.php?script_id=2699).
By [Mikhail Levchenko](https://github.com/Mishkun)
Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
Original plugin: [vim-sneak](https://github.com/justinmk/vim-sneak).
### Summary:
Jump to any location specified by two characters.
### Setup: ### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'justinmk/vim-sneak'` - Add the following command to `~/.ideavimrc`: `Plug 'vim-scripts/argtextobj.vim'`
### Instructions
* Type `s` and two chars to start sneaking in forward direction
* Type `S` and two chars to start sneaking in backward direction
* Type `;` or `,` to proceed with sneaking just as if you were using `f` or `t` commands
</details>
<details>
<summary><h2>surround: Adds provides mappings to easily delete, change, and add surroundings in pairs</h2></summary>
Original plugin: [vim-surround](https://github.com/tpope/vim-surround).
### Summary:
Surround.vim is all about "surroundings": parentheses, brackets, quotes, XML tags, and more.
The plugin provides mappings to easily delete, change and add such surroundings in pairs.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'tpope/vim-surround'`
<details> <details>
<summary>Alternative syntax</summary> <summary>Alternative syntax</summary>
<code>Plugin 'tpope/vim-surround'</code> <code>Plugin 'vim-scripts/argtextobj.vim'</code>
<br/> <br/>
<code>Plug 'https://www.vim.org/scripts/script.php?script_id=1697'</code> <code>Plug 'https://github.com/vim-scripts/argtextobj.vim'</code>
<br/> <br/>
<code>Plug 'vim-surround'</code> <code>Plug 'argtextobj.vim'</code>
<br/> <br/>
<code>set surround</code> <code>Plug 'https://www.vim.org/scripts/script.php?script_id=2699'</code>
<br/>
<code>set argtextobj</code>
</details> </details>
### Instructions ### Instructions
https://github.com/tpope/vim-surround/blob/master/doc/surround.txt By default, only the arguments inside parenthesis are considered. To extend the functionality
to other types of brackets, set `g:argtextobj_pairs` variable to a comma-separated
list of colon-separated pairs (same as VIM's `matchpairs` option), like
`let g:argtextobj_pairs="(:),{:},<:>"`. The order of pairs matters when
handling symbols that can also be operators: `func(x << 5, 20) >> 17`. To handle
this syntax parenthesis, must come before angle brackets in the list.
https://www.vim.org/scripts/script.php?script_id=2699
</details> </details>
<details> <details>
<summary><h2>Switch: Switch some text under the cursor based on regex patterns</h2></summary> <summary><h2>exchange</h2></summary>
By Julien Phalip By [fan-tom](https://github.com/fan-tom)
Original plugin: [switch.vim](https://github.com/AndrewRadev/switch.vim). Original plugin: [vim-exchange](https://github.com/tommcdo/vim-exchange).
### Summary: ### Setup:
The purpose of the plugin is to switch some text under the cursor based on regex patterns. - Add the following command to `~/.ideavimrc`: `Plug 'tommcdo/vim-exchange'`
The main entry point is a single command, :Switch. <details>
When the command is executed, <summary>Alternative syntax</summary>
the plugin looks for one of a few specific patterns under the cursor and performs a substitution depending on it. <code>Plugin 'tommcdo/vim-exchange'</code>
<br/>
### Setup <code>Plug 'https://github.com/tommcdo/vim-exchange'</code>
<br/>
Add `set switch` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc` <code>Plug 'vim-exchange'</code>
or restart the IDE. <br/>
<code>set exchange</code>
</details>
### Instructions ### Instructions
https://plugins.jetbrains.com/plugin/25899-vim-switch https://github.com/tommcdo/vim-exchange/blob/master/doc/exchange.txt
</details> </details>
<details> <details>
<summary><h2>textobj-entire: Adds mapping for selecting entire contents of file regardless of cursor position</h2></summary> <summary><h2>textobj-entire</h2></summary>
By [Alexandre Grison](https://github.com/agrison) By [Alexandre Grison](https://github.com/agrison)
Original plugin: [vim-textobj-entire](https://github.com/kana/vim-textobj-entire). Original plugin: [vim-textobj-entire](https://github.com/kana/vim-textobj-entire).
### Summary:
vim-textobj-entire is a Vim plugin to provide text objects
(ae and ie by default) to select the entire content of a buffer.
Though these are trivial operations (e.g. ggVG), text object versions are more handy,
because you do not have to be conscious of the cursor position (e.g. vae).
### Setup: ### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'kana/vim-textobj-entire'` - Add the following command to `~/.ideavimrc`: `Plug 'kana/vim-textobj-entire'`
<details> <details>
@ -577,13 +295,158 @@ https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt
</details> </details>
<details> <details>
<summary><h2>Which-Key: Displays available keybindings in popup</h2></summary> <summary><h2>highlightedyank</h2></summary>
By [KostkaBrukowa](https://github.com/KostkaBrukowa)
Original plugin: [vim-highlightedyank](https://github.com/machakann/vim-highlightedyank).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'machakann/vim-highlightedyank'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'machakann/vim-highlightedyank'</code>
<br/>
<code>Plug 'https://github.com/machakann/vim-highlightedyank'</code>
<br/>
<code>Plug 'vim-highlightedyank'</code>
<br/>
<code>set highlightedyank</code>
</details>
### Instructions
If you want to optimize highlight duration, assign a time in milliseconds:
`let g:highlightedyank_highlight_duration = "1000"`
A negative number makes the highlight persistent.
If you want to change background color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_color = "rgba(160, 160, 160, 155)"`
If you want to change text color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_foreground_color = "rgba(0, 0, 0, 255)"`
https://github.com/machakann/vim-highlightedyank/blob/master/doc/highlightedyank.txt
</details>
<details>
<summary><h2>vim-paragraph-motion</h2></summary>
Original plugin: [vim-paragraph-motion](https://github.com/dbakker/vim-paragraph-motion).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'dbakker/vim-paragraph-motion'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'dbakker/vim-paragraph-motion'</code>
<br/>
<code>Plug 'https://github.com/dbakker/vim-paragraph-motion'</code>
<br/>
<code>Plug 'vim-paragraph-motion'</code>
<br/>
<code>Plug 'https://github.com/vim-scripts/Improved-paragraph-motion'</code>
<br/>
<code>Plug 'vim-scripts/Improved-paragraph-motion'</code>
<br/>
<code>Plug 'Improved-paragraph-motion'</code>
<br/>
<code>set vim-paragraph-motion</code>
</details>
### Instructions
https://github.com/dbakker/vim-paragraph-motion#vim-paragraph-motion
</details>
<details>
<summary><h2>vim-indent-object</h2></summary>
By [Shrikant Sharat Kandula](https://github.com/sharat87)
Original plugin: [vim-indent-object](https://github.com/michaeljsmith/vim-indent-object).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'michaeljsmith/vim-indent-object'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'michaeljsmith/vim-indent-object'</code>
<br/>
<code>Plug 'https://github.com/michaeljsmith/vim-indent-object'</code>
<br/>
<code>Plug 'vim-indent-object'</code>
<br/>
<code>set textobj-indent</code>
</details>
### Instructions
https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt
</details>
<details>
<summary><h2>matchit.vim</h2></summary>
By [Martin Yzeiri](https://github.com/myzeiri)
Original plugin: [matchit.vim](https://github.com/chrisbra/matchit).
### Setup:
- Add the following command to `~/.ideavimrc`: `packadd matchit`
<details>
<summary>Alternative syntax</summary>
<code>Plug 'vim-matchit'</code>
<br/>
<code>Plug 'chrisbra/matchit'</code>
<br/>
<code>set matchit</code>
</details>
### Instructions
https://github.com/adelarsq/vim-matchit/blob/master/doc/matchit.txt
</details>
<details>
<summary><h2>IdeaVim-Quickscope</h2></summary>
Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
### Setup:
- Install [IdeaVim-Quickscope](https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope) plugin.
- Add the following command to `~/.ideavimrc`: `set quickscope`
### Instructions
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
</details>
<details>
<summary><h2>Mini.ai: Extend and create a/i textobjects (IMPORTANT: The plugin is not related with artificial intelligence)</h2></summary>
### Features:
Provides additional text object motions for handling quotes and brackets. The following motions are included:
- aq: Around any quotes.
- iq: Inside any quotes.
- ab: Around any parentheses, curly braces, and square brackets.
- ib: Inside any parentheses, curly braces, and square brackets.
Original plugin: [mini.ai](https://github.com/echasnovski/mini.ai).
### Setup:
- Add the following command to `~/.ideavimrc`: `set mini-ai`
</details>
<details>
<summary><h2>Which-Key</h2></summary>
Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key). Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
### Summary:
vim-which-key is vim port of emacs-which-key that displays available keybindings in popup.
### Setup: ### Setup:
- Install [Which-Key](https://plugins.jetbrains.com/plugin/15976-which-key) plugin. - Install [Which-Key](https://plugins.jetbrains.com/plugin/15976-which-key) plugin.
- Add the following command to `~/.ideavimrc`: `set which-key` - Add the following command to `~/.ideavimrc`: `set which-key`
@ -593,3 +456,49 @@ vim-which-key is vim port of emacs-which-key that displays available keybindings
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
</details> </details>
<details>
<summary><h2>Vim Peekaboo</h2></summary>
By Julien Phalip
Original plugin: [vim-peekaboo](https://github.com/junegunn/vim-peekaboo).
### Setup
Add `set peekaboo` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
</details>
<details>
<summary><h2>FunctionTextObj</h2></summary>
By Julien Phalip
### Setup
Add `set functiontextobj` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
</details>
<details>
<summary><h2>Switch</h2></summary>
By Julien Phalip
Original plugin: [switch.vim](https://github.com/AndrewRadev/switch.vim).
### Setup
Add `set switch` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25899-vim-switch

View File

@ -16,11 +16,11 @@
# 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=2025.1 ideaVersion=2024.3
# 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
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-47 version=chylex-45
javaVersion=21 javaVersion=21
remoteRobotVersion=0.11.23 remoteRobotVersion=0.11.23
antlrVersion=4.10.1 antlrVersion=4.10.1

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

3
gradlew vendored
View File

@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

View File

@ -20,17 +20,17 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.21") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.10")
implementation("io.ktor:ktor-client-core:3.1.3") implementation("io.ktor:ktor-client-core:3.1.1")
implementation("io.ktor:ktor-client-cio:3.1.3") implementation("io.ktor:ktor-client-cio:3.1.1")
implementation("io.ktor:ktor-client-content-negotiation:3.1.3") implementation("io.ktor:ktor-client-content-negotiation:3.1.1")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.3") implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.1")
implementation("io.ktor:ktor-client-auth:3.1.3") implementation("io.ktor:ktor-client-auth:3.1.1")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // This is needed for jgit to connect to ssh
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.0.202503040940-r") implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r")
implementation("com.vdurmont:semver4j:3.1.0") implementation("com.vdurmont:semver4j:3.1.0")
} }

View File

@ -43,8 +43,6 @@ val knownPlugins = setOf(
"com.julienphalip.ideavim.peekaboo", // https://plugins.jetbrains.com/plugin/25776-vim-peekaboo "com.julienphalip.ideavim.peekaboo", // https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
"com.julienphalip.ideavim.switch", // https://plugins.jetbrains.com/plugin/25899-vim-switch "com.julienphalip.ideavim.switch", // https://plugins.jetbrains.com/plugin/25899-vim-switch
"com.julienphalip.ideavim.functiontextobj", // https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj "com.julienphalip.ideavim.functiontextobj", // https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
"com.miksuki.HighlightCursor", // https://plugins.jetbrains.com/plugin/26743-highlightcursor
"com.ugarosa.idea.edgemotion", // https://plugins.jetbrains.com/plugin/27211-edgemotion
) )
suspend fun main() { suspend fun main() {

View File

@ -355,12 +355,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (unsubscribe) { if (unsubscribe) {
VimListenerManager.INSTANCE.turnOff(); VimListenerManager.INSTANCE.turnOff();
} }
// Use getServiceIfCreated to avoid creating the service during the dispose (this is prohibited by the platform) injector.getCommandLine().fullReset();
@Nullable VimCommandLineService service =
ApplicationManager.getApplication().getServiceIfCreated(VimCommandLineService.class);
if (service != null) {
service.fullReset();
}
// Unregister vim actions in command mode // Unregister vim actions in command mode
RegisterActions.unregisterActions(); RegisterActions.unregisterActions();
@ -376,7 +371,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (isEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) { if (isEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) {
stateUpdated = true; stateUpdated = true;
if (SystemInfo.isMac) { if (SystemInfo.isMac) {
final Boolean enabled = MacKeyRepeat.INSTANCE.isEnabled(); final MacKeyRepeat keyRepeat = MacKeyRepeat.getInstance();
final Boolean enabled = keyRepeat.isEnabled();
final Boolean isKeyRepeat = getEditor().isKeyRepeat(); final Boolean isKeyRepeat = getEditor().isKeyRepeat();
if ((enabled == null || !enabled) && (isKeyRepeat == null || isKeyRepeat)) { if ((enabled == null || !enabled) && (isKeyRepeat == null || isKeyRepeat)) {
// This system property is used in IJ ui robot to hide the startup tips // This system property is used in IJ ui robot to hide the startup tips
@ -386,7 +382,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (showNotification) { if (showNotification) {
if (VimPlugin.getNotifications().enableRepeatingMode() == Messages.YES) { if (VimPlugin.getNotifications().enableRepeatingMode() == Messages.YES) {
getEditor().setKeyRepeat(true); getEditor().setKeyRepeat(true);
MacKeyRepeat.INSTANCE.setEnabled(true); keyRepeat.setEnabled(true);
} }
else { else {
getEditor().setKeyRepeat(false); getEditor().setKeyRepeat(false);

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action package com.maddyhome.idea.vim.action
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import com.intellij.codeInsight.completion.CompletionService
import com.intellij.codeInsight.lookup.LookupManager import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.ActionUpdateThread
@ -183,6 +182,9 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
} }
if (editor.inInsertMode) { if (editor.inInsertMode) {
if (keyCode == KeyEvent.VK_ENTER) {
return ActionEnableStatus.no("Enter action in insert mode", LogLevel.INFO)
}
if (keyCode == KeyEvent.VK_TAB) { if (keyCode == KeyEvent.VK_TAB) {
// TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view // TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
// There are multiple actions registered for VK_TAB. The important items, in order, are this, the Live // There are multiple actions registered for VK_TAB. The important items, in order, are this, the Live
@ -209,10 +211,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
return ActionEnableStatus.yes("Vim only editor keys", LogLevel.INFO) return ActionEnableStatus.yes("Vim only editor keys", LogLevel.INFO)
} }
if (CompletionService.getCompletionService().currentCompletion != null) {
return ActionEnableStatus.no("Code completion active", LogLevel.INFO)
}
val savedShortcutConflicts = VimPlugin.getKey().savedShortcutConflicts val savedShortcutConflicts = VimPlugin.getKey().savedShortcutConflicts
val info = savedShortcutConflicts[keyStroke] val info = savedShortcutConflicts[keyStroke]
return when (info?.forEditor(editor.vim)) { return when (info?.forEditor(editor.vim)) {

View File

@ -0,0 +1,78 @@
/*
* 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
import org.jetbrains.annotations.ApiStatus
/**
* COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r
*/
@Deprecated("Use `injector.vimState`")
@ApiStatus.ScheduledForRemoval
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
}
}
@get:Deprecated(
"Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.commandBuilder",
"com.maddyhome.idea.vim.KeyHandler"
)
)
@get:ApiStatus.ScheduledForRemoval
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`")
@ApiStatus.ScheduledForRemoval
fun getInstance(editor: Editor): CommandState {
return CommandState(injector.vimState)
}
}
}

View File

@ -11,17 +11,16 @@ package com.maddyhome.idea.vim.customization.feature.terminal
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isAlternateBufferEditor import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isAlternateBufferEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isAlternateBufferModelEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isOutputEditor import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isOutputEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isOutputModelEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isPromptEditor import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isPromptEditor
/** /**
* The only implementation is defined right here. * The only implementation is defined right here.
*/ */
// [VERSION UPDATE] 2025.1+ Add 2 new predicates
internal class IdeaVimTerminalDisablerExtension : IdeaVimDisablerExtensionPoint { internal class IdeaVimTerminalDisablerExtension : IdeaVimDisablerExtensionPoint {
override fun isDisabledForEditor(editor: Editor): Boolean { override fun isDisabledForEditor(editor: Editor): Boolean {
return editor.isPromptEditor || editor.isOutputEditor || editor.isAlternateBufferEditor return editor.isPromptEditor || editor.isOutputEditor || editor.isAlternateBufferEditor
|| editor.isOutputModelEditor || editor.isAlternateBufferModelEditor // || editor.isOutputModelEditor || editor.isAlternateBufferModelEditor
} }
} }

View File

@ -214,13 +214,7 @@ object VimExtensionFacade {
/** Set the current contents of the given register */ /** Set the current contents of the given register */
@JvmStatic @JvmStatic
fun setRegisterForCaret( fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
editor: VimEditor,
context: ExecutionContext,
register: Char,
caret: ImmutableVimCaret,
keys: List<KeyStroke?>?,
) {
caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList()) caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
} }

View File

@ -221,16 +221,14 @@ internal class CommentaryExtension : VimExtension {
val endOffset = editor.vim.getLineEndOffset(logicalLine, true) val endOffset = editor.vim.getLineEndOffset(logicalLine, true)
val startElement = file.findElementAt(startOffset) ?: return false val startElement = file.findElementAt(startOffset) ?: return false
var next: PsiElement? = startElement var next: PsiElement? = startElement
var hasComment = false
while (next != null && next.textRange.startOffset <= endOffset) { while (next != null && next.textRange.startOffset <= endOffset) {
when { if (next !is PsiWhiteSpace && !isComment(next)) {
next is PsiWhiteSpace -> {} // Skip whitespace elementl return false
isComment(next) -> hasComment = true // Mark when we find a comment
else -> return false // Non-comment content found, exit early
} }
next = PsiTreeUtil.nextLeaf(next, true) next = PsiTreeUtil.nextLeaf(next, true)
} }
return hasComment
return true
} }
private fun isComment(element: PsiElement) = private fun isComment(element: PsiElement) =

View File

@ -11,7 +11,6 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.application.runWriteAction
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.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.VimCaret import com.maddyhome.idea.vim.api.VimCaret
@ -142,10 +141,6 @@ internal class VimSurroundExtension : VimExtension {
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
editor.exitVisualMode() editor.exitVisualMode()
// Reset the key handler so that the command trie is updated for the new mode (Normal)
// TODO: This should probably be handled by ToHandlerMapping.execute
KeyHandler.getInstance().reset(editor)
} }
} }
} }
@ -174,8 +169,8 @@ internal class VimSurroundExtension : VimExtension {
val surroundings = editor.sortedCarets() val surroundings = editor.sortedCarets()
.map { .map {
val oldValue: List<KeyStroke>? = getRegisterForCaret(editor, context, REGISTER, it) val oldValue: List<KeyStroke>? = getRegisterForCaret(editor, context, REGISTER, it)
setRegisterForCaret(editor, context, REGISTER, it, null) setRegisterForCaret(REGISTER, it, null)
SurroundingInfo(editor, context, it, null, oldValue, false) SurroundingInfo(it, null, oldValue, false)
} }
// Delete surrounding's content // Delete surrounding's content
@ -264,16 +259,9 @@ internal class VimSurroundExtension : VimExtension {
} }
} }
private data class SurroundingInfo( private data class SurroundingInfo(val caret: VimCaret, var innerText: List<KeyStroke>?, val oldRegisterContent: List<KeyStroke>?, var isValidSurrounding: Boolean) {
val editor: VimEditor,
val context: ExecutionContext,
val caret: VimCaret,
var innerText: List<KeyStroke>?,
val oldRegisterContent: List<KeyStroke>?,
var isValidSurrounding: Boolean,
) {
fun restoreRegister() { fun restoreRegister() {
setRegisterForCaret(editor, context, REGISTER, caret, oldRegisterContent) setRegisterForCaret(REGISTER, caret, oldRegisterContent)
} }
} }

View File

@ -7,7 +7,7 @@
*/ */
package com.maddyhome.idea.vim.group package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.actions.AsyncActionExecutionService import com.intellij.codeInsight.actions.AsyncActionExecutionService.Companion.getInstance
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
@ -15,7 +15,6 @@ 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.actionSystem.TypedActionHandler
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
@ -34,7 +33,7 @@ import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.commandContinuation import com.maddyhome.idea.vim.handler.commandContinuation
import com.maddyhome.idea.vim.helper.inInsertMode import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.key.KeyHandlerKeeper 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.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimCopiedText import com.maddyhome.idea.vim.newapi.IjVimCopiedText
@ -62,21 +61,8 @@ class ChangeGroup : VimChangeGroupBase() {
} }
override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) { override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
doType(vimEditor, context) {
it.execute(vimEditor.ij, key, context.ij)
}
}
override fun type(vimEditor: VimEditor, context: ExecutionContext, string: String) {
doType(vimEditor, context) { handler ->
string.forEach { char ->
handler.execute(vimEditor.ij, char, context.ij)
}
}
}
private fun doType(vimEditor: VimEditor, context: ExecutionContext, action: (TypedActionHandler) -> Unit) {
val editor = (vimEditor as IjVimEditor).editor val editor = (vimEditor as IjVimEditor).editor
val ijContext = context.ij
val doc = vimEditor.editor.document val doc = vimEditor.editor.document
val undo = injector.undo val undo = injector.undo
@ -89,9 +75,8 @@ class ChangeGroup : VimChangeGroupBase() {
} }
CommandProcessor.getInstance().executeCommand( CommandProcessor.getInstance().executeCommand(
editor.project, { editor.project, {
ApplicationManager.getApplication().runWriteAction { ApplicationManager.getApplication()
action(KeyHandlerKeeper.getInstance().originalHandler) .runWriteAction { getInstance().originalHandler.execute(editor, key, ijContext) }
}
}, "", doc, }, "", doc,
UndoConfirmationPolicy.DEFAULT, doc UndoConfirmationPolicy.DEFAULT, doc
) )
@ -165,7 +150,7 @@ class ChangeGroup : VimChangeGroupBase() {
var copiedText: IjVimCopiedText? = null var copiedText: IjVimCopiedText? = null
try { try {
if (injector.registerGroup.isPrimaryRegisterSupported()) { if (injector.registerGroup.isPrimaryRegisterSupported()) {
copiedText = injector.clipboardManager.getPrimaryContent() as IjVimCopiedText copiedText = injector.clipboardManager.getPrimaryContent(editor, context) as IjVimCopiedText
} }
} catch (e: Exception) { } catch (e: Exception) {
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection // FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection
@ -184,7 +169,7 @@ class ChangeGroup : VimChangeGroupBase() {
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line) restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
} }
if (project != null) { if (project != null) {
AsyncActionExecutionService.getInstance(project) getInstance(project)
.withExecutionAfterAction(IdeActions.ACTION_EDITOR_AUTO_INDENT_LINES, actionExecution, afterAction) .withExecutionAfterAction(IdeActions.ACTION_EDITOR_AUTO_INDENT_LINES, actionExecution, afterAction)
} else { } else {
actionExecution.invoke() actionExecution.invoke()

View File

@ -31,8 +31,10 @@ open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(sco
// Temporary options to control work-in-progress behaviour // Temporary options to control work-in-progress behaviour
var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks) var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
var oldundo: Boolean by optionProperty(IjOptions.oldundo) var oldundo: Boolean by optionProperty(IjOptions.oldundo)
var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps) var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
} }
/** /**

View File

@ -141,8 +141,12 @@ object IjOptions {
// Temporary feature flags during development, not really intended for external use // Temporary feature flags during development, not really intended for external use
val closenotebooks: ToggleOption = val closenotebooks: ToggleOption =
addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true)) addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
val commandOrMotionAnnotation: ToggleOption =
addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true)) val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true)) val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
val vimscriptFunctionAnnotation: ToggleOption =
addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which // This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
// derives from Option<VimInt> // derives from Option<VimInt>

View File

@ -58,7 +58,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
public void registerRequiredShortcutKeys(@NotNull VimEditor editor) { public void registerRequiredShortcutKeys(@NotNull VimEditor editor) {
EventFacade.getInstance() EventFacade.getInstance()
.registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(getRequiredShortcutKeys()), .registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(getRequiredShortcutKeys()),
((IjVimEditor)editor).getEditor().getContentComponent()); ((IjVimEditor)editor).getEditor().getComponent());
} }
public void registerShortcutsForLookup(@NotNull LookupImpl lookup) { public void registerShortcutsForLookup(@NotNull LookupImpl lookup) {
@ -69,7 +69,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
void unregisterShortcutKeys(@NotNull VimEditor editor) { void unregisterShortcutKeys(@NotNull VimEditor editor) {
EventFacade.getInstance().unregisterCustomShortcutSet(VimShortcutKeyAction.getInstance(), EventFacade.getInstance().unregisterCustomShortcutSet(VimShortcutKeyAction.getInstance(),
((IjVimEditor)editor).getEditor().getContentComponent()); ((IjVimEditor)editor).getEditor().getComponent());
} }
@Override @Override

View File

@ -37,7 +37,6 @@ 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.handler.KeyMapIssue import com.maddyhome.idea.vim.handler.KeyMapIssue
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.icons.VimIcons
import com.maddyhome.idea.vim.key.ShortcutOwner import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
@ -136,30 +135,8 @@ internal class NotificationService(private val project: Project?) {
).notify(project) ).notify(project)
} }
/** fun notifyActionId(id: String?, candidates: List<String>? = null) {
* Shows a notification that the user can reenable IdeaVim by clicking on the IdeaVim icon in the status bar. ActionIdNotifier.notifyActionId(id, project, candidates)
*/
fun showReenableNotification(project: Project) {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"IdeaVim has been disabled. You can reenable it by clicking on the gray IdeaVim icon in the status bar.",
NotificationType.INFORMATION,
)
notification.icon = VimIcons.IDEAVIM_DISABLED
notification.addAction(object : DumbAwareAction("Reenable IdeaVim") {
override fun actionPerformed(e: AnActionEvent) {
VimPlugin.setEnabled(true)
notification.expire()
}
})
notification.notify(project)
}
fun notifyActionId(id: String?, candidates: List<String>? = null, intentionName: String?) {
ActionIdNotifier.notifyActionId(id, project, candidates, intentionName)
} }
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) { fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
@ -237,15 +214,12 @@ internal class NotificationService(private val project: Project?) {
object ActionIdNotifier { object ActionIdNotifier {
private var notification: Notification? = null private var notification: Notification? = null
fun notifyActionId(id: String?, project: Project?, candidates: List<String>? = null, intentionName: String? = null) { fun notifyActionId(id: String?, project: Project?, candidates: List<String>? = null) {
notification?.expire() notification?.expire()
val possibleIDs = candidates?.distinct()?.sorted() val possibleIDs = candidates?.distinct()?.sorted()
val content = when { val content = when {
id != null -> "Action ID: <code>$id</code><br><br>" id != null -> "Action ID: <code>$id</code><br><br>"
possibleIDs.isNullOrEmpty() && !intentionName.isNullOrEmpty() -> {
"Intention \"$intentionName\" does not have an action ID.<br><br>"
}
possibleIDs.isNullOrEmpty() -> "<i>Cannot detect action ID</i><br><br>" possibleIDs.isNullOrEmpty() -> "<i>Cannot detect action ID</i><br><br>"
possibleIDs.size == 1 -> "Possible action ID: <code>${possibleIDs[0]}</code><br><br>" possibleIDs.size == 1 -> "Possible action ID: <code>${possibleIDs[0]}</code><br><br>"
else -> { else -> {

View File

@ -44,7 +44,7 @@ internal class SystemMarks {
internal fun Project.createLineBookmark(editor: Editor, line: Int, mnemonic: Char): LineBookmark? { internal fun Project.createLineBookmark(editor: Editor, line: Int, mnemonic: Char): LineBookmark? {
val bookmarksManager = BookmarksManager.getInstance(this) ?: return null val bookmarksManager = BookmarksManager.getInstance(this) ?: return null
val lineBookmarkProvider = LineBookmarkProvider.Util.find(this) ?: return null val lineBookmarkProvider = LineBookmarkProvider.find(this) ?: return null
val bookmark = lineBookmarkProvider.createBookmark(editor, line) as LineBookmark? ?: return null val bookmark = lineBookmarkProvider.createBookmark(editor, line) as LineBookmark? ?: return null
val type = BookmarkType.get(mnemonic) val type = BookmarkType.get(mnemonic)
if (type == BookmarkType.DEFAULT) return null if (type == BookmarkType.DEFAULT) return null

View File

@ -19,7 +19,6 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.util.PlatformUtils
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.VimCaret import com.maddyhome.idea.vim.api.VimCaret
@ -207,9 +206,8 @@ internal class PutGroup : VimPutBase() {
startOffset: Int, startOffset: Int,
endOffset: Int, endOffset: Int,
): Int { ): Int {
// Temp fix for VIM-2808 for Rider and Clion. Should be removed after rider will fix it's issues // Temp fix for VIM-2808. Should be removed after rider will fix it's issues
// Disable for client due to VIM-3857 if (isRider() || isClionNova()) return endOffset
if (isRider() || isClionNova() || PlatformUtils.isJetBrainsClient()) return endOffset
val startLine = editor.offsetToBufferPosition(startOffset).line val startLine = editor.offsetToBufferPosition(startOffset).line
val endLine = editor.offsetToBufferPosition(endOffset - 1).line val endLine = editor.offsetToBufferPosition(endOffset - 1).line

View File

@ -96,7 +96,7 @@ internal object IdeaSelectionControl {
} else { } else {
logger.debug("None of carets have selection. State before adjustment: ${editor.vim.mode}") logger.debug("None of carets have selection. State before adjustment: ${editor.vim.mode}")
if (editor.vim.inVisualMode) editor.vim.exitVisualMode() if (editor.vim.inVisualMode) editor.vim.exitVisualMode()
if (editor.vim.inSelectMode) editor.vim.exitSelectMode(false) if (editor.vim.inSelectMode) editor.exitSelectMode(false)
if (editor.vim.inNormalMode) { if (editor.vim.inNormalMode) {
activateMode(editor, chooseNonSelectionMode(editor)) activateMode(editor, chooseNonSelectionMode(editor))

View File

@ -17,6 +17,7 @@ import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.keymap.ex.KeymapManagerEx 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.jetbrains.rd.util.ConcurrentHashMap
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.api.key import com.maddyhome.idea.vim.api.key
@ -27,8 +28,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.annotations.NonNls
import java.util.concurrent.ConcurrentHashMap
// We use alarm with delay to avoid many actions in case many events are fired at the same time // We use alarm with delay to avoid many actions in case many events are fired at the same time
@ -68,7 +67,11 @@ internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
check(correctorRequester.tryEmit(Unit)) check(correctorRequester.tryEmit(Unit))
} }
override fun shortcutsChanged(keymap: Keymap, actionIds: @NonNls Collection<String>, fromSettings: Boolean) { override fun shortcutChanged(keymap: Keymap, actionId: String) {
check(correctorRequester.tryEmit(Unit))
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
check(correctorRequester.tryEmit(Unit)) check(correctorRequester.tryEmit(Unit))
} }
} }

View File

@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.annotations.NonNls
import javax.swing.KeyStroke import javax.swing.KeyStroke
// We use alarm with delay to avoid many notifications in case many events are fired at the same time // We use alarm with delay to avoid many notifications in case many events are fired at the same time
@ -68,7 +67,11 @@ internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
check(keyCheckRequests.tryEmit(Unit)) check(keyCheckRequests.tryEmit(Unit))
} }
override fun shortcutsChanged(keymap: Keymap, actionIds: @NonNls Collection<String>, fromSettings: Boolean) { override fun shortcutChanged(keymap: Keymap, actionId: String) {
check(keyCheckRequests.tryEmit(Unit))
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
check(keyCheckRequests.tryEmit(Unit)) check(keyCheckRequests.tryEmit(Unit))
} }
} }

View File

@ -60,7 +60,7 @@ internal fun Editor.updateCaretsVisualAttributes() {
* Used when Vim emulation is disabled * Used when Vim emulation is disabled
*/ */
internal fun Editor.removeCaretsVisualAttributes() { internal fun Editor.removeCaretsVisualAttributes() {
caretModel.allCarets.forEach { it.visualAttributes = CaretVisualAttributes.getDefault() } caretModel.allCarets.forEach { it.visualAttributes = CaretVisualAttributes.DEFAULT }
} }
internal fun Editor.hasBlockOrUnderscoreCaret() = isBlockCursorOverride() || internal fun Editor.hasBlockOrUnderscoreCaret() = isBlockCursorOverride() ||
@ -95,8 +95,8 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
// 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
// NOTE: At the moment, this causes project leak in tests // NOTE: At the moment, this causes project leak in tests
// IJPL-928 - this will be fixed in 2025.2 // IJPL-928 - this will be fixed in 2024.2
// [VERSION UPDATE] 2025.2 - remove if wrapping // [VERSION UPDATE] 2024.2 - remove if wrapping
if (!ApplicationManager.getApplication().isUnitTestMode) { if (!ApplicationManager.getApplication().isUnitTestMode) {
(this as? EditorEx)?.setCaretVisible(true) (this as? EditorEx)?.setCaretVisible(true)
} }

View File

@ -0,0 +1,67 @@
/*
* 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.helper
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
@Deprecated("Do not use context wrappers, use existing provided contexts. If no context available, use `injector.getExecutionContextManager().getEditorExecutionContext(editor)`")
internal class EditorDataContext @Deprecated("Please use `init` method") constructor(
private val editor: Editor,
private val editorContext: DataContext,
private val contextDelegate: DataContext? = null,
) : DataContext, UserDataHolder {
/**
* Returns the object corresponding to the specified data identifier. Some of the supported data identifiers are
* defined in the [PlatformDataKeys] class.
*
* @param dataId the data identifier for which the value is requested.
* @return the value, or null if no value is available in the current context for this identifier.
*/
override fun getData(dataId: String): Any? = when {
PlatformDataKeys.EDITOR.name == dataId -> editor
PlatformDataKeys.PROJECT.name == dataId -> editor.project
PlatformDataKeys.VIRTUAL_FILE.name == dataId -> EditorHelper.getVirtualFile(editor)
else -> editorContext.getData(dataId) ?: contextDelegate?.getData(dataId)
}
override fun <T : Any?> getUserData(key: Key<T>): T? {
return if (contextDelegate is UserDataHolder) {
contextDelegate.getUserData(key)
} else {
null
}
}
override fun <T : Any?> putUserData(key: Key<T>, value: T?) {
if (contextDelegate is UserDataHolder) {
contextDelegate.putUserData(key, value)
}
}
companion object {
@Suppress("DEPRECATION")
@JvmStatic
fun init(editor: Editor, contextDelegate: DataContext? = null): EditorDataContext {
val editorContext = EditorUtil.getEditorDataContext(editor)
return if (contextDelegate is EditorDataContext) {
if (editor === contextDelegate.editor) {
contextDelegate
} else {
EditorDataContext(editor, editorContext, contextDelegate.contextDelegate)
}
} else {
EditorDataContext(editor, editorContext, contextDelegate)
}
}
}
}

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.helper; package com.maddyhome.idea.vim.helper;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.editor.impl.EditorImpl;
@ -16,7 +15,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileUtil;
import com.intellij.testFramework.LightVirtualFile; import com.intellij.testFramework.LightVirtualFile;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt; import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor; import com.maddyhome.idea.vim.api.VimEditor;
@ -654,21 +652,7 @@ public class EditorHelper {
*/ */
public static boolean isFileEditor(@NotNull Editor editor) { public static boolean isFileEditor(@NotNull Editor editor) {
final VirtualFile virtualFile = getVirtualFile(editor); final VirtualFile virtualFile = getVirtualFile(editor);
if (virtualFile == null) return false; return virtualFile != null && !(virtualFile instanceof LightVirtualFile);
if (virtualFile instanceof LightVirtualFile) {
var hostVirtualFile = getHostFileFromInjectedFile(virtualFile);
if (hostVirtualFile == null) return false;
return !(hostVirtualFile instanceof LightVirtualFile);
}
return true;
}
private static @Nullable VirtualFile getHostFileFromInjectedFile(@NotNull VirtualFile virtualFile) {
final var vf = VirtualFileUtil.originalFileOrSelf(virtualFile);
if (vf instanceof VirtualFileWindow) {
return ((VirtualFileWindow)vf).getDelegate();
}
return null;
} }
/** /**

View File

@ -14,7 +14,6 @@ import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.util.ui.table.JBTableRowEditor import com.intellij.util.ui.table.JBTableRowEditor
@ -43,12 +42,7 @@ internal val Editor.isIdeaVimDisabledHere: Boolean
return (ideaVimDisabledInDialog(ideaVimSupportValue) && isInDialog()) || return (ideaVimDisabledInDialog(ideaVimSupportValue) && isInDialog()) ||
!ClientId.isCurrentlyUnderLocalId || // CWM-927 !ClientId.isCurrentlyUnderLocalId || // CWM-927
(ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine()) || (ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine()) ||
IdeaVimDisablerExtensionPoint.isDisabledForEditor(this) || IdeaVimDisablerExtensionPoint.isDisabledForEditor(this)
isAiChat() // VIM-3786
}
private fun Editor.isAiChat(): Boolean {
return EditorHelper.getVirtualFile(this)?.name?.contains("AIAssistantInput") == true
} }
private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean { private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean {
@ -77,19 +71,6 @@ internal fun Editor.isPrimaryEditor(): Boolean {
return fileEditorManager.allEditors.any { fileEditor -> this == EditorUtil.getEditorEx(fileEditor) } return fileEditorManager.allEditors.any { fileEditor -> this == EditorUtil.getEditorEx(fileEditor) }
} }
/**
* Checks if the editor should be treated like a terminal. I.e. switch to Insert mode automatically
*
* A "terminal" editor is an editor used for purposes other than mainstream editing, such as a terminal, console, log
* viewer, etc. In this instance, the editor is writable, the document is writable, but it's not backed by a real file
* and it's not the diff viewer. We also check that if it's an injected language fragment backed by a real file.
*/
internal fun Editor.isTerminalEditor(): Boolean {
return !isViewer
&& document.isWritable
&& this.editorKind == EditorKind.CONSOLE
}
// Optimized clone of com.intellij.ide.ui.laf.darcula.DarculaUIUtil.isTableCellEditor // Optimized clone of com.intellij.ide.ui.laf.darcula.DarculaUIUtil.isTableCellEditor
private fun isTableCellEditor(c: Component): Boolean { private fun isTableCellEditor(c: Component): Boolean {
return (java.lang.Boolean.TRUE == (c as JComponent).getClientProperty("JComboBox.isTableCellEditor")) || return (java.lang.Boolean.TRUE == (c as JComponent).getClientProperty("JComboBox.isTableCellEditor")) ||

View File

@ -8,15 +8,19 @@
package com.maddyhome.idea.vim.helper package com.maddyhome.idea.vim.helper
import com.intellij.execution.actions.StopAction import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.ActionManager 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.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.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.SimpleDataContext
import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.application.ex.ApplicationManagerEx 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
@ -24,7 +28,9 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.thisLogger 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.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.registry.Registry
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
@ -32,11 +38,14 @@ import com.maddyhome.idea.vim.api.VimActionExecutor
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
import com.maddyhome.idea.vim.ide.isRider
import com.maddyhome.idea.vim.newapi.IjNativeAction import com.maddyhome.idea.vim.newapi.IjNativeAction
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.runFromVimKey
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.Component import java.awt.Component
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.SwingUtilities
@Service @Service
internal class IjActionExecutor : VimActionExecutor { internal class IjActionExecutor : VimActionExecutor {
@ -55,11 +64,8 @@ internal class IjActionExecutor : VimActionExecutor {
override val ACTION_EXPAND_REGION_RECURSIVELY: String override val ACTION_EXPAND_REGION_RECURSIVELY: String
get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY
override val ACTION_EXPAND_COLLAPSE_TOGGLE: String override val ACTION_EXPAND_COLLAPSE_TOGGLE: String
get() = IdeActions.ACTION_EXPAND_COLLAPSE_TOGGLE_REGION // [VERSION UPDATE] 2024.3+ Replace raw "ExpandCollapseToggleAction" with IdeActions.ACTION_EXPAND_COLLAPSE_TOGGLE_REGION from the platform.
override val ACTION_UNDO: String get() = "ExpandCollapseToggleAction"
get() = IdeActions.ACTION_UNDO
override val ACTION_REDO: String
get() = IdeActions.ACTION_REDO
var isRunningActionFromVim: Boolean = false var isRunningActionFromVim: Boolean = false
@ -71,28 +77,78 @@ internal class IjActionExecutor : VimActionExecutor {
} }
val ijAction = (action as IjNativeAction).action val ijAction = (action as IjNativeAction).action
if (Registry.`is`("ideavim.old.action.execution", true) || isRider()) {
return manualActionExecution(context, ijAction)
} else {
try { try {
isRunningActionFromVim = true isRunningActionFromVim = true
// The context component should be editor. This is especially important when running the `:action` commands // The context component should be editor. This is especially important when running the `:action` commands
// because at the moment of execution, the focused component is Ex Field, not editor. // because at the moment of execution, the focused component is Ex Field, not editor.
val contextComponent = editor?.ij?.contentComponent val contextComponent = editor?.ij?.contentComponent
val place = ijAction.choosePlace() val res = ActionManager.getInstance().tryToExecute(ijAction, null, contextComponent, "IdeaVim", true)
val res = ActionManager.getInstance().tryToExecute(ijAction, null, contextComponent, place, true)
res.waitFor(5_000) res.waitFor(5_000)
return res.isDone return res.isDone
} finally { } finally {
isRunningActionFromVim = false isRunningActionFromVim = false
} }
} }
}
// Note: We should find a proper place for the IdeaVim actions private fun manualActionExecution(
// Currently, we use "IdeaVim" except a few actions context: ExecutionContext,
private fun AnAction.choosePlace(): String { ijAction: AnAction,
// StopAction works fine if `StopAction.isPlaceGlobal` returns true ): Boolean {
// Or if there is a specific data stored in the context. This data, however, is stored /**
// only if the run window is in focus. * Data context that defines that some action was started from IdeaVim.
if (this is StopAction) return ActionPlaces.ACTION_SEARCH * You can call use [runFromVimKey] key to define if intellij action was started from IdeaVim
return "IdeaVim" */
val dataContext = SimpleDataContext.getSimpleContext(runFromVimKey, true, context.ij)
val actionId = ActionManager.getInstance().getId(ijAction)
@Suppress("removal", "DEPRECATION") val event = AnActionEvent(
null,
dataContext,
ActionPlaces.KEYBOARD_SHORTCUT,
ijAction.templatePresentation.clone(),
ActionManager.getInstance(),
0,
)
Utils.initUpdateSession(event)
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems
// because rider uses an async update method. See VIM-1819.
// This method executes inside lastUpdateAndCheckDumb
// Another related issue: VIM-2604
// This is a hack to fix the tests and fix VIM-3332
// We should get rid of it in VIM-3376
if (actionId == "RunClass" || actionId == IdeActions.ACTION_COMMENT_LINE || actionId == IdeActions.ACTION_COMMENT_BLOCK) {
@Suppress("removal", "OverrideOnly", "DEPRECATION")
ijAction.beforeActionPerformedUpdate(event)
if (!event.presentation.isEnabled) return false
} else {
if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false
}
if (ijAction is ActionGroup && !event.presentation.isPerformGroup) {
// Some ActionGroups should not be performed but shown as a popup
val popup = JBPopupFactory.getInstance()
.createActionGroupPopup(event.presentation.text, ijAction, dataContext, false, null, -1)
val component = dataContext.getData(PlatformDataKeys.CONTEXT_COMPONENT)
if (component != null) {
val window = SwingUtilities.getWindowAncestor(component)
if (window != null) {
popup.showInCenterOf(window)
}
return true
}
popup.showInFocusCenter()
return true
} else {
performDumbAwareWithCallbacks(ijAction, event) {
@Suppress("OverrideOnly")
ijAction.actionPerformed(event)
}
return true
}
} }
/** /**

View File

@ -0,0 +1,75 @@
/*
* 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.helper;
import com.google.common.io.CharStreams;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* @author vlan
*/
public class MacKeyRepeat {
@VimNlsSafe public static final String FMT = "defaults %s -globalDomain ApplePressAndHoldEnabled";
@NotNull private static final MacKeyRepeat INSTANCE = new MacKeyRepeat();
@NonNls private static final String EXEC_COMMAND = "launchctl stop com.apple.SystemUIServer.agent";
@NonNls private static final String delete = "delete";
@NonNls private static final String write = "write";
@NonNls private static final String read = "read";
public static @NotNull MacKeyRepeat getInstance() {
return INSTANCE;
}
private static @NotNull String read(@NotNull InputStream stream) throws IOException {
return CharStreams.toString(new InputStreamReader(stream));
}
public @Nullable Boolean isEnabled() {
final String command = String.format(FMT, read);
try {
final Process process = Runtime.getRuntime().exec(command);
final String data = read(process.getInputStream()).trim();
try {
return Integer.parseInt(data) == 0;
}
catch (NumberFormatException e) {
return null;
}
}
catch (IOException e) {
return null;
}
}
public void setEnabled(@Nullable Boolean value) {
final String command;
if (value == null) {
command = String.format(FMT, delete);
}
else {
final String arg = value ? "0" : "1";
command = String.format(FMT, write) + " " + arg;
}
try {
final Runtime runtime = Runtime.getRuntime();
final Process defaults = runtime.exec(command);
defaults.waitFor();
final Process restartSystemUI = runtime.exec(EXEC_COMMAND);
restartSystemUI.waitFor();
}
catch (IOException | InterruptedException ignored) {
}
}
}

View File

@ -1,54 +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.helper
import com.google.common.io.CharStreams
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
object MacKeyRepeat {
var isEnabled: Boolean?
get() {
return try {
val process = Runtime.getRuntime().exec(READ_COMMAND)
val data = read(process.inputStream).trim().toIntOrNull() ?: return null
data == 0
} catch (_: IOException) {
null
}
}
set(value) {
val command: Array<String>
if (value == null) {
command = DELETE_COMMAND
} else {
val arg = if (value) "0" else "1"
command = WRITE_COMMAND + arg
}
try {
val runtime = Runtime.getRuntime()
val defaults = runtime.exec(command)
defaults.waitFor()
val restartSystemUI: Process = runtime.exec(EXEC_COMMAND)
restartSystemUI.waitFor()
} catch (_: IOException) {
} catch (_: InterruptedException) {
}
}
private val EXEC_COMMAND = arrayOf("launchctl", "stop", "com.apple.SystemUIServer.agent")
private val READ_COMMAND = arrayOf("defaults", "read", "-globalDomain", "ApplePressAndHoldEnabled")
private val WRITE_COMMAND = arrayOf("defaults", "write", "-globalDomain", "ApplePressAndHoldEnabled")
private val DELETE_COMMAND = arrayOf("defaults", "delete", "-globalDomain", "ApplePressAndHoldEnabled")
@Throws(IOException::class)
private fun read(stream: InputStream): String {
return CharStreams.toString(InputStreamReader(stream))
}
}

View File

@ -24,6 +24,28 @@ import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inSelectMode import com.maddyhome.idea.vim.state.mode.inSelectMode
/** [adjustCaretPosition] - if true, caret will be moved one char left if it's on the line end */
internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
val vimEditor = this.vim
if (!vimEditor.inSelectMode) return
vimEditor.mode = vimEditor.mode.returnTo
SelectionVimListenerSuppressor.lock().use {
this.caretModel.allCarets.forEach {
// NOTE: I think it should be write action, but the exception shows only an absence of the read action
injector.application.runReadAction { it.removeSelection() }
it.vim.vimSelectionStartClear()
if (adjustCaretPosition && !vimEditor.isEndAllowed) {
val lineEnd = IjVimEditor(this).getLineEndForOffset(it.offset)
val lineStart = IjVimEditor(this).getLineStartForOffset(it.offset)
if (it.offset == lineEnd && it.offset != lineStart) {
it.moveToInlayAwareOffset(it.offset - 1)
}
}
}
}
}
/** [adjustCaretPosition] - if true, caret will be moved one char left if it's on the line end */ /** [adjustCaretPosition] - if true, caret will be moved one char left if it's on the line end */
internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) { internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
if (!this.inSelectMode) return if (!this.inSelectMode) return

View File

@ -42,7 +42,7 @@ public class PsiHelper {
if (file == null) { if (file == null) {
return -1; return -1;
} }
StructureViewBuilder structureViewBuilder = LanguageStructureViewBuilder.getInstance().getStructureViewBuilder(file); StructureViewBuilder structureViewBuilder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(file);
if (!(structureViewBuilder instanceof TreeBasedStructureViewBuilder builder)) return -1; if (!(structureViewBuilder instanceof TreeBasedStructureViewBuilder builder)) return -1;
StructureViewModel model = builder.createStructureViewModel(editor); StructureViewModel model = builder.createStructureViewModel(editor);

View File

@ -19,7 +19,6 @@ import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.TextEditorWithPreview import com.intellij.openapi.fileEditor.TextEditorWithPreview
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.util.registry.Registry
import com.intellij.util.PlatformUtils
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.VimCaret import com.maddyhome.idea.vim.api.VimCaret
@ -44,12 +43,6 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService {
} }
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean { override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
if (PlatformUtils.isJetBrainsClient()) {
// Note: Remote Dev has special hacks for undo/redo, so we don't use the manager.
// The action is sent directly to the backend using the internal API.
return injector.actionExecutor.executeAction(editor, injector.actionExecutor.ACTION_UNDO, context)
}
val ijContext = context.context as DataContext val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
val textEditor = getTextEditor(editor.ij) val textEditor = getTextEditor(editor.ij)
@ -113,10 +106,6 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService {
} }
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean { override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
if (PlatformUtils.isJetBrainsClient()) {
return injector.actionExecutor.executeAction(editor, injector.actionExecutor.ACTION_REDO, context)
}
val ijContext = context.context as DataContext val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
val textEditor = getTextEditor(editor.ij) val textEditor = getTextEditor(editor.ij)

View File

@ -10,6 +10,8 @@ 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.Editor
import com.intellij.openapi.editor.EditorKind
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.LastUsedEditorInfo import com.maddyhome.idea.vim.LastUsedEditorInfo
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
@ -17,8 +19,8 @@ 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.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.helper.isTerminalEditor
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.state.mode.Mode
@ -55,7 +57,7 @@ class IJEditorFocusListener : EditorListener {
// 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 ijEditor = editor.ij
val isCurrentEditorTerminal = ijEditor.isTerminalEditor() val isCurrentEditorTerminal = isTerminal(ijEditor)
KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, false) KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, false)
@ -64,10 +66,8 @@ class IJEditorFocusListener : EditorListener {
VimPlugin.getChange().insertBeforeCursor(editor, context) VimPlugin.getChange().insertBeforeCursor(editor, context)
KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, true) KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, true)
} }
if (isCurrentEditorTerminal) { if (isCurrentEditorTerminal && !ijEditor.inInsertMode) {
if (!ijEditor.inInsertMode) {
switchToInsertMode.run() switchToInsertMode.run()
}
} else if (ijEditor.isInsertMode && (oldEditorInfo.isInsertModeForced || !ijEditor.document.isWritable)) { } else if (ijEditor.isInsertMode && (oldEditorInfo.isInsertModeForced || !ijEditor.document.isWritable)) {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor) val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
val mode = injector.vimState.mode val mode = injector.vimState.mode
@ -87,4 +87,12 @@ class IJEditorFocusListener : EditorListener {
} }
KeyHandler.getInstance().reset(editor) KeyHandler.getInstance().reset(editor)
} }
// By "terminal" we refer to some editor that should switch to INSERT mode on focus
private fun isTerminal(ijEditor: Editor): Boolean {
return !ijEditor.isViewer &&
!EditorHelper.isFileEditor(ijEditor) &&
ijEditor.document.isWritable &&
ijEditor.editorKind != EditorKind.DIFF
}
} }

View File

@ -20,7 +20,6 @@ import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.impl.TemplateState import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.codeInsight.template.impl.actions.NextVariableAction import com.intellij.codeInsight.template.impl.actions.NextVariableAction
import com.intellij.find.FindModelListener import com.intellij.find.FindModelListener
import com.intellij.ide.actions.ApplyIntentionAction
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
@ -100,14 +99,10 @@ internal object IdeaSpecifics {
} else { } else {
emptyList() emptyList()
} }
val intentionName = if (action is ApplyIntentionAction) {
action.name
}
else null
// We can still get empty ID and empty candidates. Notably, for the tool window toggle buttons on the new UI. // 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` // We could filter out action events with `place == ActionPlaces.TOOLWINDOW_TOOLBAR_BAR`
VimPlugin.getNotifications(event.dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id, candidates, intentionName) VimPlugin.getNotifications(event.dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id, candidates)
} }
} }

View File

@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.listener package com.maddyhome.idea.vim.listener
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
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.AnActionResult
@ -17,8 +16,6 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.ex.AnActionListener import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorAction
import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.CaretEvent import com.intellij.openapi.editor.event.CaretEvent
import com.intellij.openapi.editor.event.CaretListener import com.intellij.openapi.editor.event.CaretListener
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
@ -27,16 +24,10 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
import com.maddyhome.idea.vim.helper.getTopLevelEditor import com.maddyhome.idea.vim.helper.getTopLevelEditor
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.newapi.vim
internal class RiderActionListener : AnActionListener { internal class RiderActionListener : AnActionListener {
private var editor: Editor? = null private var editor: Editor? = null
private fun shouldExecuteOnFrontend(action: EditorAction): Boolean {
val isInsertMode = editor?.vim?.insertMode
return isInsertMode == false && action is EnterAction
}
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) { override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
if (VimPlugin.isNotEnabled()) return if (VimPlugin.isNotEnabled()) return
@ -44,12 +35,6 @@ internal class RiderActionListener : AnActionListener {
if (hostEditor != null) { if (hostEditor != null) {
editor = hostEditor editor = hostEditor
} }
// Fixes RIDER-123506
if (action is EditorAction) {
val key = ActionPlaces.EXECUTE_EDITOR_ACTION_ON_FRONTEND
editor?.putUserData(key, shouldExecuteOnFrontend(action))
}
} }
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) { override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {

View File

@ -468,9 +468,7 @@ internal object VimListenerManager {
openingEditor == null -> LocalOptionInitialisationScenario.EDIT openingEditor == null -> LocalOptionInitialisationScenario.EDIT
else -> LocalOptionInitialisationScenario.NEW else -> LocalOptionInitialisationScenario.NEW
} }
SlowOperations.knownIssue("VIM-3648").use {
EditorListeners.add(event.editor, openingEditor?.vim ?: injector.fallbackWindow, scenario) EditorListeners.add(event.editor, openingEditor?.vim ?: injector.fallbackWindow, scenario)
}
firstEditorInitialised = true firstEditorInitialised = true
} else { } else {
// We've got a virtual file, so FileOpenedSyncListener will be called. Save data // We've got a virtual file, so FileOpenedSyncListener will be called. Save data
@ -819,7 +817,7 @@ internal object VimListenerManager {
if (editor.inVisualMode) { if (editor.inVisualMode) {
editor.vim.exitVisualMode() editor.vim.exitVisualMode()
} else if (editor.vim.inSelectMode) { } else if (editor.vim.inSelectMode) {
editor.vim.exitSelectMode(false) editor.exitSelectMode(false)
KeyHandler.getInstance().reset(editor.vim) KeyHandler.getInstance().reset(editor.vim)
} }
} }

View File

@ -18,7 +18,7 @@ internal class IntellijMark(bookmark: LineBookmark, override val col: Int, proje
private val project: WeakReference<Project?> = WeakReference(project) private val project: WeakReference<Project?> = WeakReference(project)
override val key = BookmarksManager.getInstance(project)?.getType(bookmark)?.mnemonic ?: ' ' override val key = BookmarksManager.getInstance(project)?.getType(bookmark)?.mnemonic!!
override val line: Int override val line: Int
get() = getMark()?.line ?: 0 get() = getMark()?.line ?: 0
override val filepath: String override val filepath: String

View File

@ -39,16 +39,26 @@ import java.io.IOException
@Service @Service
internal class IjClipboardManager : VimClipboardManager { internal class IjClipboardManager : VimClipboardManager {
override fun getPrimaryContent(): IjVimCopiedText? { @Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#getPrimaryTextAndTransferableData")
override fun getPrimaryTextAndTransferableData(): Pair<String, List<Any>?>? {
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return null val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return null
val contents = clipboard.getContents(null) ?: return null val contents = clipboard.getContents(null) ?: return null
val (text, transferableData) = getTextAndTransferableData(contents) ?: return null return getTextAndTransferableData(contents)
}
override fun getPrimaryContent(editor: VimEditor, context: ExecutionContext): IjVimCopiedText? {
val (text, transferableData) = getPrimaryTextAndTransferableData() ?: return null
return IjVimCopiedText(text, transferableData ?: emptyList()) return IjVimCopiedText(text, transferableData ?: emptyList())
} }
override fun getClipboardContent(editor: VimEditor, context: ExecutionContext): VimCopiedText? { @Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#getClipboardTextAndTransferableData")
override fun getClipboardTextAndTransferableData(): Pair<String, List<Any>?>? {
val contents = getContents() ?: return null val contents = getContents() ?: return null
val (text, transferableData) = getTextAndTransferableData(contents) ?: return null return getTextAndTransferableData(contents)
}
override fun getClipboardContent(editor: VimEditor, context: ExecutionContext): VimCopiedText? {
val (text, transferableData) = getClipboardTextAndTransferableData() ?: return null
return IjVimCopiedText(text, transferableData ?: emptyList()) return IjVimCopiedText(text, transferableData ?: emptyList())
} }
@ -115,6 +125,14 @@ internal class IjClipboardManager : VimClipboardManager {
// return setPrimaryText(entry.text, entry.rawText, entry.transferableData) != null // return setPrimaryText(entry.text, entry.rawText, entry.transferableData) != null
// } // }
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#setPrimaryText")
override fun setPrimaryText(text: String, rawText: String, transferableData: List<Any>): Transferable? {
return handleTextSetting(text, rawText, transferableData) { content ->
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return@handleTextSetting null
clipboard.setContents(content, EmptyClipboardOwner.INSTANCE)
}
}
override fun collectCopiedText( override fun collectCopiedText(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
@ -242,6 +260,6 @@ internal class IjClipboardManager : VimClipboardManager {
} }
} }
data class IjVimCopiedText(override val text: String, override val transferableData: List<Any>) : VimCopiedText { data class IjVimCopiedText(override val text: String, val transferableData: List<Any>) : VimCopiedText {
override fun updateText(newText: String): VimCopiedText = IjVimCopiedText(newText, transferableData) override fun updateText(newText: String): VimCopiedText = IjVimCopiedText(newText, transferableData)
} }

View File

@ -32,7 +32,7 @@ internal class IjVimApplication : VimApplicationBase() {
return ApplicationManager.getApplication().isDispatchThread return ApplicationManager.getApplication().isDispatchThread
} }
override fun invokeLater(editor: VimEditor, action: () -> Unit) { override fun invokeLater(action: () -> Unit, editor: VimEditor) {
ApplicationManager.getApplication() ApplicationManager.getApplication()
.invokeLater(action, ModalityState.stateForComponent((editor as IjVimEditor).editor.component)) .invokeLater(action, ModalityState.stateForComponent((editor as IjVimEditor).editor.component))
} }

View File

@ -36,7 +36,7 @@ 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
import com.maddyhome.idea.vim.api.VimVirtualFile 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.common.IndentConfig import com.maddyhome.idea.vim.common.IndentConfig
import com.maddyhome.idea.vim.common.LiveRange import com.maddyhome.idea.vim.common.LiveRange
@ -301,13 +301,12 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
return editor.logicalPositionToOffset(logicalPosition) return editor.logicalPositionToOffset(logicalPosition)
} }
override fun getVirtualFile(): VimVirtualFile? { override fun getVirtualFile(): VirtualFile? {
val vf = EditorHelper.getVirtualFile(editor) val vf = EditorHelper.getVirtualFile(editor)
return vf?.let { return vf?.let {
object : VimVirtualFile { object : VirtualFile {
override val path: String = vf.path override val path: String = vf.path
override val protocol: String = vf.fileSystem.protocol override val protocol: String = vf.fileSystem.protocol
override val extension: String? = vf.extension
} }
} }
} }

View File

@ -213,6 +213,16 @@ internal class IjVimInjector : VimInjectorBase() {
override val redrawService: VimRedrawService override val redrawService: VimRedrawService
get() = service() get() = service()
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
override fun commandStateFor(editor: VimEditor): VimStateMachine {
return vimState
}
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
override fun commandStateFor(editor: Any): VimStateMachine {
return vimState
}
override val engineEditorHelper: EngineEditorHelper override val engineEditorHelper: EngineEditorHelper
get() = service<IjEditorHelper>() get() = service<IjEditorHelper>()
override val editorGroup: VimEditorGroup override val editorGroup: VimEditorGroup

View File

@ -1,99 +0,0 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.troubleshooting
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.EditorNotificationProvider
import com.intellij.ui.EditorNotifications
import com.intellij.util.PlatformUtils
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.icons.VimIcons
import com.maddyhome.idea.vim.vimscript.services.VimRcService
import java.util.function.Function
import javax.swing.JComponent
private var warningExplicitlyDisabled = false
private object CommandsCounter {
var commandsBeforeAutoDisable = 10
var initialized = false
@Synchronized
fun init() {
if (initialized) return
initialized = true
KeyHandler.getInstance().addCommandListener {
commandsBeforeAutoDisable -= 1
if (commandsBeforeAutoDisable <= 0) {
warningExplicitlyDisabled = true
}
}
}
}
/**
* Warning for the new users who may install IdeaVim plugin accidentally.
*/
internal class AccidentalInstallDetectorEditorNotificationProvider : EditorNotificationProvider, DumbAware {
override fun collectNotificationData(
project: Project,
file: VirtualFile,
): Function<in FileEditor, out JComponent?>? {
CommandsCounter.init()
// Note: Currently, enable this only for GoLand as it was a request from this IDE (VIM-3784).
// However, we can enable it for other IDEs if needed.
if (!PlatformUtils.isGoIde()) return null
if (warningExplicitlyDisabled) return null
if (VimPlugin.isNotEnabled()) return null
if (VimRcService.findIdeaVimRc() != null) return null
if (!injector.enabler.isNewIdeaVimUser()) return null
return Function { fileEditor: FileEditor ->
val panel = EditorNotificationPanel(fileEditor, EditorNotificationPanel.Status.Info)
panel.text = getText()
panel.icon(VimIcons.IDEAVIM)
KeyHandler.getInstance().addCommandListener {
if (CommandsCounter.commandsBeforeAutoDisable <= 0) {
KeyHandler.getInstance().removeAllCommandListeners()
EditorNotifications.getInstance(project).removeNotificationsForProvider(this)
}
panel.text = getText()
panel.invalidate()
panel.repaint()
}
@Suppress("DialogTitleCapitalization")
panel.createActionLabel("Disable IdeaVim") {
VimPlugin.setEnabled(false)
VimPlugin.getNotifications(project).showReenableNotification(project)
EditorNotifications.getInstance(project).removeNotificationsForProvider(this)
warningExplicitlyDisabled = true
}
panel.createActionLabel("Dismiss") {
EditorNotifications.getInstance(project).removeNotificationsForProvider(this)
warningExplicitlyDisabled = true
}
panel
}
}
private fun getText(): String {
return "<html>Youre using the IdeaVim plugin. If youre not familiar with Vim, consider disabling it. This message will disappear after ${CommandsCounter.commandsBeforeAutoDisable} commands.</html>"
}
}

View File

@ -428,6 +428,14 @@ 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();

View File

@ -132,9 +132,6 @@
key="ideavim.only.in.editor.component"/> key="ideavim.only.in.editor.component"/>
<registryKey defaultValue="false" description="Old action execution mechanism" key="ideavim.old.action.execution" <registryKey defaultValue="false" description="Old action execution mechanism" key="ideavim.old.action.execution"
restartRequired="false"/> restartRequired="false"/>
<editorNotificationProvider
implementation="com.maddyhome.idea.vim.troubleshooting.AccidentalInstallDetectorEditorNotificationProvider"/>
</extensions> </extensions>
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/> <xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>

View File

@ -85,23 +85,11 @@ E545=E545: Missing colon: {0}
E546=E546: Illegal mode: {0} E546=E546: Illegal mode: {0}
E548=E548: Digit expected: {0} E548=E548: Digit expected: {0}
E549=E549: Illegal percentage: {0} E549=E549: Illegal percentage: {0}
E691=E691: Can only compare List with List
E692=E692: Invalid operation for List
E694=E694: Invalid operation for Funcrefs
E695=E695: Cannot index a Funcref
E701=E701: Invalid type for len() E701=E701: Invalid type for len()
E703=E703: Using a Funcref as a Number
E728=E728: Using a Dictionary as a Number
E729=E729: Using a Funcref as a String
E730=E730: Using a List as a String E730=E730: Using a List as a String
E731=E731: Using a Dictionary as a String E731=E731: Using a Dictionary as a String
E735=E735: Can only compare Dictionary with Dictionary
E736=E736: Invalid operation for Dictionary
E745=E745: Using a List as a Number
E774=E774: 'operatorfunc' is empty E774=E774: 'operatorfunc' is empty
E804=E804: Cannot use '%' with Float
E805=E805: Using a Float as a Number E805=E805: Using a Float as a Number
E806=E806: Using a Float as a String
E808=E808: Number or Float required E808=E808: Number or Float required
e841.reserved.name.cannot.be.used.for.user.defined.command=E841: Reserved name, cannot be used for user defined command e841.reserved.name.cannot.be.used.for.user.defined.command=E841: Reserved name, cannot be used for user defined command
E939=E939: Positive count required E939=E939: Positive count required

View File

@ -1,108 +0,0 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action
import com.maddyhome.idea.vim.api.injector
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class ChangeCaseTest : VimTestCase() {
/**
* Note: The tests for duplicated commands (gugu and gUgU) might fail due to issues with the test environment,
* specifically related to file refresh operations. This is a known issue with the test infrastructure
* and not with the actual functionality being tested.
*
* The tests for guu and gUU test the same functionality and should pass.
*/
@Test
fun testChangeCaseLowerLineAction() {
typeTextInFile(
injector.parser.parseKeys("guu"),
"""
H${c}ELLO WORLD
${c}THIS IS A TEST
${c}FOR CASE CONVERSION
""".trimIndent(),
)
assertState(
"""
${c}hello world
${c}this is a test
${c}for case conversion
""".trimIndent()
)
}
@Disabled("Not yet supported")
@Test
fun testChangeCaseLowerLineActionDuplicated() {
typeTextInFile(
injector.parser.parseKeys("gugu"),
"""
H${c}ELLO WORLD
${c}THIS IS A TEST
${c}FOR CASE CONVERSION
""".trimIndent(),
)
assertState(
"""
${c}hello world
${c}this is a test
${c}for case conversion
""".trimIndent()
)
}
@Test
fun testChangeCaseUpperLineAction() {
typeTextInFile(
injector.parser.parseKeys("gUU"),
"""
h${c}ello world
${c}this is a test
${c}for case conversion
""".trimIndent(),
)
assertState(
"""
${c}HELLO WORLD
${c}THIS IS A TEST
${c}FOR CASE CONVERSION
""".trimIndent()
)
}
@Disabled("Not yet supported")
@Test
fun testChangeCaseUpperLineActionDuplicated() {
typeTextInFile(
injector.parser.parseKeys("gUgU"),
"""
h${c}ello world
${c}this is a test
${c}for case conversion
""".trimIndent(),
)
assertState(
"""
${c}HELLO WORLD
${c}THIS IS A TEST
${c}FOR CASE CONVERSION
""".trimIndent()
)
}
}

View File

@ -1,102 +0,0 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action
import com.maddyhome.idea.vim.api.injector
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class ChangeRot13Test : VimTestCase() {
@Test
fun testChangeRot13MotionAction() {
typeTextInFile(
injector.parser.parseKeys("g?2w"),
"H${c}ello World ${c}This is a ${c}test for ROT13 ${c}encoding\n",
)
assertState("H${c}ryyb Jbeyq ${c}Guvf vf a ${c}grfg sbe ROT13 ${c}rapbqvat\n")
}
@Test
fun testChangeRot13VisualAction() {
typeTextInFile(
injector.parser.parseKeys("v2wg?"),
"H${c}ello World ${c}This is a ${c}test for ROT13 ${c}encoding\n",
)
assertState("H${c}ryyb Jbeyq Guvf vf n ${c}grfg sbe EOT13 ${c}rapbqvat\n")
}
@Test
fun testChangeRot13LineAction() {
typeTextInFile(
injector.parser.parseKeys("g??"),
"""
H${c}ello World
${c}This is a test
${c}for ROT13 encoding
""".trimIndent(),
)
assertState(
"""
${c}Uryyb Jbeyq
${c}Guvf vf n grfg
${c}sbe EBG13 rapbqvat
""".trimIndent()
)
}
@Disabled("Not yet supported")
@Test
fun testChangeRot13LineActionDuplicated() {
typeTextInFile(
injector.parser.parseKeys("g?g?"),
"""
H${c}ello World
${c}This is a test
${c}for ROT13 encoding
""".trimIndent(),
)
assertState(
"""
${c}Uryyb Jbeyq
${c}Guvf vf n grfg
${c}sbe EBG13 rapbqvat
""".trimIndent()
)
}
@Test
fun testChangeRot13NonEnglishLetters() {
typeTextInFile(
injector.parser.parseKeys("g?$"),
"${c}Привет мир! Hello world!\n",
)
assertState("${c}Привет мир! Uryyb jbeyq!\n")
}
@Test
fun testChangeRot13FullAlphabet() {
typeTextInFile(
injector.parser.parseKeys("g?$"),
"${c}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n",
)
assertState("${c}nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM\n")
}
@Test
fun testChangeRot13Symbols() {
typeTextInFile(
injector.parser.parseKeys("g?$"),
"${c}!@#$%^&*()_+-=[]{}|;:'\",.<>/?\n",
)
assertState("${c}!@#$%^&*()_+-=[]{}|;:'\",.<>/?\n")
}
}

View File

@ -9,6 +9,7 @@
package org.jetbrains.plugins.ideavim.action.copy package org.jetbrains.plugins.ideavim.action.copy
import com.intellij.notification.ActionCenter import com.intellij.notification.ActionCenter
import com.intellij.notification.EventLog
import com.intellij.notification.Notification import com.intellij.notification.Notification
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
@ -104,7 +105,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
} }
typeText(injector.parser.parseKeys("p")) typeText(injector.parser.parseKeys("p"))
val notifications = ActionCenter.getNotifications(fixture.project) val notifications = EventLog.getLogModel(fixture.project).notifications
kotlin.test.assertTrue(notifications.isEmpty() || notifications.last().isExpired || OptionConstants.clipboard_ideaput !in notifications.last().content) kotlin.test.assertTrue(notifications.isEmpty() || notifications.last().isExpired || OptionConstants.clipboard_ideaput !in notifications.last().content)
} }

View File

@ -77,12 +77,4 @@ class AsciiCommandTest : VimTestCase() {
enterCommand("ascii") enterCommand("ascii")
assertEquals("<⓪> 9450, Hex 24ea, Oct 22352, Digr (0", VimPlugin.getMessage()) assertEquals("<⓪> 9450, Hex 24ea, Oct 22352, Digr (0", VimPlugin.getMessage())
} }
@Test
fun `test shows custom digraph with 32-bit Unicode codepoint`() {
configureByText("🔴")
enterCommand("digraph cr 128308")
enterCommand("ascii")
assertEquals("<🔴> 128308, Hex 1f534, Oct 372464, Digr cr", VimPlugin.getMessage())
}
} }

View File

@ -33,26 +33,20 @@ class DigraphsCommandTest : VimTestCase() {
@Test @Test
fun `test add custom digraph`() { fun `test add custom digraph`() {
enterCommand("digraph (0 9450") enterCommand("digraph (0 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar()) assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0'))
}
@Test
fun `test add custom 32-bit digraph`() {
enterCommand("digraph cr 128308")
assertEquals("🔴", String(Character.toChars(injector.digraphGroup.getCharacterForDigraph('c', 'r'))))
} }
@Test @Test
fun `test add custom digraph matches reversed characters`() { fun `test add custom digraph matches reversed characters`() {
enterCommand("digraph (0 9450") enterCommand("digraph (0 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('0', '(').toChar()) assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('0', '('))
} }
@Test @Test
fun `test add multiple custom digraphs`() { fun `test add multiple custom digraphs`() {
enterCommand("digraph (0 9450 (1 9312") enterCommand("digraph (0 9450 (1 9312")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar()) assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0'))
assertEquals('①', injector.digraphGroup.getCharacterForDigraph('(', '1').toChar()) assertEquals('①', injector.digraphGroup.getCharacterForDigraph('(', '1'))
} }
@Test @Test
@ -86,14 +80,14 @@ class DigraphsCommandTest : VimTestCase() {
@Test @Test
fun `test add custom digraph with more than two characters add custom digraph with initial two characters`() { fun `test add custom digraph with more than two characters add custom digraph with initial two characters`() {
enterCommand("digraph aaaa 9450") enterCommand("digraph aaaa 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('a', 'a').toChar()) assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('a', 'a'))
} }
@Test @Test
fun `test add custom digraphs until error`() { fun `test add custom digraphs until error`() {
enterCommand("digraph (0 9450 (1 9312 (2") enterCommand("digraph (0 9450 (1 9312 (2")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar()) assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0'))
assertEquals('①', injector.digraphGroup.getCharacterForDigraph('(', '1').toChar()) assertEquals('①', injector.digraphGroup.getCharacterForDigraph('(', '1'))
assertPluginError(true) assertPluginError(true)
assertPluginErrorMessageContains("E39: Number expected") assertPluginErrorMessageContains("E39: Number expected")
} }
@ -101,15 +95,15 @@ class DigraphsCommandTest : VimTestCase() {
@Test @Test
fun `test custom digraph overwrites existing custom digraph`() { fun `test custom digraph overwrites existing custom digraph`() {
enterCommand("digraph (0 9450") enterCommand("digraph (0 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar()) assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0'))
enterCommand("digraph (0 10003") enterCommand("digraph (0 10003")
assertEquals('✓', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar()) assertEquals('✓', injector.digraphGroup.getCharacterForDigraph('(', '0'))
} }
@Test @Test
fun `test custom digraph overwrites existing default digraph`() { fun `test custom digraph overwrites existing default digraph`() {
enterCommand("digraph OK 9450") enterCommand("digraph OK 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('O', 'K').toChar()) assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('O', 'K'))
} }
@Test @Test
@ -871,244 +865,6 @@ class DigraphsCommandTest : VimTestCase() {
) )
} }
@Test
fun `test digraph output with 32-bit custom digraphs`() {
enterCommand("digraph cr 128308") // 🔴
assertCommandOutput(
"digraphs",
"""
|NU ^@ 10 SH ^A 1 SX ^B 2 EX ^C 3 ET ^D 4 EQ ^E 5
|AK ^F 6 BL ^G 7 BS ^H 8 HT ^I 9 LF ^J 10 VT ^K 11
|FF ^L 12 CR ^M 13 SO ^N 14 SI ^O 15 DL ^P 16 D1 ^Q 17
|D2 ^R 18 D3 ^S 19 D4 ^T 20 NK ^U 21 SY ^V 22 EB ^W 23
|CN ^X 24 EM ^Y 25 SB ^Z 26 EC ^[ 27 FS ^\ 28 GS ^] 29
|RS ^^ 30 US ^_ 31 SP 32 Nb # 35 DO $ 36 At @ 64
|<( [ 91 // \ 92 )> ] 93 '> ^ 94 '! ` 96 (! { 123
|!! | 124 !) } 125 '? ~ 126 DT ^? 127 PA <80> 128 HO <81> 129
|BH <82> 130 NH <83> 131 IN <84> 132 NL <85> 133 SA <86> 134 ES <87> 135
|HS <88> 136 HJ <89> 137 VS <8a> 138 PD <8b> 139 PU <8c> 140 RI <8d> 141
|S2 <8e> 142 S3 <8f> 143 DC <90> 144 P1 <91> 145 P2 <92> 146 TS <93> 147
|CC <94> 148 MW <95> 149 SG <96> 150 EG <97> 151 SS <98> 152 GC <99> 153
|SC <9a> 154 CI <9b> 155 ST <9c> 156 OC <9d> 157 PM <9e> 158 AC <9f> 159
|NS   160 !I ¡ 161 ~! ¡ 161 Ct ¢ 162 c| ¢ 162 Pd £ 163
|$$ £ 163 Cu ¤ 164 ox ¤ 164 Ye ¥ 165 Y- ¥ 165 BB ¦ 166
||| ¦ 166 SE § 167 ': ¨ 168 Co © 169 cO © 169 -a ª 170
|<< « 171 NO ¬ 172 -, ¬ 172 -- <ad> 173 Rg ® 174 'm ¯ 175
|-= ¯ 175 DG ° 176 ~o ° 176 +- ± 177 2S ² 178 22 ² 178
|3S ³ 179 33 ³ 179 '' ´ 180 My µ 181 PI 182 pp 182
|.M · 183 ~. · 183 ', ¸ 184 1S ¹ 185 11 ¹ 185 -o º 186
|>> » 187 14 ¼ 188 12 ½ 189 34 ¾ 190 ?I ¿ 191 ~? ¿ 191
|A! À 192 A` À 192 A' Á 193 A> Â 194 A^ Â 194 A? Ã 195
|A~ Ã 195 A: Ä 196 A" Ä 196 AA Å 197 A@ Å 197 AE Æ 198
|C, Ç 199 E! È 200 E` È 200 E' É 201 E> Ê 202 E^ Ê 202
|E: Ë 203 E" Ë 203 I! Ì 204 I` Ì 204 I' Í 205 I> Î 206
|I^ Î 206 I: Ï 207 I" Ï 207 D- Ð 208 N? Ñ 209 N~ Ñ 209
|O! Ò 210 O` Ò 210 O' Ó 211 O> Ô 212 O^ Ô 212 O? Õ 213
|O~ Õ 213 O: Ö 214 *X × 215 /\ × 215 O/ Ø 216 U! Ù 217
|U` Ù 217 U' Ú 218 U> Û 219 U^ Û 219 U: Ü 220 Y' Ý 221
|TH Þ 222 Ip Þ 222 ss ß 223 a! à 224 a` à 224 a' á 225
|a> â 226 a^ â 226 a? ã 227 a~ ã 227 a: ä 228 a" ä 228
|aa å 229 a@ å 229 ae æ 230 c, ç 231 e! è 232 e` è 232
|e' é 233 e> ê 234 e^ ê 234 e: ë 235 e" ë 235 i! ì 236
|i` ì 236 i' í 237 i> î 238 i^ î 238 i: ï 239 d- ð 240
|n? ñ 241 n~ ñ 241 o! ò 242 o` ò 242 o' ó 243 o> ô 244
|o^ ô 244 o? õ 245 o~ õ 245 o: ö 246 -: ÷ 247 o/ ø 248
|u! ù 249 u` ù 249 u' ú 250 u> û 251 u^ û 251 u: ü 252
|y' ý 253 th þ 254 y: ÿ 255 y" ÿ 255 A- Ā 256 a- ā 257
|A( Ă 258 a( ă 259 A; Ą 260 a; ą 261 C' Ć 262 c' ć 263
|C> Ĉ 264 c> ĉ 265 C. Ċ 266 c. ċ 267 C< Č 268 c< č 269
|D< Ď 270 d< ď 271 D/ Đ 272 d/ đ 273 E- Ē 274 e- ē 275
|E( Ĕ 276 e( ĕ 277 E. Ė 278 e. ė 279 E; Ę 280 e; ę 281
|E< Ě 282 e< ě 283 G> Ĝ 284 g> ĝ 285 G( Ğ 286 g( ğ 287
|G. Ġ 288 g. ġ 289 G, Ģ 290 g, ģ 291 H> Ĥ 292 h> ĥ 293
|H/ Ħ 294 h/ ħ 295 I? Ĩ 296 i? ĩ 297 I- Ī 298 i- ī 299
|I( Ĭ 300 i( ĭ 301 I; Į 302 i; į 303 I. İ 304 i. ı 305
|IJ IJ 306 ij ij 307 J> Ĵ 308 j> ĵ 309 K, Ķ 310 k, ķ 311
|kk ĸ 312 L' Ĺ 313 l' ĺ 314 L, Ļ 315 l, ļ 316 L< Ľ 317
|l< ľ 318 L. Ŀ 319 l. ŀ 320 L/ Ł 321 l/ ł 322 N' Ń 323
|n' ń 324 N, Ņ 325 n, ņ 326 N< Ň 327 n< ň 328 'n ʼn 329
|NG Ŋ 330 ng ŋ 331 O- Ō 332 o- ō 333 O( Ŏ 334 o( ŏ 335
|O" Ő 336 o" ő 337 OE Œ 338 oe œ 339 R' Ŕ 340 r' ŕ 341
|R, Ŗ 342 r, ŗ 343 R< Ř 344 r< ř 345 S' Ś 346 s' ś 347
|S> Ŝ 348 s> ŝ 349 S, Ş 350 s, ş 351 S< Š 352 s< š 353
|T, Ţ 354 t, ţ 355 T< Ť 356 t< ť 357 T/ Ŧ 358 t/ ŧ 359
|U? Ũ 360 u? ũ 361 U- Ū 362 u- ū 363 U( Ŭ 364 u( ŭ 365
|U0 Ů 366 u0 ů 367 U" Ű 368 u" ű 369 U; Ų 370 u; ų 371
|W> Ŵ 372 w> ŵ 373 Y> Ŷ 374 y> ŷ 375 Y: Ÿ 376 Z' Ź 377
|z' ź 378 Z. Ż 379 z. ż 380 Z< Ž 381 z< ž 382 O9 Ơ 416
|o9 ơ 417 OI Ƣ 418 oi ƣ 419 yr Ʀ 422 U9 Ư 431 u9 ư 432
|Z/ Ƶ 437 z/ ƶ 438 ED Ʒ 439 A< Ǎ 461 a< ǎ 462 I< Ǐ 463
|i< ǐ 464 O< Ǒ 465 o< ǒ 466 U< Ǔ 467 u< ǔ 468 A1 Ǟ 478
|a1 ǟ 479 A7 Ǡ 480 a7 ǡ 481 A3 Ǣ 482 a3 ǣ 483 G/ Ǥ 484
|g/ ǥ 485 G< Ǧ 486 g< ǧ 487 K< Ǩ 488 k< ǩ 489 O; Ǫ 490
|o; ǫ 491 O1 Ǭ 492 o1 ǭ 493 EZ Ǯ 494 ez ǯ 495 j< ǰ 496
|G' Ǵ 500 g' ǵ 501 ;S ʿ 703 '< ˇ 711 '( ˘ 728 '. ˙ 729
|'0 ˚ 730 '; ˛ 731 '" ˝ 733 A% Ά 902 E% Έ 904 Y% Ή 905
|I% Ί 906 O% Ό 908 U% Ύ 910 W% Ώ 911 i3 ΐ 912 A* Α 913
|B* Β 914 G* Γ 915 D* Δ 916 E* Ε 917 Z* Ζ 918 Y* Η 919
|H* Θ 920 I* Ι 921 K* Κ 922 L* Λ 923 M* Μ 924 N* Ν 925
|C* Ξ 926 O* Ο 927 P* Π 928 R* Ρ 929 S* Σ 931 T* Τ 932
|U* Υ 933 F* Φ 934 X* Χ 935 Q* Ψ 936 W* Ω 937 J* Ϊ 938
|V* Ϋ 939 a% ά 940 e% έ 941 y% ή 942 i% ί 943 u3 ΰ 944
|a* α 945 b* β 946 g* γ 947 d* δ 948 e* ε 949 z* ζ 950
|y* η 951 h* θ 952 i* ι 953 k* κ 954 l* λ 955 m* μ 956
|n* ν 957 c* ξ 958 o* ο 959 p* π 960 r* ρ 961 *s ς 962
|s* σ 963 t* τ 964 u* υ 965 f* φ 966 x* χ 967 q* ψ 968
|w* ω 969 j* ϊ 970 v* ϋ 971 o% ό 972 u% ύ 973 w% ώ 974
|'G Ϙ 984 ,G ϙ 985 T3 Ϛ 986 t3 ϛ 987 M3 Ϝ 988 m3 ϝ 989
|K3 Ϟ 990 k3 ϟ 991 P3 Ϡ 992 p3 ϡ 993 '% ϴ 1012 j3 ϵ 1013
|IO Ё 1025 D% Ђ 1026 G% Ѓ 1027 IE Є 1028 DS Ѕ 1029 II І 1030
|YI Ї 1031 J% Ј 1032 LJ Љ 1033 NJ Њ 1034 Ts Ћ 1035 KJ Ќ 1036
|V% Ў 1038 DZ Џ 1039 A= А 1040 B= Б 1041 V= В 1042 G= Г 1043
|D= Д 1044 E= Е 1045 Z% Ж 1046 Z= З 1047 I= И 1048 J= Й 1049
|K= К 1050 L= Л 1051 M= М 1052 N= Н 1053 O= О 1054 P= П 1055
|R= Р 1056 S= С 1057 T= Т 1058 U= У 1059 F= Ф 1060 H= Х 1061
|C= Ц 1062 C% Ч 1063 S% Ш 1064 Sc Щ 1065 =" Ъ 1066 Y= Ы 1067
|%" Ь 1068 JE Э 1069 JU Ю 1070 JA Я 1071 a= а 1072 b= б 1073
|v= в 1074 g= г 1075 d= д 1076 e= е 1077 z% ж 1078 z= з 1079
|i= и 1080 j= й 1081 k= к 1082 l= л 1083 m= м 1084 n= н 1085
|o= о 1086 p= п 1087 r= р 1088 s= с 1089 t= т 1090 u= у 1091
|f= ф 1092 h= х 1093 c= ц 1094 c% ч 1095 s% ш 1096 sc щ 1097
|=' ъ 1098 y= ы 1099 %' ь 1100 je э 1101 ju ю 1102 ja я 1103
|io ё 1105 d% ђ 1106 g% ѓ 1107 ie є 1108 ds ѕ 1109 ii і 1110
|yi ї 1111 j% ј 1112 lj љ 1113 nj њ 1114 ts ћ 1115 kj ќ 1116
|v% ў 1118 dz џ 1119 Y3 Ѣ 1122 y3 ѣ 1123 O3 Ѫ 1130 o3 ѫ 1131
|F3 Ѳ 1138 f3 ѳ 1139 V3 Ѵ 1140 v3 ѵ 1141 C3 Ҁ 1152 c3 ҁ 1153
|G3 Ґ 1168 g3 ґ 1169 A+ א 1488 B+ ב 1489 G+ ג 1490 D+ ד 1491
|H+ ה 1492 W+ ו 1493 Z+ ז 1494 X+ ח 1495 Tj ט 1496 J+ י 1497
|K% ך 1498 K+ כ 1499 L+ ל 1500 M% ם 1501 M+ מ 1502 N% ן 1503
|N+ נ 1504 S+ ס 1505 E+ ע 1506 P% ף 1507 P+ פ 1508 Zj ץ 1509
|ZJ צ 1510 Q+ ק 1511 R+ ר 1512 Sh ש 1513 T+ ת 1514 ,+ ، 1548
|;+ ؛ 1563 ?+ ؟ 1567 H' ء 1569 aM آ 1570 aH أ 1571 wH ؤ 1572
|ah إ 1573 yH ئ 1574 a+ ا 1575 b+ ب 1576 tm ة 1577 t+ ت 1578
|tk ث 1579 g+ ج 1580 hk ح 1581 x+ خ 1582 d+ د 1583 dk ذ 1584
|r+ ر 1585 z+ ز 1586 s+ س 1587 sn ش 1588 c+ ص 1589 dd ض 1590
|tj ط 1591 zH ظ 1592 e+ ع 1593 i+ غ 1594 ++ ـ 1600 f+ ف 1601
|q+ ق 1602 k+ ك 1603 l+ ل 1604 m+ م 1605 n+ ن 1606 h+ ه 1607
|w+ و 1608 j+ ى 1609 y+ ي 1610 :+ ً 1611 "+ ٌ 1612 =+ ٍ 1613
|/+ َ 1614 '+ ُ 1615 1+ ِ 1616 3+ ّ 1617 0+ ْ 1618 aS ٰ 1648
|p+ پ 1662 v+ ڤ 1700 gf گ 1711 0a ۰ 1776 1a ۱ 1777 2a ۲ 1778
|3a ۳ 1779 4a ۴ 1780 5a ۵ 1781 6a ۶ 1782 7a ۷ 1783 8a ۸ 1784
|9a ۹ 1785 B. 7682 b. 7683 B_ 7686 b_ 7687 D. 7690
|d. 7691 D_ 7694 d_ 7695 D, 7696 d, 7697 F. 7710
|f. 7711 G- 7712 g- 7713 H. 7714 h. 7715 H: 7718
|h: 7719 H, 7720 h, 7721 K' 7728 k' 7729 K_ 7732
|k_ 7733 L_ 7738 l_ 7739 M' 7742 m' ḿ 7743 M. 7744
|m. 7745 N. 7748 n. 7749 N_ 7752 n_ 7753 P' 7764
|p' 7765 P. 7766 p. 7767 R. 7768 r. 7769 R_ 7774
|r_ 7775 S. 7776 s. 7777 T. 7786 t. 7787 T_ 7790
|t_ 7791 V? 7804 v? 7805 W! 7808 W` 7808 w! 7809
|w` 7809 W' 7810 w' 7811 W: 7812 w: 7813 W. 7814
|w. 7815 X. 7818 x. 7819 X: 7820 x: 7821 Y. 7822
|y. 7823 Z> 7824 z> 7825 Z_ 7828 z_ 7829 h_ 7830
|t: 7831 w0 7832 y0 7833 A2 7842 a2 7843 E2 7866
|e2 7867 E? 7868 e? 7869 I2 7880 i2 7881 O2 7886
|o2 7887 U2 7910 u2 7911 Y! 7922 Y` 7922 y! 7923
|y` 7923 Y2 7926 y2 7927 Y? 7928 y? 7929 ;' 7936
|,' 7937 ;! 7938 ,! 7939 ?; 7940 ?, 7941 !: 7942
|?: 7943 1N 8194 1M 8195 3M 8196 4M 8197 6M 8198
|1T 8201 1H 8202 -1 8208 -N 8211 -M 8212 -3 8213
|!2 8214 =2 8215 '6 8216 '9 8217 .9 8218 9' 8219
|"6 “ 8220 "9 8221 :9 8222 9" ‟ 8223 /- † 8224 /= ‡ 8225
|oo 8226 .. 8229 ,. 8230 %0 8240 1' 8242 2' 8243
|3' 8244 4' 8279 1" 8245 2" 8246 3" ‷ 8247 Ca ‸ 8248
|<1 8249 >1 8250 :X 8251 '- 8254 /f 8260 0S 8304
|4S 8308 5S 8309 6S 8310 7S 8311 8S 8312 9S 8313
|+S 8314 -S 8315 =S 8316 (S 8317 )S 8318 nS 8319
|0s 8320 1s 8321 2s 8322 3s 8323 4s 8324 5s 8325
|6s 8326 7s 8327 8s 8328 9s 8329 +s 8330 -s 8331
|=s 8332 (s 8333 )s 8334 Li 8356 Pt 8359 W= 8361
|=e 8364 Eu 8364 =R 8381 =P 8381 oC 8451 co 8453
|oF 8457 N0 8470 PO 8471 Rx 8478 SM 8480 TM 8482
|Om 8486 AO 8491 13 8531 23 8532 15 8533 25 8534
|35 8535 45 8536 16 8537 56 8538 18 8539 38 8540
|58 8541 78 8542 1R 8544 2R 8545 3R 8546 4R 8547
|5R 8548 6R 8549 7R 8550 8R 8551 9R 8552 aR 8553
|bR 8554 cR 8555 1r 8560 2r 8561 3r 8562 4r 8563
|5r 8564 6r 8565 7r 8566 8r 8567 9r 8568 ar 8569
|br 8570 cr 8571 <- 8592 -! 8593 -> 8594 -v 8595
|<> 8596 UD 8597 <= 8656 => 8658 == 8660 FA 8704
|dP 8706 TE 8707 /0 8709 DE 8710 NB 8711 (- 8712
|-) 8715 *P 8719 +Z 8721 -2 8722 -+ 8723 *- 8727
|Ob 8728 Sb 8729 RT 8730 0( 8733 00 8734 -L 8735
|-V 8736 PP 8741 AN 8743 OR 8744 (U 8745 )U 8746
|In 8747 DI 8748 Io 8750 .: 8756 :. 8757 :R 8758
|:: 8759 ?1 8764 CG 8766 ?- 8771 ?= 8773 ?2 8776
|=? 8780 HI 8787 != 8800 =3 8801 =< 8804 >= 8805
|<* 8810 *> 8811 !< 8814 !> 8815 (C 8834 )C 8835
|(_ 8838 )_ 8839 0. 8857 02 8858 -T 8869 .P 8901
|:3 8942 .3 8943 Eh 8962 <7 8968 >7 8969 7< 8970
|7> 8971 NI 8976 (A 8978 TR 8981 Iu 8992 Il 8993
|</ 9001 /> 9002 Vs 9251 1h 9280 3h 9281 2h 9282
|4h 9283 1j 9286 2j 9287 3j 9288 4j 9289 1. 9352
|2. 9353 3. 9354 4. 9355 5. 9356 6. 9357 7. 9358
|8. 9359 9. 9360 hh 9472 HH 9473 vv 9474 VV 9475
|3- 9476 3_ 9477 3! 9478 3/ 9479 4- 9480 4_ 9481
|4! 9482 4/ 9483 dr 9484 dR 9485 Dr 9486 DR 9487
|dl 9488 dL 9489 Dl 9490 LD 9491 ur 9492 uR 9493
|Ur 9494 UR 9495 ul 9496 uL 9497 Ul 9498 UL 9499
|vr 9500 vR 9501 Vr 9504 VR 9507 vl 9508 vL 9509
|Vl 9512 VL 9515 dh 9516 dH 9519 Dh 9520 DH 9523
|uh 9524 uH 9527 Uh 9528 UH 9531 vh 9532 vH 9535
|Vh 9538 VH 9547 FD 9585 BD 9586 TB 9600 LB 9604
|FB 9608 lB 9612 RB 9616 .S 9617 :S 9618 ?S 9619
|fS 9632 OS 9633 RO 9634 Rr 9635 RF 9636 RY 9637
|RH 9638 RZ 9639 RK 9640 RX 9641 sB 9642 SR 9644
|Or 9645 UT 9650 uT 9651 PR 9654 Tr 9655 Dt 9660
|dT 9661 PL 9664 Tl 9665 Db 9670 Dw 9671 LZ 9674
|0m 9675 0o 9678 0M 9679 0L 9680 0R 9681 Sn 9688
|Ic 9689 Fd 9698 Bd 9699 *2 9733 *1 9734 <H 9756
|>H 9758 0u 9786 0U 9787 SU 9788 Fm 9792 Ml 9794
|cS 9824 cH 9825 cD 9826 cC 9827 Md 9833 M8 9834
|M2 9835 Mb 9837 Mx 9838 MX 9839 OK 10003 XX 10007
|-X 10016 IS   12288 ,_ 12289 ._ 12290 +" 〃 12291 +_ 〄 12292
|*_ 12293 ;_ 12294 0_ 12295 <+ 12298 >+ 12299 <' 12300
|>' 12301 <" 『 12302 >" 12303 (" 【 12304 )" 12305 =T 12306
|=_ 12307 (' 12308 )' 12309 (I 12310 )I 12311 -? 12316
|A5 12353 a5 12354 I5 12355 i5 12356 U5 12357 u5 12358
|E5 12359 e5 12360 O5 12361 o5 12362 ka 12363 ga 12364
|ki 12365 gi 12366 ku 12367 gu 12368 ke 12369 ge 12370
|ko 12371 go 12372 sa 12373 za 12374 si 12375 zi 12376
|su 12377 zu 12378 se 12379 ze 12380 so 12381 zo 12382
|ta 12383 da 12384 ti 12385 di 12386 tU 12387 tu 12388
|du 12389 te 12390 de 12391 to 12392 do 12393 na 12394
|ni 12395 nu 12396 ne 12397 no 12398 ha 12399 ba 12400
|pa 12401 hi 12402 bi 12403 pi 12404 hu 12405 bu 12406
|pu 12407 he 12408 be 12409 pe 12410 ho 12411 bo 12412
|po 12413 ma 12414 mi 12415 mu 12416 me 12417 mo 12418
|yA 12419 ya 12420 yU 12421 yu 12422 yO 12423 yo 12424
|ra 12425 ri 12426 ru 12427 re 12428 ro 12429 wA 12430
|wa 12431 wi 12432 we 12433 wo 12434 n5 12435 vu 12436
|"5 ゛ 12443 05 ゜ 12444 *5 ゝ 12445 +5 ゞ 12446 a6 ァ 12449 A6 ア 12450
|i6 12451 I6 12452 u6 12453 U6 12454 e6 12455 E6 12456
|o6 12457 O6 12458 Ka 12459 Ga 12460 Ki 12461 Gi 12462
|Ku 12463 Gu 12464 Ke 12465 Ge 12466 Ko 12467 Go 12468
|Sa 12469 Za 12470 Si 12471 Zi 12472 Su 12473 Zu 12474
|Se 12475 Ze 12476 So 12477 Zo 12478 Ta 12479 Da 12480
|Ti 12481 Di 12482 TU 12483 Tu 12484 Du 12485 Te 12486
|De 12487 To 12488 Do 12489 Na 12490 Ni 12491 Nu 12492
|Ne 12493 No 12494 Ha 12495 Ba 12496 Pa 12497 Hi 12498
|Bi 12499 Pi 12500 Hu 12501 Bu 12502 Pu 12503 He 12504
|Be 12505 Pe 12506 Ho 12507 Bo 12508 Po 12509 Ma 12510
|Mi 12511 Mu 12512 Me 12513 Mo 12514 YA 12515 Ya 12516
|YU 12517 Yu 12518 YO 12519 Yo 12520 Ra 12521 Ri 12522
|Ru 12523 Re 12524 Ro 12525 WA 12526 Wa 12527 Wi 12528
|We 12529 Wo 12530 N6 12531 Vu 12532 KA 12533 KE 12534
|Va 12535 Vi 12536 Ve 12537 Vo 12538 .6 12539 -6 12540
|*6 12541 +6 12542 b4 12549 p4 12550 m4 12551 f4 12552
|d4 12553 t4 12554 n4 12555 l4 12556 g4 12557 k4 12558
|h4 12559 j4 12560 q4 12561 x4 12562 zh 12563 ch 12564
|sh 12565 r4 12566 z4 12567 c4 12568 s4 12569 a4 12570
|o4 12571 e4 12572 ai 12574 ei 12575 au 12576 ou 12577
|an 12578 en 12579 aN 12580 eN 12581 er 12582 i4 12583
|u4 12584 iu 12585 v4 12586 nG 12587 gn 12588 1c 12832
|2c 12833 3c 12834 4c 12835 5c 12836 6c 12837 7c 12838
|8c 12839 9c 12840 ff 64256 fi 64257 fl 64258 ft 64261
|st 64262 cr 🔴 128308
""".trimMargin()
)
}
@Test @Test
fun `test digraph output with headers and custom digraphs`() { fun `test digraph output with headers and custom digraphs`() {
enterCommand("digraphs (0 9450 (2 9313 (1 9312") enterCommand("digraphs (0 9450 (2 9313 (1 9312")

View File

@ -13,7 +13,7 @@ import com.intellij.openapi.application.ApplicationManager
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.api.options import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.history.VimHistory import com.maddyhome.idea.vim.history.HistoryConstants
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
@ -240,7 +240,7 @@ class GlobalCommandTest : VimTestCase() {
@Test @Test
fun `test check history`() { fun `test check history`() {
VimPlugin.getHistory().clear() VimPlugin.getHistory().clear()
val initialEntries = VimPlugin.getHistory().getEntries(VimHistory.Type.Command, 0, 0) val initialEntries = VimPlugin.getHistory().getEntries(HistoryConstants.COMMAND, 0, 0)
doTest( doTest(
"g/found/d", "g/found/d",
initialText, initialText,
@ -252,7 +252,7 @@ class GlobalCommandTest : VimTestCase() {
hard by the torrent of a mountain pass. hard by the torrent of a mountain pass.
""".trimIndent(), """.trimIndent(),
) )
val entries = VimPlugin.getHistory().getEntries(VimHistory.Type.Command, 0, 0) val entries = VimPlugin.getHistory().getEntries(HistoryConstants.COMMAND, 0, 0)
kotlin.test.assertEquals(1, entries.size - initialEntries.size) kotlin.test.assertEquals(1, entries.size - initialEntries.size)
val element = entries.last() val element = entries.last()
kotlin.test.assertEquals("g/found/d", element.entry) kotlin.test.assertEquals("g/found/d", element.entry)

View File

@ -10,7 +10,6 @@ package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.intellij.idea.TestFor import com.intellij.idea.TestFor
import com.intellij.testFramework.LoggedErrorProcessor import com.intellij.testFramework.LoggedErrorProcessor
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
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.api.keys import com.maddyhome.idea.vim.api.keys
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
@ -22,7 +21,6 @@ import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.exceptionMappingOwner import org.jetbrains.plugins.ideavim.exceptionMappingOwner
import org.jetbrains.plugins.ideavim.waitAndAssert
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -107,16 +105,16 @@ class MapCommandTest : VimTestCase() {
enterCommand("imap <C-Down> <C-O>gt") enterCommand("imap <C-Down> <C-O>gt")
enterCommand("nmap ,f <Plug>Foo") enterCommand("nmap ,f <Plug>Foo")
enterCommand("nmap <Plug>Foo iHello<Esc>") enterCommand("nmap <Plug>Foo iHello<Esc>")
enterCommand("imap")
assertCommandOutput("imap", assertExOutput(
""" """
|i <C-Down> <C-O>gt |i <C-Down> <C-O>gt
|i bar <Esc> |i bar <Esc>
|i foo bar |i foo bar
""".trimMargin(), """.trimMargin(),
) )
enterCommand("map")
assertCommandOutput("map", assertExOutput(
""" """
| <C-Down> gt | <C-Down> gt
|n <Plug>Foo iHello<Esc> |n <Plug>Foo iHello<Esc>
@ -144,9 +142,11 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
enterCommand("map")
// Note that Vim doesn't appear to have an order. Items are kinda sorted, but also not. I.e. `m{something}` are // Note that Vim doesn't appear to have an order. Items are kinda sorted, but also not. I.e. `m{something}` are
// grouped together, but followed later by `g{something}`. We'll sort by {lhs}, so we're at least consistent // grouped together, but followed later by `g{something}`. We'll sort by {lhs}, so we're at least consistent
assertCommandOutput("map", assertExOutput(
""" """
| all foo | all foo
|n normal foo |n normal foo
@ -163,7 +163,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("nmap", enterCommand("nmap")
assertExOutput(
""" """
| all foo | all foo
|n normal foo |n normal foo
@ -176,7 +178,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("vmap", enterCommand("vmap")
assertExOutput(
""" """
| all foo | all foo
|s select foo |s select foo
@ -191,7 +195,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("smap", enterCommand("smap")
assertExOutput(
""" """
| all foo | all foo
|s select foo |s select foo
@ -205,7 +211,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("xmap", enterCommand("xmap")
assertExOutput(
""" """
| all foo | all foo
|x visual foo |x visual foo
@ -219,7 +227,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("omap", enterCommand("omap")
assertExOutput(
""" """
| all foo | all foo
|o op-pending foo |o op-pending foo
@ -232,7 +242,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("map!", enterCommand("map!")
assertExOutput(
""" """
|c cmdline foo |c cmdline foo
|i insert foo |i insert foo
@ -255,7 +267,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("imap", enterCommand("imap")
assertExOutput(
""" """
|i insert foo |i insert foo
|! insert+cmdline foo |! insert+cmdline foo
@ -269,7 +283,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("lmap", enterCommand("lmap")
assertExOutput(
""" """
|l lang foo |l lang foo
""".trimMargin() """.trimMargin()
@ -281,7 +297,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
addTestMaps() addTestMaps()
assertCommandOutput("cmap", enterCommand("cmap")
assertExOutput(
""" """
|c cmdline foo |c cmdline foo
|! insert+cmdline foo |! insert+cmdline foo
@ -295,9 +313,10 @@ class MapCommandTest : VimTestCase() {
addTestMaps() // Adds a mapping of all for NVO addTestMaps() // Adds a mapping of all for NVO
enterCommand("sunmap all") // Removes Select from the NVO mapping for foo enterCommand("sunmap all") // Removes Select from the NVO mapping for foo
enterCommand("map")
// Note that the formatting is exactly how Vim shows it. Messy, isn't it? // Note that the formatting is exactly how Vim shows it. Messy, isn't it?
assertCommandOutput("map", assertExOutput(
""" """
|noxall foo |noxall foo
|n normal foo |n normal foo
@ -315,8 +334,9 @@ class MapCommandTest : VimTestCase() {
addTestMaps() // Adds a mapping of all for NVO addTestMaps() // Adds a mapping of all for NVO
enterCommand("vunmap all") // Removes Visual+Select from the NVO mapping for foo enterCommand("vunmap all") // Removes Visual+Select from the NVO mapping for foo
enterCommand("map")
assertCommandOutput("map", assertExOutput(
""" """
|no all foo |no all foo
|n normal foo |n normal foo
@ -335,7 +355,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("vmap foo baz") // Visual, Select enterCommand("vmap foo baz") // Visual, Select
// Just to be sure we're set up correctly // Just to be sure we're set up correctly
assertCommandOutput("map", enterCommand("map")
assertExOutput(
""" """
|no foo bar |no foo bar
|v foo baz |v foo baz
@ -345,7 +366,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("sunmap foo") enterCommand("sunmap foo")
enterCommand("ounmap foo") enterCommand("ounmap foo")
assertCommandOutput("map", enterCommand("map")
assertExOutput(
""" """
|n foo bar |n foo bar
|x foo baz |x foo baz
@ -361,7 +383,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("nmap fee bap") enterCommand("nmap fee bap")
enterCommand("nmap zzz ppp") enterCommand("nmap zzz ppp")
assertCommandOutput("map f", enterCommand("map f")
assertExOutput(
""" """
|n fee bap |n fee bap
| foo bar | foo bar
@ -374,7 +397,8 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
enterCommand("map foo bar") enterCommand("map foo bar")
assertCommandOutput("map ", enterCommand("map ")
assertExOutput(
""" """
| foo bar | foo bar
""".trimMargin() """.trimMargin()
@ -386,7 +410,8 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
enterCommand("imap foo bar") enterCommand("imap foo bar")
assertCommandOutput("imap f ", enterCommand("imap f ")
assertExOutput(
""" """
|i foo bar |i foo bar
""".trimMargin() """.trimMargin()
@ -404,7 +429,7 @@ class MapCommandTest : VimTestCase() {
} }
@Test @Test
fun `test dd with mapping starting with d`() { fun testddWithMapping() {
configureByText( configureByText(
""" """
Hello$c 1 Hello$c 1
@ -435,8 +460,8 @@ class MapCommandTest : VimTestCase() {
configureByText("\n") configureByText("\n")
enterCommand("inoremap jj <Esc>") enterCommand("inoremap jj <Esc>")
enterCommand("imap foo bar") enterCommand("imap foo bar")
enterCommand("imap")
assertCommandOutput("imap", assertExOutput(
""" """
|i foo bar |i foo bar
|i jj * <Esc> |i jj * <Esc>
@ -466,7 +491,8 @@ class MapCommandTest : VimTestCase() {
""".trimIndent(), """.trimIndent(),
) )
assertOffset(1) assertOffset(1)
assertCommandOutput("nmap", "n <Right> * <Nop>") enterCommand("nmap")
assertExOutput("n <Right> * <Nop>")
} }
@Test @Test
@ -479,8 +505,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("nmap <script> ,e /e<CR>") enterCommand("nmap <script> ,e /e<CR>")
enterCommand("nmap <expr> ,f '/f<CR>'") enterCommand("nmap <expr> ,f '/f<CR>'")
enterCommand("nmap <unique> ,g /g<CR>") enterCommand("nmap <unique> ,g /g<CR>")
enterCommand("nmap")
assertCommandOutput("nmap", assertExOutput(
""" """
|n ,a /a<CR> |n ,a /a<CR>
|n ,b /b<CR> |n ,b /b<CR>
@ -587,8 +613,8 @@ class MapCommandTest : VimTestCase() {
typeText("i" + "#" + "<Esc>") typeText("i" + "#" + "<Esc>")
assertState("#\n") assertState("#\n")
assertMode(Mode.NORMAL()) assertMode(Mode.NORMAL())
enterCommand("imap")
assertCommandOutput("imap", "i # * X<C-H>#") assertExOutput("i # * X<C-H>#")
} }
// VIM-679 |:map| // VIM-679 |:map|
@ -612,7 +638,8 @@ class MapCommandTest : VimTestCase() {
""".trimIndent(), """.trimIndent(),
) )
assertMode(Mode.NORMAL()) assertMode(Mode.NORMAL())
assertCommandOutput("map", " <C-X>i dd") enterCommand("map")
assertExOutput(" <C-X>i dd")
typeText("<C-X>i") typeText("<C-X>i")
assertState("bar\n") assertState("bar\n")
} }
@ -727,59 +754,6 @@ class MapCommandTest : VimTestCase() {
assertState("Bye\n") assertState("Bye\n")
} }
@Test
fun `test map applies longest mapping`() {
configureByText("\n")
enterCommand("imap ab AB")
enterCommand("imap abcd ABCD")
typeText("i", "abcd", "<Esc>")
assertState("ABCD\n")
}
@Test
fun `test map falls back to previous longest mapping when abandoned`() {
configureByText("\n")
enterCommand("imap abc ABC")
enterCommand("imap abcd ABCD")
typeText("i", "abcg", "<Esc>")
assertState("ABCg\n")
}
@Test
fun `test map falls back to previous longest mapping when abandoned with shorter prefix`() {
configureByText("\n")
enterCommand("imap ab AB")
enterCommand("imap abcd ABCD")
typeText("i", "abcg", "<Esc>")
assertState("ABcg\n")
}
@Test
fun `test map falls back to previous longest mapping after timeout`() {
configureByText("\n")
enterCommand("imap ab AB")
enterCommand("imap abcd ABCD")
enterCommand("set timeoutlen=100")
typeText("i", "abc")
waitAndAssert(injector.globalOptions().timeoutlen + 100) {
fixture.editor.document.text == "ABc\n"
}
assertState("ABc\n")
}
@Test
fun `test map falls back to previous longest mapping after timeout with shorter prefix`() {
configureByText("\n")
enterCommand("imap ab AB")
enterCommand("imap abcde ABCDE")
enterCommand("set timeoutlen=100")
typeText("i", "abcd")
waitAndAssert(injector.globalOptions().timeoutlen + 100) {
fixture.editor.document.text == "ABcd\n"
}
assertState("ABcd\n")
}
@TestWithoutNeovim(SkipNeovimReason.PLUG) @TestWithoutNeovim(SkipNeovimReason.PLUG)
@Test @Test
fun testPlugMapping() { fun testPlugMapping() {
@ -799,6 +773,16 @@ class MapCommandTest : VimTestCase() {
assertState("123${c}7890") assertState("123${c}7890")
} }
@TestWithoutNeovim(SkipNeovimReason.PLUG)
@Test
fun testIncompleteMapping() {
configureByText("123${c}4567890")
enterCommand("map <Plug>(Hi)l lll")
enterCommand("map I <Plug>(Hi)")
typeText("Ih")
assertState("12${c}34567890")
}
@Test @Test
fun testIntersectingCommands2() { fun testIntersectingCommands2() {
configureByText("123${c}4567890") configureByText("123${c}4567890")
@ -807,86 +791,6 @@ class MapCommandTest : VimTestCase() {
assertState("123${c}567890") assertState("123${c}567890")
} }
@Test
fun `test partial Plug mapping`() {
doTest(
listOf("i", "Xy"),
"Lorem $c ipsum dolor sit amet",
"Lorem Hello ipsum dolor sit amet",
Mode.INSERT
) {
enterCommand("imap <Plug>xy Hello")
enterCommand("imap X <Plug>x")
}
}
@Test
fun `test abandoned Plug mapping replays all keys as text`() {
// The default Vim behaviour for an abandoned mapping is to replay it, character by character, even if that makes no
// sense for the mode or mapping, and will move the caret or delete text or whatever. This is also true for <Plug>
// mappings, even though `<Plug>` is a special char that can't be typed by the user. When used in Insert mode, Vim
// expands the special char to the string "<Plug>".
doTest(
listOf("i", "Xz"),
"Lorem $c ipsum dolor sit amet",
"Lorem <Plug>xz ipsum dolor sit amet",
Mode.INSERT
) {
enterCommand("imap <Plug>xy Hello")
enterCommand("imap X <Plug>x")
}
}
@Test
fun `test partial Action mapping`() {
doTest(
listOf("i", "X(EditorToggleCase)"),
"Lorem ${c}ipsum dolor sit amet",
"Lorem IPSUM dolor sit amet",
Mode.INSERT
) {
enterCommand("imap X <Action>")
}
}
@Test
fun `test abandoned Action mapping replays all keys as text`() {
// The mapping is looking for `<Action>(...)`, so we need to feed it a key that is not part of this mapping. A space
// char will work
doTest(
listOf("i", "X( "),
"Lorem $c ipsum dolor sit amet",
"Lorem <Action>( ipsum dolor sit amet",
Mode.INSERT
) {
enterCommand("imap X <Action>")
}
}
@Test
fun `test abandoned Action mapping replays all keys as text 2`() {
doTest(
listOf("i", "Xz"),
"Lorem $c ipsum dolor sit amet",
"Lorem <Action>z ipsum dolor sit amet",
Mode.INSERT
) {
enterCommand("imap X <Action>")
}
}
@Test
fun `test timedout Action mapping replays all keys as text`() {
configureByText("Lorem $c ipsum dolor sit amet")
enterCommand("imap X <Action>")
enterCommand("set timeoutlen=100")
typeText("i", "X")
waitAndAssert(injector.globalOptions().timeoutlen + 100) {
fixture.editor.document.text == "Lorem <Action> ipsum dolor sit amet"
}
assertState("Lorem <Action> ipsum dolor sit amet")
}
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
fun testMapZero() { fun testMapZero() {
@ -999,7 +903,7 @@ class MapCommandTest : VimTestCase() {
@Test @Test
fun `test rhs with triangle brackets`() { fun `test rhc with triangle brackets`() {
configureByText("\n") configureByText("\n")
enterCommand("inoremap p <p>") enterCommand("inoremap p <p>")
typeText("ip") typeText("ip")
@ -1038,8 +942,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("nmap ,g :action Back<C-M>") enterCommand("nmap ,g :action Back<C-M>")
enterCommand("nmap ,h :action Back<C-m>") enterCommand("nmap ,h :action Back<C-m>")
enterCommand("nmap ,i :action Back<c-m>") enterCommand("nmap ,i :action Back<c-m>")
enterCommand("nmap")
assertCommandOutput("nmap", assertExOutput(
""" """
|n ,a <Action>(Back) |n ,a <Action>(Back)
|n ,b <Action>(Back) |n ,b <Action>(Back)
@ -1066,8 +970,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("nnoremap ,g :action Back<C-M>") enterCommand("nnoremap ,g :action Back<C-M>")
enterCommand("nnoremap ,h :action Back<C-m>") enterCommand("nnoremap ,h :action Back<C-m>")
enterCommand("nnoremap ,i :action Back<c-m>") enterCommand("nnoremap ,i :action Back<c-m>")
enterCommand("nnoremap")
assertCommandOutput("nnoremap", assertExOutput(
""" """
|n ,a <Action>(Back) |n ,a <Action>(Back)
|n ,b <Action>(Back) |n ,b <Action>(Back)

View File

@ -148,7 +148,7 @@ class MoveCommandTest : VimTestCase() {
enterCommand("m 0") enterCommand("m 0")
assertState( assertState(
""" """
${c}For example: homework, homework, homework, homework, homework, homework, homework, homework, homework. For example: homewor${c}k, homework, homework, homework, homework, homework, homework, homework, homework.
==== ====
My mother taught me this trick: if you repeat something over and over again it loses its meaning. My mother taught me this trick: if you repeat something over and over again it loses its meaning.
See, nothing. See, nothing.
@ -173,8 +173,8 @@ class MoveCommandTest : VimTestCase() {
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. |Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Morbi nec luctus tortor, id venenatis lacus. |Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus. |Nunc sit amet tellus vel purus cursus posuere et at purus.
|${c}Ut id dapibus augue. |Ut id dapibus augue.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui. |${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin() """.trimMargin()
) )
} }
@ -194,7 +194,7 @@ class MoveCommandTest : VimTestCase() {
assertState( assertState(
""" """
For example: homework, homework, homework, homework, homework, homework, homework, homework, homework. For example: homework, homework, homework, homework, homework, homework, homework, homework, homework.
${c}See, nothing. See, nothing.
==== ====
My mother taught me this trick: if you repeat something over and over again it loses its meaning. My mother taught me this trick: if you repeat something over and over again it loses its meaning.
""".trimIndent(), """.trimIndent(),
@ -216,7 +216,7 @@ class MoveCommandTest : VimTestCase() {
""" """
==== ====
My mother taught me this trick: if you repeat something over and over again it loses its meaning. My mother taught me this trick: if you repeat something over and over again it loses its meaning.
${c}See, nothing. See, not${c}hing.
For example: homework, homework, homework, homework, homework, homework, homework, homework, homework. For example: homework, homework, homework, homework, homework, homework, homework, homework, homework.
""".trimIndent(), """.trimIndent(),
) )
@ -238,103 +238,8 @@ class MoveCommandTest : VimTestCase() {
==== ====
My mother taught me this trick: if you repeat something over and over again it loses its meaning. My mother taught me this trick: if you repeat something over and over again it loses its meaning.
See, nothing. See, nothing.
${c}For example: homework, homework, homework, homework, homework, homework, homework, homework, homework. For example: homewor${c}k, homework, homework, homework, homework, homework, homework, homework, homework.
""".trimIndent(), """.trimIndent(),
) )
} }
// VIM-3837
@Test
fun `test moving relative line positions caret correctly`() {
doTest(
exCommand("+2m."), // Move the line 2 lines below, to below the current line
"""
|2
|1
|${c}3
|1
|2
""".trimMargin(),
"""
|2
|1
|3
|${c}2
|1
""".trimMargin()
)
}
@Test
fun `test moving relative line positions caret correctly 2`() {
doTest(
exCommand("+2m."), // Move the line 2 lines below, to below the current line
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet ${c}tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Ut id dapibus augue.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test moving lines positions caret correctly with nostartofline option`() {
doTest(
exCommand("+2m."), // Move the line 2 lines below, to below the current line
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet ${c}tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Orci varius na${c}toque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Ut id dapibus augue.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
) {
enterCommand("set nostartofline")
}
}
@Test
fun `test moving lines positions caret correctly with nostartofline option on shorter line`() {
doTest(
exCommand("+2m."), // Move the line 2 lines below, to below the current line
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id ${c}venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Ut id dapibus augue${c}.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
) {
enterCommand("set nostartofline")
}
}
} }

View File

@ -8,40 +8,33 @@
package org.jetbrains.plugins.ideavim.ex.implementation.commands package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.maddyhome.idea.vim.state.mode.Mode /*
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class NormalCommandTest : VimTestCase() { class NormalCommandTest : VimTestCase() {
@Test @Test
fun `test simple execution`() { fun `test simple execution`() {
doTest(exCommand("normal x"), "123<caret>456", "123<caret>56") doTest("normal x", "123<caret>456", "123<caret>56")
} }
@Test @Test
fun `test short command`() { fun `test short command`() {
doTest(exCommand("norm x"), "123<caret>456", "123<caret>56") doTest("norm x", "123<caret>456", "123<caret>56")
}
@Test
fun `test normal command automatically exits Insert mode`() {
doTest(exCommand("normal iFoo"), "123<caret>456", "123Fo<caret>o456", Mode.NORMAL())
} }
@Test @Test
fun `test multiple commands`() { fun `test multiple commands`() {
doTest(exCommand("normal xiNewText"), "123<caret>456", "123NewTex<caret>t56") doTest("normal xiNewText", "123<caret>456", "123NewTex<caret>t56")
} }
@Test @Test
fun `test normal command with current line range moves caret to start of line before executing command`() { fun `test range single stroke`() {
doTest(exCommand(".norm x"), "123<caret>456", "<caret>23456") doTest(".norm x", "123<caret>456", "<caret>23456")
} }
@Test @Test
fun `test normal command with multi-line range`() { fun `test range multiple strokes`() {
doTest( doTest(
exCommand("1,3norm x"), "1,3norm x",
""" """
123456 123456
123456 123456
@ -60,7 +53,7 @@ class NormalCommandTest : VimTestCase() {
} }
@Test @Test
fun `test normal command with single letter mapping`() { fun `test with mapping`() {
configureByText( configureByText(
""" """
<caret>123456 <caret>123456
@ -68,8 +61,8 @@ class NormalCommandTest : VimTestCase() {
123456 123456
""".trimIndent() """.trimIndent()
) )
enterCommand("map G dd") typeText(commandToKeys("map G dd"))
enterCommand("normal G") typeText(commandToKeys("normal G"))
assertState( assertState(
""" """
<caret>123456 <caret>123456
@ -79,28 +72,7 @@ class NormalCommandTest : VimTestCase() {
} }
@Test @Test
fun `test normal command with multi-letter mapping`() { fun `test with disabled mapping`() {
doTest(
exCommand("normal dd"),
"""
|${c}Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|${c}Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
) {
enterCommand("map dd G")
}
}
@Test
fun `test normal command with disabled mapping`() {
configureByText( configureByText(
""" """
<caret>123456 <caret>123456
@ -108,8 +80,8 @@ class NormalCommandTest : VimTestCase() {
123456 123456
""".trimIndent() """.trimIndent()
) )
enterCommand("map G dd") typeText(commandToKeys("map G dd"))
enterCommand("normal! G") typeText(commandToKeys("normal! G"))
assertState( assertState(
""" """
123456 123456
@ -120,7 +92,7 @@ class NormalCommandTest : VimTestCase() {
} }
@Test @Test
fun `test normal from Visual mode runs command on start of each line in range`() { fun `test from visual mode`() {
configureByText( configureByText(
""" """
<caret>123456 <caret>123456
@ -130,8 +102,8 @@ class NormalCommandTest : VimTestCase() {
123456 123456
""".trimIndent() """.trimIndent()
) )
typeText("Vjj") typeText(parseKeys("Vjj"))
enterCommand("normal x") // Will give `:'<,'>normal x` typeText(commandToKeys("normal x"))
assertState( assertState(
""" """
23456 23456
@ -144,7 +116,7 @@ class NormalCommandTest : VimTestCase() {
} }
@Test @Test
fun `test normal command switches to Visual mode`() { fun `test execute visual mode`() {
configureByText( configureByText(
""" """
<caret>123456 <caret>123456
@ -154,8 +126,8 @@ class NormalCommandTest : VimTestCase() {
123456 123456
""".trimIndent() """.trimIndent()
) )
enterCommand("normal Vjj") typeText(commandToKeys("normal Vjj"))
typeText("x") typeText(parseKeys("x"))
assertState( assertState(
""" """
<caret>123456 <caret>123456
@ -176,8 +148,8 @@ class NormalCommandTest : VimTestCase() {
123456 123456
""".trimIndent() """.trimIndent()
) )
typeText("qqxq", "jVjjj") typeText(parseKeys("qqxq", "jVjjj"))
enterCommand("norm @q") typeText(commandToKeys("norm @q"))
assertState( assertState(
""" """
23456 23456
@ -193,22 +165,29 @@ class NormalCommandTest : VimTestCase() {
@Test @Test
fun `test command executes at selection start`() { fun `test command executes at selection start`() {
configureByText("hello <caret>world !") configureByText("hello <caret>world !")
typeText("vw") typeText(parseKeys("vw"))
enterCommand("<C-u>norm x") typeText(parseKeys(":<C-u>norm x<CR>"))
assertState("hello <caret>orld !") assertState("hello <caret>orld !")
} }
@Test @Test
fun `test false escape`() { fun `test false escape`() {
configureByText("hello <caret>world !") configureByText("hello <caret>world !")
enterCommand("norm i<Esc>") typeText(commandToKeys("norm i<Esc>"))
assertState("hello <Esc<caret>>world !") assertState("hello <Esc<caret>>world !")
} }
@Test @Test
fun `test C-R`() { fun `test C-R`() {
configureByText("""myprop: "my value"""") configureByText("myprop: \"my value\"")
enterCommand("""exe "norm ^dei-\<C-R>\"-"""") typeText(commandToKeys("exe \"norm ^dei-\\<C-R>\\\"-\""))
assertState("""-myprop-: "my value"""") assertState("-myprop-: \"my value\"")
}
private fun doTest(command: String, before: String, after: String) {
myFixture.configureByText("a.java", before)
typeText(commandToKeys(command))
myFixture.checkResult(after)
} }
} }
*/

View File

@ -1,109 +0,0 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.vimscript.model.commands.SmileCommand
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class SmileCommandTest : VimTestCase() {
private fun loadResourceContent(resourcePath: String): String {
return SmileCommand::class.java.getResourceAsStream(resourcePath)
?.bufferedReader()
?.use { it.readText() }
?: throw IllegalStateException("Could not load resource: $resourcePath")
}
@Test
fun `test smile command with default file`() {
configureByText("\n")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.DEFAULT_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with kotlin file`() {
configureByText("\n")
configureByFileName("Test.kt")
typeText(commandToKeys("smile"))
val output = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent = loadResourceContent(SmileCommand.KOTLIN_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with kotlin script file`() {
configureByText("\n")
configureByFileName("Test.kts")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.KOTLIN_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with java file`() {
configureByText("\n")
configureByFileName("Test.java")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.JAVA_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with python file`() {
configureByText("\n")
configureByFileName("Test.py")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.PYTHON_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with unknown file extension`() {
configureByText("\n")
configureByFileName("Test.unknown")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.DEFAULT_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with gitignore file`() {
configureByText("\n")
configureByFileName(".gitignore")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.DEFAULT_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
}

View File

@ -192,32 +192,6 @@ class SubstituteCommandTest : VimTestCase() {
) )
} }
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test ampersand group`() {
doTest(
exCommand("s/a\\|b/z&/g"),
"${c}abcdefg",
"zazbcdefg",
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test missing group`() {
doTest(
exCommand("s/b/<\\7>/"),
"${c}abc",
"a<>c",
)
}
@OptionTest( @OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true), VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true), VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
@ -1354,60 +1328,4 @@ class SubstituteCommandTest : VimTestCase() {
" comment ", " comment ",
) )
} }
// VIM-3510
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test replace action U`() {
doTest(
exCommand("s/\\(foo\\)/\\U\\1bar/"),
"${c}a foo",
"a FOOBAR",
)
}
// VIM-3510
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test replace action U and E`() {
doTest(
exCommand("s/\\(foo\\)/\\U\\1\\ebar/"),
"${c}a foo",
"a FOObar",
)
}
// VIM-3510
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test replace action u`() {
doTest(
exCommand("s/\\(foo\\)/\\u\\1bar/"),
"${c}a foo",
"a Foobar",
)
}
// VIM-3510
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test replace action u and empty group`() {
doTest(
exCommand("s/a foo\\(\\)/a foo\\u\\1bar/"),
"${c}a foo",
"a fooBar",
)
}
} }

View File

@ -10,7 +10,6 @@ package org.jetbrains.plugins.ideavim.ex.implementation.expressions.datatypes
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFloat import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFloat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.util.Locale
import kotlin.test.assertEquals import kotlin.test.assertEquals
class VimFloatTest { class VimFloatTest {
@ -24,15 +23,4 @@ class VimFloatTest {
fun `round 7 digits`() { fun `round 7 digits`() {
assertEquals("1.0", VimFloat(0.9999999).toString()) assertEquals("1.0", VimFloat(0.9999999).toString())
} }
@Test
fun `use point as decimal separator always`() {
val oldLocale = Locale.getDefault()
Locale.setDefault(Locale.GERMANY) // In Germany, they use a comma as a decimal separator, i.e., "3,14".
try {
assertEquals("3.14", VimFloat(3.14).toString())
} finally {
Locale.setDefault(oldLocale)
}
}
} }

View File

@ -11,7 +11,6 @@ package org.jetbrains.plugins.ideavim.ex.implementation.expressions.operators
import com.maddyhome.idea.vim.ex.ExException import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.ex.evaluate import org.jetbrains.plugins.ideavim.ex.evaluate
import org.jetbrains.plugins.ideavim.productForArguments import org.jetbrains.plugins.ideavim.productForArguments
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
@ -19,7 +18,7 @@ import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import kotlin.test.assertEquals import kotlin.test.assertEquals
class ConcatenationOperatorTest : VimTestCase() { class ConcatenationOperatorTest {
companion object { companion object {
@JvmStatic @JvmStatic
@ -48,7 +47,7 @@ class ConcatenationOperatorTest : VimTestCase() {
try { try {
VimscriptParser.parseExpression("3.4$sp1$operator${sp2}2")!!.evaluate() VimscriptParser.parseExpression("3.4$sp1$operator${sp2}2")!!.evaluate()
} catch (e: ExException) { } catch (e: ExException) {
assertEquals("E806: Using a Float as a String", e.message) assertEquals("E806: using Float as a String", e.message)
} }
} }
@ -58,7 +57,7 @@ class ConcatenationOperatorTest : VimTestCase() {
try { try {
VimscriptParser.parseExpression("3.4$sp1$operator${sp2}2.2")!!.evaluate() VimscriptParser.parseExpression("3.4$sp1$operator${sp2}2.2")!!.evaluate()
} catch (e: ExException) { } catch (e: ExException) {
assertEquals("E806: Using a Float as a String", e.message) assertEquals("E806: using Float as a String", e.message)
} }
} }
@ -68,7 +67,7 @@ class ConcatenationOperatorTest : VimTestCase() {
try { try {
VimscriptParser.parseExpression("'string'$sp1$operator${sp2}3.4")!!.evaluate() VimscriptParser.parseExpression("'string'$sp1$operator${sp2}3.4")!!.evaluate()
} catch (e: ExException) { } catch (e: ExException) {
assertEquals("E806: Using a Float as a String", e.message) assertEquals("E806: using Float as a String", e.message)
} }
} }

View File

@ -1,80 +0,0 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.ex.implementation.expressions.operators
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.ex.evaluate
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class DoesNotMatchOperatorTest : VimTestCase() {
@Test
fun `test does not match operator returns false when pattern matches string`() {
assertFalse(evaluate("'lorem ipsum' !~ 'l*sum'").asBoolean())
}
@Test
fun `test does not match operator returns true when pattern does not match string`() {
assertTrue(evaluate("'lorem ipsum' !~ 'l*foo'").asBoolean())
}
@Test
fun `test does not match operator returns false when pattern matches case`() {
assertFalse(evaluate("'Lorem Ipsum' !~ 'L*I'").asBoolean())
}
@Test
fun `test does not match operator returns true when pattern does not match case`() {
assertTrue(evaluate("'Lorem Ipsum' !~ 'l*i'").asBoolean())
}
@Test
fun `test does not match operator with 'noignorecase' returns false with different case pattern`() {
injector.globalOptions().ignorecase = true // Default is false
assertFalse(evaluate("'Lorem Ipsum' !~ 'l*i'").asBoolean())
}
// Case-sensitive operator
@Test
fun `test case-sensitive does not match operator returns false when pattern matches string`() {
assertFalse(evaluate("'lorem ipsum' !~# 'l*sum'").asBoolean())
}
@Test
fun `test case-sensitive does not match operator returns true when pattern does not match string`() {
assertTrue(evaluate("'lorem ipsum' !~# 'l*foo'").asBoolean())
}
@Test
fun `test case-sensitive does not match operator returns true when pattern does not match string case`() {
assertTrue(evaluate("'lorem ipsum' !~# 'L*Sum'").asBoolean())
}
// Case-insensitive operator
@Test
fun `test case-insensitive does not match operator returns false when pattern matches string`() {
assertFalse(evaluate("'lorem ipsum' !~? 'l*sum'").asBoolean())
}
@Test
fun `test case-insensitive does not match operator returns true when pattern does not match string`() {
assertTrue(evaluate("'lorem ipsum' !~? 'l*foo'").asBoolean())
}
@Test
fun `test case-insensitive does not match operator returns false when pattern does not match string case`() {
assertFalse(evaluate("'lorem ipsum' !~? 'L*Sum'").asBoolean())
}
private fun evaluate(expression: String) = VimscriptParser.parseExpression(expression)!!.evaluate()
}

View File

@ -8,9 +8,6 @@
package org.jetbrains.plugins.ideavim.ex.implementation.expressions.operators package org.jetbrains.plugins.ideavim.ex.implementation.expressions.operators
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDictionary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFloat
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
@ -23,67 +20,25 @@ import kotlin.test.assertEquals
class FalsyOperatorTest : VimTestCase() { class FalsyOperatorTest : VimTestCase() {
@Test @Test
fun `test non-zero Number treated as truthy`() { fun `left expression is true`() {
assertEquals(VimInt(42), VimscriptParser.parseExpression("42 ?? 999")!!.evaluate()) assertEquals(VimInt("42"), VimscriptParser.parseExpression("42 ?? 999")!!.evaluate())
} }
@Test @Test
fun `test non-zero negative Number treated as truthy`() { fun `left expression is false`() {
assertEquals(VimInt(-1), VimscriptParser.parseExpression("-1 ?? 999")!!.evaluate()) assertEquals(VimInt("42"), VimscriptParser.parseExpression("0 ?? 42")!!.evaluate())
} }
@Test @Test
fun `test Number 0 is treated as falsy`() { fun `empty list as a left expression`() {
assertEquals(VimInt(42), VimscriptParser.parseExpression("0 ?? 42")!!.evaluate())
}
@Test
fun `test zero Float is treated as falsy`() {
assertEquals(VimInt(42), VimscriptParser.parseExpression("0.0 ?? 42")!!.evaluate())
}
@Test
fun `test non-zero Float treated as truthy`() {
assertEquals(VimFloat(42.0), VimscriptParser.parseExpression("42.0 ?? 999")!!.evaluate())
}
@Test
fun `test empty String treated as falsy`() {
assertEquals(VimString("string is empty"), VimscriptParser.parseExpression("'' ?? 'string is empty'")!!.evaluate())
}
@Test
fun `test String '0' treated as falsy`() {
assertEquals(VimString("string is empty"), VimscriptParser.parseExpression("'0' ?? 'string is empty'")!!.evaluate())
}
@Test
fun `test non-empty String treated as truthy`() {
assertEquals(VimString("string is not empty"), VimscriptParser.parseExpression("'string is not empty' ?? 'string is empty'")!!.evaluate())
}
@Test
fun `test empty list treated as falsy`() {
assertEquals(VimString("list is empty"), VimscriptParser.parseExpression("[] ?? 'list is empty'")!!.evaluate()) assertEquals(VimString("list is empty"), VimscriptParser.parseExpression("[] ?? 'list is empty'")!!.evaluate())
} }
@Test @Test
fun `test non-empty List treated as truthy`() { fun `nonempty list as a left expression`() {
assertEquals( assertEquals(
VimList(mutableListOf(VimInt(1), VimInt(2), VimInt(3))), VimList(mutableListOf(VimInt(1), VimInt(2), VimInt(3))),
VimscriptParser.parseExpression("[1, 2, 3] ?? 'list is empty'")!!.evaluate(), VimscriptParser.parseExpression("[1, 2, 3] ?? 'list is empty'")!!.evaluate(),
) )
} }
@Test
fun `test empty Dictionary treated as falsy`() {
assertEquals(VimString("dict is empty"), VimscriptParser.parseExpression("{} ?? 'dict is empty'")!!.evaluate())
}
@Test
fun `test non-empty Dictionary treated as truthy`() {
val dictionary = LinkedHashMap<VimString, VimDataType>()
dictionary.put(VimString("1"), VimInt(1))
assertEquals(VimDictionary(dictionary), VimscriptParser.parseExpression("{'1': 1} ?? 'dict is empty'")!!.evaluate())
}
} }

View File

@ -1,80 +0,0 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.ex.implementation.expressions.operators
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.ex.evaluate
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class MatchesOperatorTest : VimTestCase() {
@Test
fun `test matches operator returns true when pattern matches string`() {
assertTrue(evaluate("'lorem ipsum' =~ 'l*sum'").asBoolean())
}
@Test
fun `test matches operator returns false when pattern does not match string`() {
assertFalse(evaluate("'lorem ipsum' =~ 'l*foo'").asBoolean())
}
@Test
fun `test matches operator returns true when pattern matches case`() {
assertTrue(evaluate("'Lorem Ipsum' =~ 'L*I'").asBoolean())
}
@Test
fun `test matches operator returns false when pattern does not match case`() {
assertFalse(evaluate("'Lorem Ipsum' =~ 'l*i'").asBoolean())
}
@Test
fun `test matches operator with 'noignorecase' returns true with different case pattern`() {
injector.globalOptions().ignorecase = true // Default is false
assertTrue(evaluate("'Lorem Ipsum' =~ 'l*i'").asBoolean())
}
// Case-sensitive operator
@Test
fun `test case-sensitive matches operator returns true when pattern matches string`() {
assertTrue(evaluate("'lorem ipsum' =~# 'l*sum'").asBoolean())
}
@Test
fun `test case-sensitive matches operator returns false when pattern does not match string`() {
assertFalse(evaluate("'lorem ipsum' =~# 'l*foo'").asBoolean())
}
@Test
fun `test case-sensitive matches operator returns false when pattern does not match string case`() {
assertFalse(evaluate("'lorem ipsum' =~# 'L*Sum'").asBoolean())
}
// Case-insensitive operator
@Test
fun `test case-insensitive matches operator returns true when pattern matches string`() {
assertTrue(evaluate("'lorem ipsum' =~? 'l*sum'").asBoolean())
}
@Test
fun `test case-insensitive matches operator returns false when pattern does not match string`() {
assertFalse(evaluate("'lorem ipsum' =~? 'l*foo'").asBoolean())
}
@Test
fun `test case-insensitive matches operator returns true when pattern does not match string case`() {
assertTrue(evaluate("'lorem ipsum' =~? 'L*Sum'").asBoolean())
}
private fun evaluate(expression: String) = VimscriptParser.parseExpression(expression)!!.evaluate()
}

View File

@ -37,7 +37,8 @@ class ColFunctionTest : VimTestCase() {
// With selection - make sure to delete the '<,'> that is automatically prepended when entering Command-line mode // With selection - make sure to delete the '<,'> that is automatically prepended when entering Command-line mode
// with a selection // with a selection
typeText("vll") typeText("vll")
assertCommandOutput("""<C-U>echo col("v")""", "5") typeText(":<C-U>echo col(\"v\")<CR>") // enterCommand/assertCommandOutput cannot handle <C-U>!
assertExOutput("5")
// Remove selection and check again - note that exiting Command-line mode removes selection and switches back to // Remove selection and check again - note that exiting Command-line mode removes selection and switches back to
// Normal. This <esc> does nothing // Normal. This <esc> does nothing

View File

@ -32,7 +32,8 @@ class LineFunctionTest : VimTestCase() {
// With selection - make sure to delete the '<,'> that is automatically prepended when entering Command-line mode // With selection - make sure to delete the '<,'> that is automatically prepended when entering Command-line mode
// with a selection // with a selection
typeText("vj") typeText("vj")
assertCommandOutput("""<C-U>echo line("v")""", "3") typeText(""":<C-U>echo line("v")<CR>""") // enterCommand/assertCommandOutput cannot handle <C-U>!
assertExOutput("3")
// Remove selection and check again - note that exiting Command-line mode removes selection and switches back to // Remove selection and check again - note that exiting Command-line mode removes selection and switches back to
// Normal. This <esc> does nothing // Normal. This <esc> does nothing
@ -45,50 +46,4 @@ class LineFunctionTest : VimTestCase() {
"0 1 5 0 5 0" "0 1 5 0 5 0"
) )
} }
@Test
fun `test line with selection`() {
doTest(
listOf("V2j", "q"),
"""
|1
|${c}2
|3
|4
|5
""".trimMargin(),
"""
|1
|2
|3
|4 - line(v)==${c}2
|5
""".trimMargin(),
) {
enterCommand("vmap <expr> q '<Esc>a - line(v)=='.line('v').'<Esc>'")
}
}
@Test
fun `test line with reverse selection`() {
doTest(
listOf("V2k", "q"),
"""
|1
|2
|3
|${c}4
|5
""".trimMargin(),
"""
|1
|2 - line(v)==${c}4
|3
|4
|5
""".trimMargin(),
) {
enterCommand("vmap <expr> q '<Esc>a - line(v)=='.line('v').'<Esc>'")
}
}
} }

View File

@ -46,7 +46,9 @@ class RegisterVariableTest : VimTestCase() {
typeText("vl\"zy") typeText("vl\"zy")
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor) val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val register = injector.registerGroup.getRegisters(vimEditor, context).first { reg -> reg.name == 'z' } val register = injector.registerGroup.getRegisters(vimEditor, context)
.filter { reg -> reg.name == 'z' }
.first()
assertEquals("ab", register.text) assertEquals("ab", register.text)
} }

View File

@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.extension.miniai package org.jetbrains.plugins.ideavim.extension.miniai
import com.intellij.ide.highlighter.JavaFileType
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.BeforeEach import org.junit.jupiter.api.BeforeEach
@ -30,6 +31,7 @@ class MiniAIExtensionTest : VimTestCase() {
"<caret>This is a \"'simple'\" test", "<caret>This is a \"'simple'\" test",
"This is a \"<caret>\" test", "This is a \"<caret>\" test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -42,6 +44,7 @@ class MiniAIExtensionTest : VimTestCase() {
"'balanced'false <caret>string'balanced'", "'balanced'false <caret>string'balanced'",
"'balanced'false string'<caret>'", "'balanced'false string'<caret>'",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -54,6 +57,7 @@ class MiniAIExtensionTest : VimTestCase() {
"\"balanced\"false <caret>string\"balanced\"", "\"balanced\"false <caret>string\"balanced\"",
"\"balanced\"false string\"<caret>\"", "\"balanced\"false string\"<caret>\"",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -66,6 +70,7 @@ class MiniAIExtensionTest : VimTestCase() {
"`balanced`false <caret>string`balanced`", "`balanced`false <caret>string`balanced`",
"`balanced`false string`<caret>`", "`balanced`false string`<caret>`",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -82,6 +87,7 @@ class MiniAIExtensionTest : VimTestCase() {
'""", '""",
"''", "''",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -97,6 +103,7 @@ class MiniAIExtensionTest : VimTestCase() {
"""", """",
"\"\"", "\"\"",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -113,6 +120,7 @@ class MiniAIExtensionTest : VimTestCase() {
`""", `""",
"``", "``",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -134,6 +142,7 @@ class MiniAIExtensionTest : VimTestCase() {
'<caret>' '<caret>'
""", """,
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -152,6 +161,7 @@ print(something)
{} {}
""", """,
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -173,6 +183,7 @@ print(something)
} }
""".trimIndent(), """.trimIndent(),
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -185,6 +196,7 @@ print(something)
"<caret>This is a `'simple'` test", "<caret>This is a `'simple'` test",
"This is a `<caret>` test", "This is a `<caret>` test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -198,6 +210,7 @@ print(something)
"<caret>This is a '\"simple\"' test", "<caret>This is a '\"simple\"' test",
"This is a '<caret>' test", "This is a '<caret>' test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -209,6 +222,7 @@ print(something)
"this 'simple<caret> \"test\"'", "this 'simple<caret> \"test\"'",
"this '<caret>'", "this '<caret>'",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -220,6 +234,7 @@ print(something)
"this 'simple<caret> \"test\"'", "this 'simple<caret> \"test\"'",
"this '<caret>'", "this '<caret>'",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -231,6 +246,7 @@ print(something)
"this \"simple<caret> 'test'\"", "this \"simple<caret> 'test'\"",
"this \"<caret>\"", "this \"<caret>\"",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -242,6 +258,7 @@ print(something)
"this \"simple<caret> 'test'\"", "this \"simple<caret> 'test'\"",
"this \"<caret>\"", "this \"<caret>\"",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -253,6 +270,7 @@ print(something)
"this `simple<caret> \"test\"`", "this `simple<caret> \"test\"`",
"this `<caret>`", "this `<caret>`",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -264,6 +282,7 @@ print(something)
"this `simple<caret> \"test\"`", "this `simple<caret> \"test\"`",
"this `<caret>`", "this `<caret>`",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -275,6 +294,7 @@ print(something)
"this 'simple<caret> \"test\"'", "this 'simple<caret> \"test\"'",
"this <caret>", "this <caret>",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -286,6 +306,7 @@ print(something)
"this 'simple<caret> \"test\"' test", "this 'simple<caret> \"test\"' test",
"this <caret> test", "this <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -297,6 +318,7 @@ print(something)
"this \"simple<caret> 'test'\"", "this \"simple<caret> 'test'\"",
"this <caret>", "this <caret>",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -308,6 +330,7 @@ print(something)
"this \"simple<caret> 'test'\" test", "this \"simple<caret> 'test'\" test",
"this <caret> test", "this <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -319,6 +342,7 @@ print(something)
"this `simple<caret> \"test\"`", "this `simple<caret> \"test\"`",
"this <caret>", "this <caret>",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -330,6 +354,7 @@ print(something)
"this `simple<caret> \"test\"` test", "this `simple<caret> \"test\"` test",
"this <caret> test", "this <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -342,6 +367,7 @@ print(something)
"this 'simple \"<caret>test\"'", "this 'simple \"<caret>test\"'",
"this 'simple <caret>'", "this 'simple <caret>'",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -353,6 +379,7 @@ print(something)
"this 'simple \"<caret>test\"'", "this 'simple \"<caret>test\"'",
"this 'simple '", "this 'simple '",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -365,6 +392,7 @@ print(something)
"this 'simple \"nested `<caret>test`\"'", "this 'simple \"nested `<caret>test`\"'",
"this 'simple \"nested <caret>\"'", "this 'simple \"nested <caret>\"'",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -376,6 +404,7 @@ print(something)
"this 'simple \"nested `<caret>test`\"'", "this 'simple \"nested `<caret>test`\"'",
"this 'simple \"nested <caret>\"'", "this 'simple \"nested <caret>\"'",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -387,6 +416,7 @@ print(something)
"<caret>This is a 'simple' test", "<caret>This is a 'simple' test",
"This is a '<caret>' test", "This is a '<caret>' test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -399,6 +429,7 @@ print(something)
"<caret>This is a 'simple test", "<caret>This is a 'simple test",
"<caret>This is a 'simple test", "<caret>This is a 'simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -411,6 +442,7 @@ print(something)
"<caret>This is a \"simple\" test", "<caret>This is a \"simple\" test",
"This is a \"<caret>\" test", "This is a \"<caret>\" test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -423,6 +455,7 @@ print(something)
"<caret>This is a \"simple test", "<caret>This is a \"simple test",
"<caret>This is a \"simple test", "<caret>This is a \"simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -435,6 +468,7 @@ print(something)
"<caret>This is a `simple` test", "<caret>This is a `simple` test",
"This is a `<caret>` test", "This is a `<caret>` test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -447,6 +481,7 @@ print(something)
"<caret>This is a `simple test", "<caret>This is a `simple test",
"<caret>This is a `simple test", "<caret>This is a `simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -459,6 +494,7 @@ print(something)
"<caret>This is a 'simple' test", "<caret>This is a 'simple' test",
"This is a <caret> test", "This is a <caret> test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -471,6 +507,7 @@ print(something)
"<caret>This is a 'simple test", "<caret>This is a 'simple test",
"<caret>This is a 'simple test", "<caret>This is a 'simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -483,6 +520,7 @@ print(something)
"<caret>This is a \"simple\" test", "<caret>This is a \"simple\" test",
"This is a <caret> test", "This is a <caret> test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -495,6 +533,7 @@ print(something)
"<caret>This is a \"simple test", "<caret>This is a \"simple test",
"<caret>This is a \"simple test", "<caret>This is a \"simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -506,6 +545,7 @@ print(something)
"<caret>This is a `simple` test", "<caret>This is a `simple` test",
"This is a <caret> test", "This is a <caret> test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -518,6 +558,7 @@ print(something)
"<caret>This is a `simple test", "<caret>This is a `simple test",
"<caret>This is a `simple test", "<caret>This is a `simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -529,6 +570,7 @@ print(something)
"<caret>This is a (simple) test", "<caret>This is a (simple) test",
"This is a (<caret>) test", "This is a (<caret>) test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -541,6 +583,7 @@ print(something)
"<caret>This is a (simple test", "<caret>This is a (simple test",
"<caret>This is a (simple test", "<caret>This is a (simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -553,6 +596,7 @@ print(something)
"<caret>This is a [simple] test", "<caret>This is a [simple] test",
"This is a [<caret>] test", "This is a [<caret>] test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -565,6 +609,7 @@ print(something)
"<caret>This is a [simple test", "<caret>This is a [simple test",
"<caret>This is a [simple test", "<caret>This is a [simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -577,6 +622,7 @@ print(something)
"<caret>This is a {simple} test", "<caret>This is a {simple} test",
"This is a {<caret>} test", "This is a {<caret>} test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -589,6 +635,7 @@ print(something)
"<caret>This is a {simple test", "<caret>This is a {simple test",
"<caret>This is a {simple test", "<caret>This is a {simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -601,6 +648,7 @@ print(something)
"<caret>This is a (simple) test", "<caret>This is a (simple) test",
"This is a <caret> test", "This is a <caret> test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -613,6 +661,7 @@ print(something)
"<caret>This is a (simple test", "<caret>This is a (simple test",
"<caret>This is a (simple test", "<caret>This is a (simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -625,6 +674,7 @@ print(something)
"<caret>This is a [simple] test", "<caret>This is a [simple] test",
"This is a <caret> test", "This is a <caret> test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -636,6 +686,7 @@ print(something)
"<caret>This is a [simple test", "<caret>This is a [simple test",
"<caret>This is a [simple test", "<caret>This is a [simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -648,6 +699,7 @@ print(something)
"<caret>This is a {simple} test", "<caret>This is a {simple} test",
"This is a <caret> test", "This is a <caret> test",
Mode.INSERT, Mode.INSERT,
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -660,6 +712,7 @@ print(something)
"<caret>This is a {simple test", "<caret>This is a {simple test",
"<caret>This is a {simple test", "<caret>This is a {simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -672,6 +725,7 @@ print(something)
"<caret>This is a 'simple' test", "<caret>This is a 'simple' test",
"This is a '<caret>' test", "This is a '<caret>' test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -684,6 +738,7 @@ print(something)
"<caret>This is a 'simple test", "<caret>This is a 'simple test",
"<caret>This is a 'simple test", "<caret>This is a 'simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -696,6 +751,7 @@ print(something)
"<caret>This is a \"simple\" test", "<caret>This is a \"simple\" test",
"This is a \"<caret>\" test", "This is a \"<caret>\" test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -708,6 +764,7 @@ print(something)
"<caret>This is a \"simple test", "<caret>This is a \"simple test",
"<caret>This is a \"simple test", "<caret>This is a \"simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -720,6 +777,7 @@ print(something)
"<caret>This is a `simple` test", "<caret>This is a `simple` test",
"This is a `<caret>` test", "This is a `<caret>` test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -732,6 +790,7 @@ print(something)
"<caret>This is a `simple test", "<caret>This is a `simple test",
"<caret>This is a `simple test", "<caret>This is a `simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -744,6 +803,7 @@ print(something)
"<caret>This is a 'simple' test", "<caret>This is a 'simple' test",
"This is a <caret> test", "This is a <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -755,6 +815,7 @@ print(something)
"<caret>This is a 'simple test", "<caret>This is a 'simple test",
"<caret>This is a 'simple test", "<caret>This is a 'simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -767,6 +828,7 @@ print(something)
"<caret>This is a \"simple\" test", "<caret>This is a \"simple\" test",
"This is a <caret> test", "This is a <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -779,6 +841,7 @@ print(something)
"<caret>This is a \"simple test", "<caret>This is a \"simple test",
"<caret>This is a \"simple test", "<caret>This is a \"simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -791,6 +854,7 @@ print(something)
"<caret>This is a `simple` test", "<caret>This is a `simple` test",
"This is a <caret> test", "This is a <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -803,6 +867,7 @@ print(something)
"<caret>This is a `simple test", "<caret>This is a `simple test",
"<caret>This is a `simple test", "<caret>This is a `simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -815,6 +880,7 @@ print(something)
"<caret>This is a (simple) test", "<caret>This is a (simple) test",
"This is a (<caret>) test", "This is a (<caret>) test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -827,6 +893,7 @@ print(something)
"<caret>This is a (simple test", "<caret>This is a (simple test",
"<caret>This is a (simple test", "<caret>This is a (simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -839,6 +906,7 @@ print(something)
"<caret>This is a [simple] test", "<caret>This is a [simple] test",
"This is a [<caret>] test", "This is a [<caret>] test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -851,6 +919,7 @@ print(something)
"<caret>This is a [simple test", "<caret>This is a [simple test",
"<caret>This is a [simple test", "<caret>This is a [simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -863,6 +932,7 @@ print(something)
"<caret>This is a {simple} test", "<caret>This is a {simple} test",
"This is a {<caret>} test", "This is a {<caret>} test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -875,6 +945,7 @@ print(something)
"<caret>This is a {simple test", "<caret>This is a {simple test",
"<caret>This is a {simple test", "<caret>This is a {simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -887,6 +958,7 @@ print(something)
"<caret>This is a (simple) test", "<caret>This is a (simple) test",
"This is a <caret> test", "This is a <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -899,6 +971,7 @@ print(something)
"<caret>This is a (simple test", "<caret>This is a (simple test",
"<caret>This is a (simple test", "<caret>This is a (simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -911,6 +984,7 @@ print(something)
"<caret>This is a [simple] test", "<caret>This is a [simple] test",
"This is a <caret> test", "This is a <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -923,6 +997,7 @@ print(something)
"<caret>This is a [simple test", "<caret>This is a [simple test",
"<caret>This is a [simple test", "<caret>This is a [simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -935,6 +1010,7 @@ print(something)
"<caret>This is a {simple} test", "<caret>This is a {simple} test",
"This is a <caret> test", "This is a <caret> test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }
@ -947,6 +1023,7 @@ print(something)
"<caret>This is a {simple test", "<caret>This is a {simple test",
"<caret>This is a {simple test", "<caret>This is a {simple test",
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE,
) )
assertSelection(null) assertSelection(null)
} }

View File

@ -8,7 +8,6 @@
package org.jetbrains.plugins.ideavim.extension.replacewithregister package org.jetbrains.plugins.ideavim.extension.replacewithregister
import com.intellij.idea.TestFor
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
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
@ -651,137 +650,4 @@ class ReplaceWithRegisterTest : VimTestCase() {
assertState("one two three") assertState("one two three")
enterCommand("set clipboard&") enterCommand("set clipboard&")
} }
@TestFor(issues = ["VIM-2263"])
@Test
fun `test replace with count from register a`() {
val before = """
${c}first line
second line
third line
fourth line
fifth line
""".trimIndent()
configureByText(before)
typeText("\"ay$")
val context = injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim)
val aRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first line", aRegText)
val defaultRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("first line", defaultRegText )
typeText("j")
typeText("y$")
val aRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first line", aRegTextAfter)
val defaultRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("second line", defaultRegTextAfter )
typeText("\"a2grr")
val expected = """
first line
${c}first line
fourth line
fifth line
""".trimIndent()
assertState(expected)
}
@TestFor(issues = ["VIM-2263"])
@Test
fun `test replace from the register a (without count)`() {
val before = """
${c}first line
second line
third line
fourth line
fifth line
""".trimIndent()
configureByText(before)
typeText("\"ay$")
val context = injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim)
val aRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first line", aRegText)
val defaultRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("first line", defaultRegText )
typeText("j")
typeText("y$")
val aRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first line", aRegTextAfter)
val defaultRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("second line", defaultRegTextAfter )
typeText("\"agrr")
val expected = """
first line
${c}first line
third line
fourth line
fifth line
""".trimIndent()
assertState(expected)
}
@TestFor(issues = ["VIM-2263"])
@Test
fun `test replace from the register a to the end of the line`() {
val before = """
${c}first line
second line
third line
fourth line
fifth line
""".trimIndent()
configureByText(before)
typeText("\"ayiw")
val context = injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim)
val aRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first", aRegText)
val defaultRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("first", defaultRegText )
typeText("j")
typeText("y$")
typeText("w")
val aRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first", aRegTextAfter)
val defaultRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("second line", defaultRegTextAfter )
typeText("\"agr$")
val expected = """
first line
second firs${c}t
third line
fourth line
fifth line
""".trimIndent()
assertState(expected)
}
} }

View File

@ -633,17 +633,6 @@ class VimSurroundExtensionTest : VimTestCase() {
doTest(listOf(motion), before, after, Mode.NORMAL()) doTest(listOf(motion), before, after, Mode.NORMAL())
} }
// VIM-3841
@Test
fun `test return to Normal mode after surround in Visual mode`() {
doTest(
listOf("veS\"", "i"),
"lorem ${c}ipsum dolor sit amet",
"lorem ${c}\"ipsum\" dolor sit amet",
Mode.INSERT,
)
}
companion object { companion object {
@JvmStatic @JvmStatic
fun removeWhiteSpaceWithClosingBracketParams() = listOf( fun removeWhiteSpaceWithClosingBracketParams() = listOf(

View File

@ -32,7 +32,7 @@ class VimListenersTest : VimTestCase() {
super.setUp(testInfo) super.setUp(testInfo)
val manager = val manager =
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "VimListenersTest")) FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable) fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable)
VimListenerTestObject.disposedCounter = 0 VimListenerTestObject.disposedCounter = 0

View File

@ -67,7 +67,7 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
// Copied from FileEditorManagerTestCase to allow us to split windows // Copied from FileEditorManagerTestCase to allow us to split windows
manager = manager =
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "EffectiveOptionChangeListenerTest")) FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable) fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable)
// Create a new editor that will represent a new buffer in a separate window. It will have default values // Create a new editor that will represent a new buffer in a separate window. It will have default values

View File

@ -61,7 +61,7 @@ class OptionDeclaredScopeTest : VimTestCase() {
super.setUp(testInfo) super.setUp(testInfo)
// Copied from FileEditorManagerTestCase to allow us to split windows // Copied from FileEditorManagerTestCase to allow us to split windows
fileEditorManager = FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "OptionDeclaredScopeTest")) fileEditorManager = FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
fixture.project.replaceService(FileEditorManager::class.java, fileEditorManager, fixture.testRootDisposable) fixture.project.replaceService(FileEditorManager::class.java, fileEditorManager, fixture.testRootDisposable)
// Create a new editor that will represent a new buffer in a separate window. It will have default values // Create a new editor that will represent a new buffer in a separate window. It will have default values
@ -160,7 +160,7 @@ class OptionDeclaredScopeTest : VimTestCase() {
} }
val virtualFile = editor.virtualFile val virtualFile = editor.virtualFile
if (virtualFile != null) { if (editorWindow != null && virtualFile != null) {
editorWindow.closeFile(virtualFile) editorWindow.closeFile(virtualFile)
editorWindow.requestFocus(true) editorWindow.requestFocus(true)
} }

View File

@ -50,7 +50,7 @@ class TextWidthOptionMapperTest : VimTestCase() {
// Copied from FileEditorManagerTestCase to allow us to split windows // Copied from FileEditorManagerTestCase to allow us to split windows
val manager = val manager =
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "TextWidthOptionMapperTest")) FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable) fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable)
ApplicationManager.getApplication().invokeAndWait { ApplicationManager.getApplication().invokeAndWait {

View File

@ -37,7 +37,7 @@ class WrapOptionMapperTest : VimTestCase() {
// Copied from FileEditorManagerTestCase to allow us to split windows // Copied from FileEditorManagerTestCase to allow us to split windows
manager = manager =
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "WrapOptionMapperTest")) FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable) fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable)
configureByText("\n") configureByText("\n")

View File

@ -8,7 +8,6 @@
package org.jetbrains.plugins.ideavim.ui package org.jetbrains.plugins.ideavim.ui
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.ui.ShowCmd import com.maddyhome.idea.vim.ui.ShowCmd
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
@ -17,6 +16,7 @@ import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.waitAndAssert import org.jetbrains.plugins.ideavim.waitAndAssert
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
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
@ -87,12 +87,15 @@ class ShowCmdTest : VimTestCase() {
assertEquals("32d", getShowCmdText()) assertEquals("32d", getShowCmdText())
} }
// TODO: This test fails because IdeaVim's mapping handler doesn't correctly expand unhandled keys on timeout
@Test @Test
@Disabled
fun `test showcmd expands ambiguous mapped keys on timeout`() { fun `test showcmd expands ambiguous mapped keys on timeout`() {
// `rrr` should timeout and replay `rr` which is mapped to `42`
enterCommand("nmap rr 42") enterCommand("nmap rr 42")
enterCommand("nmap rrr 55") enterCommand("nmap rrr 55")
typeText(injector.parser.parseKeys("12rr")) typeText(injector.parser.parseKeys("12rr"))
waitAndAssert(injector.globalOptions().timeoutlen + 100) { "1242" == getShowCmdText() } waitAndAssert { "1242" == getShowCmdText() }
} }
@TestWithoutNeovim(reason = SkipNeovimReason.SHOW_CMD) @TestWithoutNeovim(reason = SkipNeovimReason.SHOW_CMD)

View File

@ -137,7 +137,7 @@ abstract class VimTestCase {
VimPlugin.getSearch().resetState() VimPlugin.getSearch().resetState()
if (VimPlugin.isNotEnabled()) VimPlugin.setEnabled(true) if (VimPlugin.isNotEnabled()) VimPlugin.setEnabled(true)
injector.globalOptions().ideastrictmode = true injector.globalOptions().ideastrictmode = true
Checks.reset() VimTestCase.Checks.reset()
clearClipboard() clearClipboard()
// Make sure the entry text field gets a bounds, or we won't be able to work out caret location // Make sure the entry text field gets a bounds, or we won't be able to work out caret location
@ -442,9 +442,9 @@ abstract class VimTestCase {
editor, editor,
) )
val project = fixture.project val project = fixture.project
when (Checks.keyHandler) { when (VimTestCase.Checks.keyHandler) {
Checks.KeyHandlerMethod.DIRECT_TO_VIM -> typeText(keys.filterNotNull(), editor, project) VimTestCase.Checks.KeyHandlerMethod.DIRECT_TO_VIM -> typeText(keys.filterNotNull(), editor, project)
Checks.KeyHandlerMethod.VIA_IDE -> typeTextViaIde(keys.filterNotNull(), editor) VimTestCase.Checks.KeyHandlerMethod.VIA_IDE -> typeTextViaIde(keys.filterNotNull(), editor)
} }
return editor return editor
} }
@ -749,7 +749,7 @@ abstract class VimTestCase {
} }
protected fun assertCaretsVisualAttributes() { protected fun assertCaretsVisualAttributes() {
if (!Checks.caretShape) return if (!VimTestCase.Checks.caretShape) return
val editor = fixture.editor val editor = fixture.editor
val attributes = GuiCursorOptionHelper.getAttributes(getGuiCursorMode(editor)) val attributes = GuiCursorOptionHelper.getAttributes(getGuiCursorMode(editor))
val colour = editor.colorsScheme.getColor(EditorColors.CARET_COLOR) val colour = editor.colorsScheme.getColor(EditorColors.CARET_COLOR)
@ -946,8 +946,8 @@ abstract class VimTestCase {
} }
// Disable or enable checks for the particular test // Disable or enable checks for the particular test
protected inline fun setupChecks(setup: Checks.() -> Unit) { protected inline fun setupChecks(setup: VimTestCase.Checks.() -> Unit) {
Checks.setup() VimTestCase.Checks.setup()
} }
protected fun assertExException(expectedErrorMessage: String, action: () -> Unit) { protected fun assertExException(expectedErrorMessage: String, action: () -> Unit) {
@ -1082,24 +1082,11 @@ abstract class VimTestCase {
@JvmStatic @JvmStatic
fun commandToKeys(command: String): List<KeyStroke> { fun commandToKeys(command: String): List<KeyStroke> {
val keys: MutableList<KeyStroke> = ArrayList() val keys: MutableList<KeyStroke> = ArrayList()
if (!command.startsWith(":")) {
keys.addAll(injector.parser.parseKeys(":")) keys.addAll(injector.parser.parseKeys(":"))
var startIndex = if (command.startsWith(":")) 1 else 0
// Special case support for <C-U>
startIndex = if (command.substring(startIndex).startsWith("<C-U>", ignoreCase = true)) {
keys.addAll(injector.parser.parseKeys("<C-U>"))
startIndex + 5
} }
else { keys.addAll(injector.parser.stringToKeys(command)) // Avoids trying to parse 'command ... <args>' as a special char
startIndex
}
// We don't parse the rest of the command, to avoid parsing special keys in e.g. maps. Note that values such as
// `<expr>` or `<args>` would be correctly handled by parseKeys
keys.addAll(injector.parser.stringToKeys(command.substring(startIndex)))
if (keys.last().keyCode != KeyEvent.VK_ENTER) {
keys.addAll(injector.parser.parseKeys("<Enter>")) keys.addAll(injector.parser.parseKeys("<Enter>"))
}
return keys return keys
} }

View File

@ -32,21 +32,18 @@ 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.12.2") testImplementation("org.junit.vintage:junit-vintage-engine:5.12.0")
intellijPlatform { intellijPlatform {
// Snapshots don't use installers // Snapshots don't use installers
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers // https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers
var useInstaller = "EAP-SNAPSHOT" !in ideaVersion val useInstaller = "EAP-SNAPSHOT" !in ideaVersion
if (ideaType == "RD") {
// Using Rider as a target IntelliJ Platform with `useInstaller = true` is currently not supported, please set `useInstaller = false` instead. See: https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1852
useInstaller = false
}
create(ideaType, ideaVersion, useInstaller) create(ideaType, ideaVersion, useInstaller)
testFramework(TestFrameworkType.Platform) testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5) testFramework(TestFrameworkType.JUnit5)
bundledPlugins("com.intellij.java", "org.jetbrains.plugins.yaml") bundledPlugins("com.intellij.java", "org.jetbrains.plugins.yaml")
instrumentationTools()
} }
} }

View File

@ -15,7 +15,6 @@ import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimJavaTestCase import org.jetbrains.plugins.ideavim.VimJavaTestCase
import org.jetbrains.yaml.YAMLFileType import org.jetbrains.yaml.YAMLFileType
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo import org.junit.jupiter.api.TestInfo
@ -438,6 +437,63 @@ class CommentaryExtensionTest : VimJavaTestCase() {
) )
} }
@Test
fun `test text object deletes single line comment from leading whitespace 2`() {
doTest(
"dgc",
"""
<caret>
// Comment 1
final Int value = 42;
""".trimIndent(),
"""
final Int value = 42;
""".trimIndent(),
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
}
@Test
fun `test text object deletes single line comment from leading whitespace 3`() {
doTest(
"dgc",
"""
final Int value1 = 42;
<caret>
// Comment 1
final Int value2 = 42;
""".trimIndent(),
"""
final Int value1 = 42;
final Int value2 = 42;
""".trimIndent(),
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
}
@Test
fun `test text object deletes single line comment from trailing whitespace`() {
doTest(
"dgc",
"""
// Comment 1
<caret>
final Int value = 42;
""".trimIndent(),
"""
final Int value = 42;
""".trimIndent(),
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
}
@Test @Test
fun `test text object deletes single line comments separated by whitespace`() { fun `test text object deletes single line comments separated by whitespace`() {
doTest( doTest(
@ -449,10 +505,26 @@ class CommentaryExtensionTest : VimJavaTestCase() {
final Int value = 42; final Int value = 42;
""".trimIndent(), """.trimIndent(),
""" """
final Int value = 42;
""".trimIndent(),
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
}
@Test
fun `test text object deletes disjointed single line comments from whitespace`() {
doTest(
"dgc",
"""
// Comment 1
<caret>
// Comment 2 // Comment 2
final Int value = 42; final Int value = 42;
""".trimIndent(), """.trimIndent(),
"""
final Int value = 42;
""".trimIndent(),
Mode.NORMAL(), Mode.NORMAL(),
JavaFileType.INSTANCE, JavaFileType.INSTANCE,
) )
@ -583,10 +655,6 @@ class CommentaryExtensionTest : VimJavaTestCase() {
final Int value = 42; final Int value = 42;
""".trimIndent(), """.trimIndent(),
""" """
/* Comment 1
* Comment 2
* Comment 3 */
final Int value = 42; final Int value = 42;
""".trimIndent(), """.trimIndent(),
Mode.NORMAL(), Mode.NORMAL(),
@ -636,7 +704,8 @@ class CommentaryExtensionTest : VimJavaTestCase() {
doTest( doTest(
"dgc", "dgc",
""" """
/**<caret> <caret>
/**
* Cool summary, dude * Cool summary, dude
* @param value the value, innit * @param value the value, innit
* @param name what's your name? * @param name what's your name?
@ -694,79 +763,6 @@ class CommentaryExtensionTest : VimJavaTestCase() {
} }
""".trimIndent(), """.trimIndent(),
""" """
/* Block comment */
/**
* Cool summary, dude
* @param value the value, innit
* @param name what's your name?
*/
public void something(int value, String name) {
}
""".trimIndent(),
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
}
@Test
fun `test text object deletes JavaDoc comment and adjoining comments separated by whitespace 2`() {
doTest(
"dgc",
"""
// This should be deleted too
/* <caret>Block comment */
/**
* Cool summary, dude
* @param value the value, innit
* @param name what's your name?
*/
public void something(int value, String name) {
}
""".trimIndent(),
"""
// This should be deleted too
/**
* Cool summary, dude
* @param value the value, innit
* @param name what's your name?
*/
public void something(int value, String name) {
}
""".trimIndent(),
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
}
@Test
fun `test text object deletes JavaDoc comment and adjoining comments separated by whitespace 3`() {
doTest(
"dgc",
"""
// This should be deleted too
/* Block comment */
/**<caret>
* Cool summary, dude
* @param value the value, innit
* @param name what's your name?
*/
public void something(int value, String name) {
}
""".trimIndent(),
"""
// This should be deleted too
/* Block comment */
public void something(int value, String name) { public void something(int value, String name) {
} }
""".trimIndent(), """.trimIndent(),
@ -875,7 +871,6 @@ class CommentaryExtensionTest : VimJavaTestCase() {
} }
@Test @Test
@Disabled("Doesn't work with the new version of IntelliJ and gradle plugin")
fun `test block comment falls back to line comment when not available`() { fun `test block comment falls back to line comment when not available`() {
doTest( doTest(
"gcw", "gcw",

View File

@ -25,16 +25,17 @@ 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.12.2") testImplementation("org.junit.vintage:junit-vintage-engine:5.12.0")
intellijPlatform { intellijPlatform {
// Snapshots don't use installers // Snapshots don't use installers
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers // https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers
val useInstaller = "EAP-SNAPSHOT" !in ideaVersion val useInstaller = "EAP-SNAPSHOT" !in ideaVersion
create(ideaType, ideaVersion, false) create(ideaType, ideaVersion, useInstaller)
testFramework(TestFrameworkType.Platform) testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5) testFramework(TestFrameworkType.JUnit5)
instrumentationTools()
} }
} }

View File

@ -25,17 +25,18 @@ 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.12.2") testImplementation("org.junit.vintage:junit-vintage-engine:5.12.0")
intellijPlatform { intellijPlatform {
// Snapshots don't use installers // Snapshots don't use installers
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers // https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers
val useInstaller = "EAP-SNAPSHOT" !in ideaVersion val useInstaller = "EAP-SNAPSHOT" !in ideaVersion
create(ideaType, ideaVersion, false) create(ideaType, ideaVersion, useInstaller)
bundledPlugins("com.intellij.java") bundledPlugins("com.intellij.java")
testFramework(TestFrameworkType.Platform) testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5) testFramework(TestFrameworkType.JUnit5)
instrumentationTools()
} }
} }

View File

@ -15,7 +15,7 @@ val javaVersion: String by project
val remoteRobotVersion: String by project val remoteRobotVersion: String by project
dependencies { dependencies {
testFixturesImplementation("org.junit.jupiter:junit-jupiter:5.12.2") testFixturesImplementation("org.junit.jupiter:junit-jupiter:5.12.0")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testFixturesImplementation(testFixtures(project(":"))) // The root project testFixturesImplementation(testFixtures(project(":"))) // The root project

View File

@ -16,6 +16,7 @@ import com.intellij.remoterobot.fixtures.FixtureName
import com.intellij.remoterobot.fixtures.JTreeFixture import com.intellij.remoterobot.fixtures.JTreeFixture
import com.intellij.remoterobot.search.locators.byXpath import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.remoterobot.stepsProcessing.step import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.WaitForConditionTimeoutException
import com.intellij.remoterobot.utils.waitFor import com.intellij.remoterobot.utils.waitFor
import java.time.Duration import java.time.Duration
@ -32,7 +33,12 @@ class IdeaFrame(
val projectViewTree: JTreeFixture val projectViewTree: JTreeFixture
get() { get() {
return find<JTreeFixture>(byXpath("MyProjectViewTree", "//div[@class='MyProjectViewTree']"), Duration.ofSeconds(30)) return try {
find<JTreeFixture>(byXpath("MyProjectViewTree", "//div[@class='MyProjectViewTree']"), Duration.ofSeconds(30))
} catch (_: WaitForConditionTimeoutException) {
// [VERSION UPDATE] 2025.1+ Leave only MyProjectViewTree.
find<JTreeFixture>(byXpath("ProjectViewTree", "//div[@class='ProjectViewTree']"), Duration.ofSeconds(30))
}
} }
val projectName val projectName

View File

@ -13,10 +13,9 @@ import com.intellij.remoterobot.stepsProcessing.StepWorker
object StepsLogger { object StepsLogger {
private var initializaed = false private var initializaed = false
private val initializationLock = Any()
@JvmStatic @JvmStatic
fun init() = synchronized(initializationLock) { fun init() = synchronized(initializaed) {
if (initializaed.not()) { if (initializaed.not()) {
StepWorker.registerProcessor(StepLogger()) StepWorker.registerProcessor(StepLogger())
initializaed = true initializaed = true

View File

@ -9,6 +9,7 @@
import com.intellij.remoterobot.RemoteRobot import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.data.RemoteComponent import com.intellij.remoterobot.data.RemoteComponent
import com.intellij.remoterobot.fixtures.CommonContainerFixture import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.fixtures.ComponentFixture
import com.intellij.remoterobot.fixtures.DefaultXpath import com.intellij.remoterobot.fixtures.DefaultXpath
import com.intellij.remoterobot.fixtures.FixtureName import com.intellij.remoterobot.fixtures.FixtureName
import com.intellij.remoterobot.search.locators.byXpath import com.intellij.remoterobot.search.locators.byXpath
@ -24,10 +25,13 @@ class ManageLicensesFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteCompo
CommonContainerFixture(remoteRobot, remoteComponent) { CommonContainerFixture(remoteRobot, remoteComponent) {
fun enableFreeTier() { fun enableFreeTier() {
radioButton("Activation code").click() find<ComponentFixture>(
/// Note: The license code is obfuscated, so we use the class `W`. But a better solution is required. byXpath(
textFields(byXpath("//div[@class='W']")).first().text = System.getenv("RIDER_LICENSE") "//div[@class='SegmentedButton' and @action='Start trial (null)']",
button("Activate").click() )
).click()
// checkBox("I agree with", contains = true).select()
button("Start Free 30-Day Trial").click()
button("Close").click() button("Close").click()
} }
} }

View File

@ -45,54 +45,9 @@ class RiderUiTest {
val editor = editor("Program.cs") val editor = editor("Program.cs")
testEnterWorksInNormalMode(editor) testEnterWorksInNormalMode(editor)
testReformatCodeUsingMappingWithSpace(editor)
testEnterInInsertMode(editor)
} }
} }
private fun IdeaFrame.testEnterInInsertMode(editor: Editor) {
editor.findText(" for more information").click()
keyboard {
enterText("A")
enter()
}
Thread.sleep(1000)
assertEquals(
"""
|// See https://aka.ms/new-console-template for more information
|
|
|Console.WriteLine("Hello, World!");
""".trimMargin(), editor.text
)
keyboard {
escape()
enterText("dd")
}
}
private fun IdeaFrame.testReformatCodeUsingMappingWithSpace(editor: Editor) {
editor.findText(" for more information").click()
keyboard {
enterText("jj>>")
enterText(":map <Space>x <Action>(ReformatCode)")
enter()
enterText(" x")
}
Thread.sleep(1000)
assertEquals(
"""
|// See https://aka.ms/new-console-template for more information
|
|Console.WriteLine("Hello, World!");
""".trimMargin(), editor.text
)
}
private fun IdeaFrame.testEnterWorksInNormalMode(editor: Editor) { private fun IdeaFrame.testEnterWorksInNormalMode(editor: Editor) {
editor.findText(" for more information").click() editor.findText(" for more information").click()
keyboard { keyboard {

View File

@ -45,13 +45,13 @@ afterEvaluate {
} }
dependencies { dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.12.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.12.0")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4 // 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 // Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2") // testRuntimeOnly("junit:junit:4.13.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.12.2") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.12.0")
// 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")

View File

@ -34,8 +34,6 @@ import com.maddyhome.idea.vim.key.consumers.SelectRegisterConsumer
import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.KeyHandlerState
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.annotations.ApiStatus
import java.util.concurrent.ConcurrentLinkedDeque
import javax.swing.KeyStroke import javax.swing.KeyStroke
/** /**
@ -44,7 +42,7 @@ import javax.swing.KeyStroke
*/ */
// TODO for future refactorings (PRs are welcome) // TODO for future refactorings (PRs are welcome)
// 1. avoid using handleKeyRecursionCount & shouldRecord // 1. avoid using handleKeyRecursionCount & shouldRecord
// 2. maybe we can live without allowKeyMappings: Boolean // 2. maybe we can live without allowKeyMappings: Boolean & mappingCompleted: Boolean
class KeyHandler { class KeyHandler {
private val keyConsumers: List<KeyConsumer> = listOf( private val keyConsumers: List<KeyConsumer> = listOf(
ModalInputConsumer(), ModalInputConsumer(),
@ -61,24 +59,6 @@ class KeyHandler {
) )
private var handleKeyRecursionCount = 0 private var handleKeyRecursionCount = 0
private var commandListener: ConcurrentLinkedDeque<() -> Unit> = ConcurrentLinkedDeque()
/**
* This is an internal API of IdeaVim. External plugins must not use it.
*/
@ApiStatus.Internal
fun addCommandListener(listener: () -> Unit) {
commandListener.add(listener)
}
/**
* This is an internal API of IdeaVim. External plugins must not use it.
*/
@ApiStatus.Internal
fun removeAllCommandListeners() {
commandListener.clear()
}
// KeyHandlerState requires injector.keyGroup to be initialized and that's why we don't create it immediately and have this here // KeyHandlerState requires injector.keyGroup to be initialized and that's why we don't create it immediately and have this here
// TODO figure out a better solution // TODO figure out a better solution
private val defaultKeyHandlerState by lazy { KeyHandlerState() } private val defaultKeyHandlerState by lazy { KeyHandlerState() }
@ -103,15 +83,14 @@ class KeyHandler {
* @param context The data context * @param context The data context
*/ */
fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext, keyState: KeyHandlerState) { fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext, keyState: KeyHandlerState) {
commandListener.forEach { it() }
handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false, keyState) handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false, keyState)
} }
/** /**
* Handling input keys with additional parameters * Handling input keys with additional parameters
* *
* @param allowKeyMappings If we allow key mappings or not * @param allowKeyMappings - If we allow key mappings or not
* @param mappingCompleted No longer used * @param mappingCompleted - if true, we don't check if the mapping is incomplete
*/ */
fun handleKey( fun handleKey(
editor: VimEditor, editor: VimEditor,
@ -121,7 +100,13 @@ class KeyHandler {
mappingCompleted: Boolean, mappingCompleted: Boolean,
keyState: KeyHandlerState, keyState: KeyHandlerState,
) { ) {
val result = processKey(key, editor, allowKeyMappings, KeyProcessResult.SynchronousKeyProcessBuilder(keyState)) val result = processKey(
key,
editor,
allowKeyMappings,
mappingCompleted,
KeyProcessResult.SynchronousKeyProcessBuilder(keyState)
)
if (result is KeyProcessResult.Executable) { if (result is KeyProcessResult.Executable) {
result.execute(editor, context) result.execute(editor, context)
} }
@ -134,30 +119,31 @@ class KeyHandler {
* Alternatively, if we understand the key, we return a 'KeyProcessResult.Executable', which contains a runnable that * Alternatively, if we understand the key, we return a 'KeyProcessResult.Executable', which contains a runnable that
* could execute the key if needed. * could execute the key if needed.
*/ */
private fun processKey( fun processKey(
key: KeyStroke, key: KeyStroke,
editor: VimEditor, editor: VimEditor,
allowKeyMappings: Boolean, allowKeyMappings: Boolean,
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, mappingCompleted: Boolean,
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
): KeyProcessResult { ): KeyProcessResult {
synchronized(lock) { synchronized(lock) {
logger.trace { logger.trace {
""" """
------- Key Handler ------- ------- Key Handler -------
Start key processing. allowKeyMappings: $allowKeyMappings Start key processing. allowKeyMappings: $allowKeyMappings, mappingCompleted: $mappingCompleted
Key: $key Key: $key
""".trimIndent() """.trimIndent()
} }
logger.trace { keyProcessResultBuilder.state.toString() } logger.trace { processBuilder.state.toString() }
logger.trace { "Mode = ${editor.mode}" } logger.trace { "Mode = ${editor.mode}" }
val maxMapDepth = injector.globalOptions().maxmapdepth val maxMapDepth = injector.globalOptions().maxmapdepth
if (handleKeyRecursionCount >= maxMapDepth) { if (handleKeyRecursionCount >= maxMapDepth) {
keyProcessResultBuilder.addExecutionStep { _, lambdaEditor, _ -> processBuilder.addExecutionStep { _, lambdaEditor, _ ->
logger.warn("Key handling, maximum recursion of the key received. maxdepth=$maxMapDepth") logger.warn("Key handling, maximum recursion of the key received. maxdepth=$maxMapDepth")
injector.messages.showStatusBarMessage(lambdaEditor, injector.messages.message("E223")) injector.messages.showStatusBarMessage(lambdaEditor, injector.messages.message("E223"))
injector.messages.indicateError() injector.messages.indicateError()
} }
return keyProcessResultBuilder.build() return processBuilder.build()
} }
injector.messages.clearError() injector.messages.clearError()
@ -167,25 +153,25 @@ class KeyHandler {
handleKeyRecursionCount++ handleKeyRecursionCount++
try { try {
val isProcessed = keyConsumers.any { val isProcessed = keyConsumers.any {
it.consumeKey(key, editor, allowKeyMappings, keyProcessResultBuilder) it.consumeKey(key, editor, allowKeyMappings, mappingCompleted, processBuilder)
} }
if (isProcessed) { if (isProcessed) {
logger.trace { "Key was successfully caught by consumer" } logger.trace { "Key was successfully caught by consumer" }
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
finishedCommandPreparation(lambdaEditor, lambdaContext, key, shouldRecord, lambdaKeyState) finishedCommandPreparation(lambdaEditor, lambdaContext, key, shouldRecord, lambdaKeyState)
} }
} else { } else {
// Key wasn't processed by any of the consumers, so we reset our key state // Key wasn't processed by any of the consumers, so we reset our key state
onUnknownKey(editor, keyProcessResultBuilder.state) onUnknownKey(editor, processBuilder.state)
updateState(keyProcessResultBuilder.state) updateState(processBuilder.state)
return KeyProcessResult.Unknown.apply { return KeyProcessResult.Unknown.apply {
handleKeyRecursionCount-- // because onFinish will now be executed for unknown handleKeyRecursionCount-- // because onFinish will now be executed for unknown
} }
} }
} finally { } finally {
keyProcessResultBuilder.onFinish = { handleKeyRecursionCount-- } processBuilder.onFinish = { handleKeyRecursionCount-- }
} }
return keyProcessResultBuilder.build() return processBuilder.build()
} }
} }
@ -239,7 +225,7 @@ class KeyHandler {
val operatorArguments = OperatorArguments(command.rawCount, editorState.mode) val operatorArguments = OperatorArguments(command.rawCount, editorState.mode)
// If we were in "operator pending" mode, reset back to normal mode. // If we were in "operator pending" mode, reset back to normal mode.
// But opening command line should not reset operator pending mode (e.g. `d/foo`) // But opening command line should not reset operator pending mode (e.g. `d/foo`
if (!command.flags.contains(CommandFlags.FLAG_START_EX)) { if (!command.flags.contains(CommandFlags.FLAG_START_EX)) {
editor.resetOpPending() editor.resetOpPending()
} }

View File

@ -1,95 +0,0 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.DuplicableOperatorAction
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@CommandOrMotion(keys = ["g?"], modes = [Mode.NORMAL])
class ChangeRot13MotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction {
override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.MOTION
override val duplicateWith: Char = '?'
override fun execute(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
if (argument == null) return false
val range = injector.motion.getMotionRange(
editor, caret, context, argument,
operatorArguments
) ?: return false
return applyRot13(editor, caret, range)
}
companion object {
/**
* Apply ROT13 encoding to the text in the given range.
* Only ASCII letters below 0x80 are converted.
*/
fun applyRot13(editor: VimEditor, caret: VimCaret, range: TextRange): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
for (i in ends.indices.reversed()) {
applyRot13ToRange(editor, caret, starts[i], ends[i])
}
caret.moveToOffset(range.startOffset)
return true
}
/**
* Apply ROT13 encoding to the text between start and end offsets.
* Only ASCII letters below 0x80 are converted.
*/
private fun applyRot13ToRange(editor: VimEditor, caret: VimCaret, start: Int, end: Int) {
val (newStart, newEnd) = if (start > end) end to start else start to end
val changedText = buildString {
for (i in newStart until newEnd) {
append(rot13(editor.text()[i]))
}
}
injector.changeGroup.replaceText(editor, caret, newStart, newEnd, changedText)
}
/**
* Apply ROT13 encoding to a single character.
*
* Only ASCII letters below 0x80 are converted, see https://github.com/vim/vim/blob/470317f78b110b4559cecb26039b5f93447c1bf0/src/ops.c#L1591
*/
private fun rot13(ch: Char): Char {
// Only convert ASCII letters below 0x80
return when {
ch.code < 0x80 && ch in 'a'..'z' -> 'a' + (ch - 'a' + 13) % 26
ch.code < 0x80 && ch in 'A'..'Z' -> 'A' + (ch - 'A' + 13) % 26
else -> ch
}
}
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
/**
* @author vlan
*/
@CommandOrMotion(keys = ["g?"], modes = [Mode.VISUAL])
class ChangeRot13VisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override fun executeAction(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
cmd: Command,
range: VimSelection,
operatorArguments: OperatorArguments,
): Boolean {
return ChangeRot13MotionAction.applyRot13(editor, caret, range.toVimTextRange(false))
}
}

View File

@ -70,6 +70,6 @@ class MotionDownNotLineWiseAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
return injector.motion.getVerticalMotionOffset(editor, caret, operatorArguments.count1, bufferLines = true) return injector.motion.getVerticalMotionOffset(editor, caret, operatorArguments.count1)
} }
} }

View File

@ -70,6 +70,6 @@ class MotionUpNotLineWiseAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
return injector.motion.getVerticalMotionOffset(editor, caret, -operatorArguments.count1, bufferLines = true) return injector.motion.getVerticalMotionOffset(editor, caret, -operatorArguments.count1)
} }
} }

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