1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +02:00

Compare commits

..

24 Commits

Author SHA1 Message Date
0f0a73c139 Set plugin version to chylex-49 2025-06-10 02:58:25 +02:00
9eccf626db Make g0/g^/g$ work with soft wraps 2025-06-10 02:58:15 +02:00
f53f980a01 Make gj/gk jump over soft wraps 2025-06-10 01:43:13 +02:00
54e3f7053c Make camelCase motions adjust based on direction of visual selection 2025-06-10 01:43:13 +02:00
c7a8f6b4aa Make search highlights temporary 2025-06-10 01:43:13 +02:00
f680f0b2cd Exit insert mode after refactoring 2025-06-10 01:43:13 +02:00
10131a8d57 Add action to run last macro in all opened files 2025-06-10 01:43:13 +02:00
f5b194002c Stop macro execution after a failed search 2025-06-10 01:43:13 +02:00
41e4adbd6a Revert per-caret registers 2025-06-10 01:43:11 +02:00
191bf9967e Apply scrolloff after executing native IDEA actions 2025-06-10 01:42:50 +02:00
ed84d20d4a Stay on same line after reindenting 2025-06-10 01:42:50 +02:00
3e829b8949 Update search register when using f/t 2025-06-10 01:42:50 +02:00
28b511131b Automatically add unambiguous imports after running a macro 2025-06-10 01:42:50 +02:00
cc2a8d25e2 Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2025-06-10 01:42:50 +02:00
fb9f83821b Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2025-06-10 01:42:50 +02:00
fcb9fd8169 Add support for count for visual and line motion surround 2025-06-10 01:42:50 +02:00
8793889f05 Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2025-06-10 01:42:50 +02:00
f294059e81 Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2025-06-10 01:42:50 +02:00
c7a9fbf374 Respect count with <Action> mappings 2025-06-10 01:42:50 +02:00
9b04739f15 Change matchit plugin to use HTML patterns in unrecognized files 2025-06-10 01:42:50 +02:00
f68c56b968 Reset insert mode when switching active editor 2025-06-10 01:42:50 +02:00
0282d99692 Remove notifications about configuration options 2025-06-10 01:42:49 +02:00
03be3e1be2 Remove update checker 2025-06-10 01:42:49 +02:00
7c233f5e1a Set custom plugin version 2025-06-10 01:42:49 +02:00
52 changed files with 274 additions and 487 deletions

View File

@@ -5,7 +5,7 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-x :tests:property-tests:test -x :tests:long-running-tests:test" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
@@ -19,7 +19,6 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@@ -45,7 +45,6 @@ object Compatibility : IdeaVimBuildType({
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
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}cn.mumukehao.plugin' [latest-IU] -team-city
""".trimIndent()
}
}

View File

@@ -25,7 +25,7 @@ object LongRunning : IdeaVimBuildType({
steps {
gradle {
tasks = "clean :tests:long-running-tests:test"
tasks = "clean :tests:long-running-tests:testLongRunning"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"

View File

@@ -39,7 +39,7 @@ object Nvim : IdeaVimBuildType({
""".trimIndent()
}
gradle {
tasks = "clean test -x :tests:property-tests:test -x :tests:long-running-tests:test -Dnvim"
tasks = "clean test -Dnvim"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"

View File

@@ -25,7 +25,7 @@ object PropertyBased : IdeaVimBuildType({
steps {
gradle {
clearConditions()
tasks = "clean :tests:property-tests:test"
tasks = "clean :tests:property-tests:testPropertyBased"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"

View File

@@ -115,7 +115,7 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
}
script {
name = "Run tests"
scriptContent = "./gradlew test -x :tests:property-tests:test -x :tests:long-running-tests:test"
scriptContent = "./gradlew test"
}
gradle {
name = "Publish release"

View File

@@ -40,7 +40,7 @@ open class TestingBuildType(
steps {
gradle {
clearConditions()
tasks = "clean test -x :tests:property-tests:test -x :tests:long-running-tests:test"
tasks = "clean test"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"

View File

@@ -15,7 +15,7 @@ object GitHub : Project({
name = "Pull Requests checks"
description = "Automatic checking of GitHub Pull Requests"
buildType(GithubBuildType("clean test -x :tests:property-tests:test -x :tests:long-running-tests:test", "Tests"))
buildType(GithubBuildType("clean test", "Tests"))
})
class GithubBuildType(command: String, desc: String) : IdeaVimBuildType({

View File

@@ -614,14 +614,6 @@ Contributors:
[![icon][github]](https://github.com/Malandril)
&nbsp;
Thomas Canava
* [![icon][mail]](mailto:xinhe.wang@jetbrains.com)
[![icon][github]](https://github.com/wxh06)
&nbsp;
Xinhe Wang
* [![icon][mail]](mailto:zuber.kuba@gmail.com)
[![icon][github]](https://github.com/zuberol)
&nbsp;
Jakub Zuber
Previous contributors:

View File

@@ -65,7 +65,7 @@ We've prepared some useful configurations for you:
And here are useful gradle commands:
* `./gradlew runIde` — start the dev version of IntelliJ IDEA with IdeaVim installed.
* `./gradlew test -x :tests:property-tests:test -x :tests:long-running-tests:test` — run tests.
* `./gradlew test` — run tests.
* `./gradlew buildPlugin` — build the plugin. The result will be located in `build/distributions`. This file can be
installed by using `Settings | Plugin | >Gear Icon< | Install Plugin from Disk...`. You can stay with your personal build
for a few days or send it to a friend for testing.

View File

@@ -8,7 +8,7 @@
plugins {
kotlin("jvm")
kotlin("plugin.serialization") version "2.2.0"
kotlin("plugin.serialization") version "2.0.21"
}
val kotlinxSerializationVersion: String by project
@@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.21-2.0.2")
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.21-2.0.1")
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
exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

@@ -35,8 +35,6 @@ import org.intellij.markdown.ast.impl.ListCompositeNode
import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.kohsuke.github.GHUser
import java.net.HttpURLConnection
import java.net.URL
@@ -48,19 +46,19 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2")
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.3.0.202506031305-r")
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.1.202505142326-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:3.2.2")
classpath("io.ktor:ktor-client-cio:3.2.2")
classpath("io.ktor:ktor-client-auth:3.2.2")
classpath("io.ktor:ktor-client-content-negotiation:3.2.2")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.2.2")
classpath("io.ktor:ktor-client-core:3.1.3")
classpath("io.ktor:ktor-client-cio:3.1.3")
classpath("io.ktor:ktor-client-auth:3.1.3")
classpath("io.ktor:ktor-client-content-negotiation:3.1.3")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.1.3")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@@ -69,7 +67,7 @@ buildscript {
plugins {
java
kotlin("jvm") version "2.2.0"
kotlin("jvm") version "2.0.21"
application
id("java-test-fixtures")
@@ -81,7 +79,7 @@ plugins {
id("org.jetbrains.changelog") version "2.2.1"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "4.0.1"
id("com.google.devtools.ksp") version "2.2.0-2.0.2"
id("com.google.devtools.ksp") version "2.0.21-1.0.25"
}
val moduleSources by configurations.registering
@@ -142,11 +140,6 @@ dependencies {
plugin("com.intellij.classic.ui", "251.23774.318")
bundledPlugins("org.jetbrains.plugins.terminal")
// VERSION UPDATE: This module is required since 2025.2
if (ideaVersion == "LATEST-EAP-SNAPSHOT") {
bundledModule("intellij.spellchecker")
}
}
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
@@ -166,19 +159,19 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:6.0.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.3")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.13.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.13.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.13.3")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.13.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.13.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.13.0")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.0")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
}
@@ -228,6 +221,38 @@ tasks {
options.encoding = "UTF-8"
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions
apiVersion = "2.0"
freeCompilerArgs = listOf(
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true
}
}
compileTestKotlin {
enabled = false
kotlinOptions {
jvmTarget = javaVersion
apiVersion = "2.0"
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
freeCompilerArgs += listOf("-Xskip-prerelease-check", "-Xallow-unstable-dependencies")
// allWarningsAsErrors = true
}
}
// Note that this will run the plugin installed in the IDE specified in dependencies. To run in a different IDE, use
// a custom task (see below)
runIde {
@@ -301,23 +326,6 @@ kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
compilerOptions {
jvmTarget.set(JvmTarget.fromTarget(javaVersion))
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions
apiVersion.set(KotlinVersion.KOTLIN_2_0)
freeCompilerArgs = listOf(
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true
}
}
gradle.projectsEvaluated {

View File

@@ -20,7 +20,7 @@ ideaVersion=2025.1
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
instrumentPluginCode=true
version=chylex-51
version=chylex-49
javaVersion=21
remoteRobotVersion=0.11.23
antlrVersion=4.10.1
@@ -28,7 +28,7 @@ antlrVersion=4.10.1
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version
kotlinVersion=2.2.0
kotlinVersion=2.0.21
publishToken=token
publishChannels=eap

View File

@@ -20,25 +20,27 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.2.0")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.21")
implementation("io.ktor:ktor-client-core:3.2.2")
implementation("io.ktor:ktor-client-cio:3.2.2")
implementation("io.ktor:ktor-client-content-negotiation:3.2.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.2.2")
implementation("io.ktor:ktor-client-auth:3.2.2")
implementation("io.ktor:ktor-client-core:3.1.3")
implementation("io.ktor:ktor-client-cio:3.1.3")
implementation("io.ktor:ktor-client-content-negotiation:3.1.3")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.3")
implementation("io.ktor:ktor-client-auth:3.1.3")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.3.0.202506031305-r")
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.1.202505142326-r")
implementation("com.vdurmont:semver4j:3.1.0")
}
val releaseType: String? by project
kotlin {
compilerOptions {
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
tasks {
compileKotlin {
kotlinOptions {
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
}
}
}

View File

@@ -45,8 +45,6 @@ val knownPlugins = setOf(
"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
"cn.mumukehao.plugin",
)
suspend fun main() {

View File

@@ -0,0 +1,52 @@
package com.maddyhome.idea.vim.action
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
class VimRunLastMacroInOpenFiles : DumbAwareAction() {
override fun update(e: AnActionEvent) {
val lastRegister = injector.macro.lastRegister
val isEnabled = lastRegister != 0.toChar()
e.presentation.isEnabled = isEnabled
e.presentation.text = if (isEnabled) "Run Macro '${lastRegister}' in Open Files" else "Run Last Macro in Open Files"
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return
val editors = fileEditorManager.allEditors.filterIsInstance<TextEditor>()
WriteCommandAction.writeCommandAction(project)
.withName(e.presentation.text)
.withGlobalUndo()
.withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION)
.run<RuntimeException> {
val reg = injector.macro.lastRegister
for (editor in editors) {
fileEditorManager.openFile(editor.file, true)
val vimEditor = editor.editor.vim
vimEditor.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(vimEditor)
injector.macro.playbackRegister(vimEditor, IjEditorExecutionContext(e.dataContext), reg, 1)
}
}
}
}

View File

@@ -1,67 +0,0 @@
package com.maddyhome.idea.vim.action.macro
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.command.impl.FinishMarkAction
import com.intellij.openapi.command.impl.StartMarkAction
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
@CommandOrMotion(keys = ["z@"], modes = [Mode.NORMAL])
class PlaybackRegisterInOpenFilesAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val argumentType: Argument.Type = Argument.Type.CHARACTER
private val playbackRegisterAction = PlaybackRegisterAction()
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
val argument = cmd.argument as? Argument.Character ?: return false
val project = editor.ij.project ?: return false
val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return false
val register = argument.character.let { if (it == '@') injector.macro.lastRegister else it }
val commandName = "Execute Macro '$register' in All Open Files"
val action = Runnable {
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
for (textEditor in fileEditorManager.allEditors.filterIsInstance<TextEditor>()) {
fileEditorManager.openFile(textEditor.file, true)
val editor = textEditor.editor
val vimEditor = editor.vim
vimEditor.mode = com.maddyhome.idea.vim.state.mode.Mode.NORMAL()
KeyHandler.Companion.getInstance().reset(vimEditor)
val startMarkAction = StartMarkAction.start(editor, project, commandName)
playbackRegisterAction.execute(vimEditor, context, cmd, operatorArguments)
FinishMarkAction.finish(project, editor, startMarkAction)
}
}
CommandProcessor.getInstance()
.executeCommand(project, action, commandName, null, UndoConfirmationPolicy.REQUEST_CONFIRMATION)
return true
}
}

View File

@@ -455,17 +455,6 @@ internal class NerdTree : VimExtension {
tree.scrollRowToVisible(expectedRow)
},
)
registerCommand("gg", NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
tree.setSelectionRow(0)
tree.scrollRowToVisible(0)
})
registerCommand("G", NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val lastRowIndex = tree.rowCount -1
tree.setSelectionRow(lastRowIndex)
tree.scrollRowToVisible(lastRowIndex)
})
registerCommand(
"NERDTreeMapJumpNextSibling",
"<C-J>",

View File

@@ -134,7 +134,7 @@ internal object IdeaSelectionControl {
is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType)
is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType)
is Mode.INSERT -> VimPlugin.getChange()
.insertBeforeCaret(editor.vim, injector.executionContextManager.getEditorExecutionContext(editor.vim))
.insertBeforeCursor(editor.vim, injector.executionContextManager.getEditorExecutionContext(editor.vim))
is Mode.NORMAL -> Unit
else -> error("Unexpected mode: $mode")

View File

@@ -25,19 +25,15 @@ import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
import com.intellij.openapi.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.util.NlsContexts
import com.intellij.refactoring.actions.BaseRefactoringAction
import com.maddyhome.idea.vim.RegisterActions
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction
import com.maddyhome.idea.vim.api.VimActionExecutor
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
import com.maddyhome.idea.vim.newapi.IjNativeAction
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import org.jetbrains.annotations.NonNls
import java.awt.Component
import javax.swing.JComponent
@@ -74,12 +70,6 @@ internal class IjActionExecutor : VimActionExecutor {
thisLogger().error("Actions cannot be updated when write-action is running or pending")
}
val startVisualModeType = (editor?.mode as? Mode.VISUAL)?.selectionType
val startVisualCaretSelection = if (editor != null && startVisualModeType != null && action.action !is BaseRefactoringAction)
editor.primaryCaret().let { Triple(it.offset, it.selectionStart, it.selectionEnd) }
else
null
val ijAction = (action as IjNativeAction).action
try {
isRunningActionFromVim = true
@@ -89,20 +79,6 @@ internal class IjActionExecutor : VimActionExecutor {
val place = ijAction.choosePlace()
val res = ActionManager.getInstance().tryToExecute(ijAction, null, contextComponent, place, true)
res.waitFor(5_000)
if (startVisualModeType != null && startVisualCaretSelection != null) {
val primaryCaret = editor.primaryCaret()
val endVisualCaretOffset = primaryCaret.offset
if (startVisualCaretSelection.first != endVisualCaretOffset) {
if (!editor.inVisualMode || (editor.mode as Mode.VISUAL).selectionType != startVisualModeType) {
injector.visualMotionGroup.toggleVisual(editor, 1, 0, startVisualModeType)
}
primaryCaret.moveToOffset(startVisualCaretSelection.first)
primaryCaret.setSelection(startVisualCaretSelection.second, startVisualCaretSelection.third)
primaryCaret.moveToOffset(endVisualCaretOffset)
}
}
return res.isDone
} finally {
isRunningActionFromVim = false

View File

@@ -61,7 +61,7 @@ class IJEditorFocusListener : EditorListener {
val switchToInsertMode = Runnable {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
VimPlugin.getChange().insertBeforeCaret(editor, context)
VimPlugin.getChange().insertBeforeCursor(editor, context)
KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, true)
}
if (isCurrentEditorTerminal) {

View File

@@ -169,7 +169,7 @@ internal object IdeaSpecifics {
) {
editor?.let {
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCaret(it.vim, event.dataContext.vim)
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
}
}
@@ -223,7 +223,7 @@ internal object IdeaSpecifics {
// Enable insert mode if there is no selection in template
// Template with selection is handled by [com.maddyhome.idea.vim.group.visual.VisualMotionGroup.controlNonVimSelectionChange]
if (editor.vim.inNormalMode) {
VimPlugin.getChange().insertBeforeCaret(
VimPlugin.getChange().insertBeforeCursor(
editor.vim,
injector.executionContextManager.getEditorExecutionContext(editor.vim),
)

View File

@@ -20,7 +20,6 @@ import com.intellij.openapi.editor.ex.ScrollingModelEx
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.editor.impl.CaretModelImpl
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFileManager
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ExecutionContext
@@ -151,7 +150,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
}
}
}
editor.document.insertString(atPosition, StringUtil.convertLineSeparators(text, "\n"))
editor.document.insertString(atPosition, text)
}
override fun replaceString(start: Int, end: Int, newString: String) {

View File

@@ -135,8 +135,6 @@
<editorNotificationProvider
implementation="com.maddyhome.idea.vim.troubleshooting.AccidentalInstallDetectorEditorNotificationProvider"/>
<dependencySupport coordinate="configuration" kind="vim" displayName="IdeaVim"/>
</extensions>
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>
@@ -144,10 +142,12 @@
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions resource-bundle="messages.IdeaVimBundle">
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction">
<group id="com.chylex.intellij.vim" text="Vim" popup="true">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/>
<action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/>
</group>
<!-- Internal -->
<!--suppress PluginXmlI18n -->
<action id="VimInternalAddBlockInlays" class="com.maddyhome.idea.vim.action.internal.AddBlockInlaysAction" text="Add Test Block Inlays | IdeaVim Internal" internal="true"/>

View File

@@ -78,10 +78,5 @@
"keys": "gJ",
"class": "com.maddyhome.idea.vim.action.change.delete.DeleteJoinVisualLinesAction",
"modes": "X"
},
{
"keys": "z@",
"class": "com.maddyhome.idea.vim.action.macro.PlaybackRegisterInOpenFilesAction",
"modes": "N"
}
]

View File

@@ -1144,12 +1144,6 @@ $c tw${c}o
"O${c}NcE thIs ${c}TEXt wIlL n${c}Ot lOoK s${c}O rIdIcuLoUs\n",
)
assertState("O${c}nce this text will n${c}ot look s${c}o ridiculous\n")
typeTextInFile(
injector.parser.parseKeys("v2wgu"),
"O${c}NcE thIs ${c}TEXt wIlL n${c}Ot lOoK s${c}O rIdIcuLoUs\n",
)
assertState("O${c}nce this text will n${c}ot look s${c}o ridiculous\n")
}
@Test
@@ -1186,12 +1180,6 @@ $c tw${c}o
"O${c}NcE thIs ${c}TEXt wIlL N${c}Ot lOoK S${c}O rIdIcuLoUs\n",
)
assertState("O${c}NCE THIS TEXT WILL N${c}OT LOOK S${c}O RIDICULOUS\n")
typeTextInFile(
injector.parser.parseKeys("v2wgU"),
"O${c}NcE thIs ${c}TEXt wIlL N${c}Ot lOoK S${c}O rIdIcuLoUs\n",
)
assertState("O${c}NCE THIS TEXT WILL N${c}OT LOOK S${c}O RIDICULOUS\n")
}
@Test

View File

@@ -320,61 +320,4 @@ class ChangeCaseToggleCharacterActionTest : VimTestCase() {
enterCommand("set nooldundo")
}
}
@Test
fun `test toggle case line caret position`() {
configureByText(" Hello ${c}World")
typeText("g~~")
assertState(" ${c}hELLO wORLD")
typeText("u")
assertState(" Hello ${c}World")
typeText("^g~~")
assertState(" ${c}hELLO wORLD")
typeText("u")
assertState(" ${c}Hello World")
typeText("hg~~")
assertState(" $c hELLO wORLD")
typeText("u")
assertState(" $c Hello World")
}
@Test
fun `test uppercase line caret position`() {
configureByText(" Hello ${c}World")
typeText("gUU")
assertState(" ${c}HELLO WORLD")
typeText("u")
assertState(" Hello ${c}World")
typeText("^gUU")
assertState(" ${c}HELLO WORLD")
typeText("u")
assertState(" ${c}Hello World")
typeText("hgUU")
assertState(" $c HELLO WORLD")
typeText("u")
assertState(" $c Hello World")
}
@Test
fun `test lowercase line caret position`() {
configureByText(" Hello ${c}World")
typeText("guu")
assertState(" ${c}hello world")
typeText("u")
assertState(" Hello ${c}World")
typeText("^guu")
assertState(" ${c}hello world")
typeText("u")
assertState(" ${c}Hello World")
typeText("hguu")
assertState(" $c hello world")
typeText("u")
assertState(" $c Hello World")
}
}

View File

@@ -17,7 +17,7 @@ import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class VisualInsertActionTest : VimTestCase() {
class VisualBlockInsertActionTest : VimTestCase() {
// VIM-1379 |CTRL-V| |j| |v_b_I|
@TestWithoutNeovim(SkipNeovimReason.VISUAL_BLOCK_MODE)
@Test
@@ -101,70 +101,28 @@ class VisualInsertActionTest : VimTestCase() {
)
}
@TestWithoutNeovim(SkipNeovimReason.VISUAL_BLOCK_MODE)
@Test
fun `test insert in non-block visual within single line`() {
val before = """
| A ${c}Discovery
fun `test insert in non block mode`() {
doTest(
listOf("vwIHello<esc>"),
"""
${c}A Discovery
| I ${c}found it in a legendary land
| all rocks and lavender and tufted grass,
| where it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
val after = """
|Hell${c}o A Discovery
${c}I found it in a legendary land
all rocks and ${c}lavender and tufted grass,
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent(),
"""
Hell${c}oA Discovery
|Hell${c}o I found it in a legendary land
| all rocks and lavender and tufted grass,
| where it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
doTest(listOf($$"v$IHello<esc>"), before, after)
doTest(listOf("VIHello<esc>"), before, after)
}
@Test
fun `test insert in non-block visual spanning multiple lines down`() {
val before = """
| A ${c}Discovery
| I ${c}found it in a legendary land
| all rocks and lavender and tufted grass,
| where it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
val after = """
|Hell${c}o A Discovery
|Hell${c}o I found it in a legendary land
| all rocks and lavender and tufted grass,
| where it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
doTest(listOf("vjIHello<esc>"), before, after)
doTest(listOf("VjIHello<esc>"), before, after)
}
@Test
fun `test insert in non-block visual spanning multiple lines up`() {
val before = """
| A Discovery
| I found it in a legendary land
| all rocks and lavender and tufted grass${c},
| where it was settled on some sodden sand
| hard ${c}by the torrent of a mountain pass.
""".trimMargin()
val after = """
| A Discovery
| I found it in a legendary landHell${c}o
| all rocks and lavender and tufted grass,
| whereHell${c}o it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
doTest(listOf("vkIHello<esc>"), before, after)
doTest(listOf("VkIHello<esc>"), before, after)
Hell${c}oI found it in a legendary land
Hell${c}oall rocks and lavender and tufted grass,
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent(),
)
}
@TestWithoutNeovim(SkipNeovimReason.VISUAL_BLOCK_MODE)

View File

@@ -241,7 +241,12 @@ class RegistersCommandTest : VimTestCase() {
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.saveRegister(vimEditor, context, '+', Register('+', SelectionType.LINE_WISE, "Lorem ipsum dolor", mutableListOf()))
injector.registerGroup.saveRegister(
vimEditor,
context,
'+',
Register('+', injector.clipboardManager.dumbCopiedText("Lorem ipsum dolor"), SelectionType.LINE_WISE)
)
val clipboardContent = injector.clipboardManager.dumbCopiedText("clipboard content")
injector.clipboardManager.setClipboardContent(vimEditor, context, clipboardContent)
typeText("V<Esc>")
@@ -448,7 +453,12 @@ class RegistersCommandTest : VimTestCase() {
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val clipboardContent = injector.clipboardManager.dumbCopiedText("clipboard content")
injector.registerGroup.saveRegister(vimEditor, context, '+', Register('+', SelectionType.LINE_WISE, "Lorem ipsum dolor", mutableListOf()))
injector.registerGroup.saveRegister(
vimEditor,
context,
'+',
Register('+', injector.clipboardManager.dumbCopiedText("Lorem ipsum dolor"), SelectionType.LINE_WISE)
)
injector.clipboardManager.setClipboardContent(vimEditor, context, clipboardContent)
typeText("V<Esc>")

View File

@@ -32,7 +32,7 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.0")
intellijPlatform {
// Snapshots don't use installers

View File

@@ -17,7 +17,7 @@ import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimJavaTestCase
import org.junit.jupiter.api.Test
class VisualInsertActionJavaTest : VimJavaTestCase() {
class VisualBlockInsertActionJavaTest : VimJavaTestCase() {
// VIM-1110 |CTRL-V| |v_b_i| |zc|
@TestWithoutNeovim(SkipNeovimReason.FOLDING)
@Test

View File

@@ -25,7 +25,7 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.0")
intellijPlatform {
// Snapshots don't use installers
@@ -47,8 +47,17 @@ tasks {
// I didn't find a better way to exclude except disabling and defining a new task with a different name
// Note that useJUnitTestPlatform() is required to prevent red code
test {
enabled = false
useJUnitPlatform()
}
// The `test` task is automatically set up with IntelliJ goodness. A custom test task needs to be configured for it
val testLongRunning by intellijPlatformTesting.testIde.registering {
task {
group = "verification"
useJUnitPlatform()
}
}
}
java {

View File

@@ -25,7 +25,7 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.0")
intellijPlatform {
// Snapshots don't use installers
@@ -48,6 +48,15 @@ tasks {
// I didn't find a better way to exclude except disabling and defining a new task with a different name
test {
useJUnitPlatform()
enabled = false
}
// The `test` task is automatically set up with IntelliJ goodness. A custom test task needs to be configured for it
val testPropertyBased by intellijPlatformTesting.testIde.registering {
task {
group = "verification"
useJUnitPlatform()
}
}
}

View File

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

View File

@@ -11,7 +11,6 @@ import com.intellij.remoterobot.data.RemoteComponent
import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.fixtures.DefaultXpath
import com.intellij.remoterobot.fixtures.FixtureName
import com.intellij.remoterobot.fixtures.JButtonFixture
import com.intellij.remoterobot.search.locators.byXpath
import java.time.Duration
@@ -29,6 +28,6 @@ class ManageLicensesFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteCompo
/// Note: The license code is obfuscated, so we use the class `W`. But a better solution is required.
textFields(byXpath("//div[@class='W']")).first().text = System.getenv("RIDER_LICENSE")
button("Activate").click()
button(JButtonFixture.byText("Close"), timeout = Duration.ofSeconds(20)).click()
button("Close").click()
}
}

View File

@@ -10,8 +10,8 @@ plugins {
java
kotlin("jvm")
// id("org.jlleitschuh.gradle.ktlint")
id("com.google.devtools.ksp") version "2.2.0-2.0.2"
kotlin("plugin.serialization") version "2.2.0"
id("com.google.devtools.ksp") version "2.0.21-1.0.25"
kotlin("plugin.serialization") version "2.0.21"
`maven-publish`
antlr
}
@@ -45,13 +45,13 @@ afterEvaluate {
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.3")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.0")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.13.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.13.0")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
@@ -68,7 +68,7 @@ dependencies {
compileOnly(kotlin("reflect"))
testImplementation("org.mockito.kotlin:mockito-kotlin:6.0.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
}
tasks {
@@ -88,13 +88,13 @@ tasks {
named("compileTestKotlin") {
dependsOn("generateTestGrammarSource")
}
}
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
}
compileKotlin {
kotlinOptions {
apiVersion = "2.0"
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
}
}
}
// --- Linting

View File

@@ -18,13 +18,10 @@ 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.diagnostic.vimLogger
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@CommandOrMotion(keys = ["gu"], modes = [Mode.NORMAL])
class ChangeCaseLowerMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction {
private val logger = vimLogger<ChangeCaseLowerMotionAction>()
override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.MOTION
@@ -38,18 +35,15 @@ class ChangeCaseLowerMotionAction : ChangeEditorActionHandler.ForEachCaret(), Du
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
if (argument == null || argument !is Argument.Motion) {
logger.error("Argument is null or not Argument.Motion. argument=$argument")
return false
}
return injector.changeGroup.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.LOWER,
argument,
operatorArguments,
)
return argument != null &&
injector.changeGroup
.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.LOWER,
argument,
operatorArguments,
)
}
}

View File

@@ -18,13 +18,10 @@ 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.diagnostic.vimLogger
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@CommandOrMotion(keys = ["g~"], modes = [Mode.NORMAL])
class ChangeCaseToggleMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction {
private val logger = vimLogger<ChangeCaseToggleMotionAction>()
override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.MOTION
@@ -38,18 +35,15 @@ class ChangeCaseToggleMotionAction : ChangeEditorActionHandler.ForEachCaret(), D
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
if (argument == null || argument !is Argument.Motion) {
logger.error("Argument is null or not Argument.Motion. argument=$argument")
return false
}
return injector.changeGroup.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.TOGGLE,
argument,
operatorArguments,
)
return argument != null &&
injector.changeGroup
.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.TOGGLE,
argument,
operatorArguments,
)
}
}

View File

@@ -18,13 +18,10 @@ 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.diagnostic.vimLogger
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@CommandOrMotion(keys = ["gU"], modes = [Mode.NORMAL])
class ChangeCaseUpperMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction {
private val logger = vimLogger<ChangeCaseUpperMotionAction>()
override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.MOTION
@@ -38,18 +35,15 @@ class ChangeCaseUpperMotionAction : ChangeEditorActionHandler.ForEachCaret(), Du
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
if (argument == null || argument !is Argument.Motion) {
logger.error("Argument is null or not Argument.Motion. argument=$argument")
return false
}
return injector.changeGroup.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.UPPER,
argument,
operatorArguments,
)
return argument != null &&
injector.changeGroup
.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.UPPER,
argument,
operatorArguments,
)
}
}

View File

@@ -27,7 +27,7 @@ class InsertAfterCursorAction : ChangeEditorActionHandler.SingleExecution() {
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
injector.changeGroup.insertAfterCaret(editor, context)
injector.changeGroup.insertAfterCursor(editor, context)
return true
}
}

View File

@@ -47,5 +47,5 @@ private fun insertAtPreviousInsert(editor: VimEditor, context: ExecutionContext)
if (motion is Motion.AbsoluteOffset) {
caret.moveToOffset(motion.offset)
}
injector.changeGroup.insertBeforeCaret(editor, context)
injector.changeGroup.insertBeforeCursor(editor, context)
}

View File

@@ -29,7 +29,7 @@ class InsertBeforeCursorAction : ChangeEditorActionHandler.SingleExecution() {
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
injector.changeGroup.insertBeforeCaret(editor, context)
injector.changeGroup.insertBeforeCursor(editor, context)
return true
}
}

View File

@@ -13,7 +13,6 @@ 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.api.normalizeLine
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
@@ -21,17 +20,10 @@ import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.state.mode.SelectionType
/**
* Handles the 'I' command in Visual mode.
*
* For (linewise) Visual mode, the caret positioning follows these rules (based on observation in Vim):
* - If text on multiple lines is selected AND the caret is on the first line (e.g., when selecting from bottom to top),
* the caret position remains unchanged
* - In all other cases, the caret is moved to the start of the first selected line
*
* For blockwise Visual mode, it initiates insert at the start of block on each line in the selection
* @author vlan
*/
@CommandOrMotion(keys = ["I"], modes = [Mode.VISUAL])
class VisualInsertAction : VisualOperatorActionHandler.SingleExecution() {
class VisualBlockInsertAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT
override fun executeForAllCarets(
@@ -46,17 +38,7 @@ class VisualInsertAction : VisualOperatorActionHandler.SingleExecution() {
return if (vimSelection.type == SelectionType.BLOCK_WISE) {
injector.changeGroup.initBlockInsert(editor, context, vimSelection.toVimTextRange(false), false)
} else {
// For visual selections spanning multiple lines, keep caret position if it's on the first line
// Otherwise move the caret to the start of the first selected line
for ((caret, selection) in caretsAndSelections) {
val range = selection.toVimTextRange()
val posStart = editor.offsetToBufferPosition(range.startOffset)
val nextLineStart = editor.getLineStartOffset(editor.normalizeLine(posStart.line + 1))
if (caret.offset >= nextLineStart || nextLineStart >= range.endOffset) {
caret.moveToOffset(injector.motion.moveCaretToLineStart(editor, posStart.line))
}
}
injector.changeGroup.insertBeforeCaret(editor, context)
injector.changeGroup.insertBeforeFirstNonBlank(editor, context)
true
}
}

View File

@@ -1,32 +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.file
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.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["ZQ"], modes = [Mode.NORMAL])
class FileCloseAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
injector.file.closeFile(editor, context)
return true
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2025 The IdeaVim authors
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
@@ -16,9 +16,9 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["ZZ"], modes = [Mode.NORMAL])
@CommandOrMotion(keys = ["ZQ", "ZZ"], modes = [Mode.NORMAL])
class FileSaveCloseAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val type: Command.Type = Command.Type.OTHER_WRITABLE
override fun execute(
editor: VimEditor,

View File

@@ -48,7 +48,7 @@ class SelectMotionArrowLeftAction : MotionActionHandler.ForEachCaret() {
editor.exitSelectModeNative(false)
if (editor.isTemplateActive()) {
logger.debug("Template is active. Activate insert mode")
injector.changeGroup.insertBeforeCaret(editor, context)
injector.changeGroup.insertBeforeCursor(editor, context)
if (caret.offset in startSelection..endSelection) {
return startSelection.toMotion()
}

View File

@@ -48,7 +48,7 @@ class SelectMotionArrowRightAction : MotionActionHandler.ForEachCaret() {
editor.exitSelectModeNative(false)
if (editor.isTemplateActive()) {
logger.debug("Template is active. Activate insert mode")
injector.changeGroup.insertBeforeCaret(editor, context)
injector.changeGroup.insertBeforeCursor(editor, context)
if (caret.offset in startSelection..endSelection) {
return endSelection.toMotion()
}

View File

@@ -23,13 +23,13 @@ import javax.swing.KeyStroke
interface VimChangeGroup {
fun setInsertRepeat(lines: Int, column: Int, append: Boolean)
fun insertBeforeCaret(editor: VimEditor, context: ExecutionContext)
fun insertBeforeCursor(editor: VimEditor, context: ExecutionContext)
fun insertBeforeFirstNonBlank(editor: VimEditor, context: ExecutionContext)
fun insertLineStart(editor: VimEditor, context: ExecutionContext)
fun insertAfterCaret(editor: VimEditor, context: ExecutionContext)
fun insertAfterCursor(editor: VimEditor, context: ExecutionContext)
fun insertAfterLineEnd(editor: VimEditor, context: ExecutionContext)
@@ -198,7 +198,7 @@ interface VimChangeGroup {
caret: VimCaret,
context: ExecutionContext?,
type: ChangeCaseType,
argument: Argument.Motion,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean

View File

@@ -401,7 +401,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
* @param editor The editor to insert into
* @param context The data context
*/
override fun insertBeforeCaret(editor: VimEditor, context: ExecutionContext) {
override fun insertBeforeCursor(editor: VimEditor, context: ExecutionContext) {
initInsert(editor, context, Mode.INSERT)
}
@@ -417,7 +417,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
* @param editor The editor to insert into
* @param context The data context
*/
override fun insertAfterCaret(editor: VimEditor, context: ExecutionContext) {
override fun insertAfterCursor(editor: VimEditor, context: ExecutionContext) {
for (caret in editor.nativeCarets()) {
caret.moveToMotion(injector.motion.getHorizontalMotion(editor, caret, 1, true))
}
@@ -781,7 +781,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
lambdaEditor.exitSelectModeNative(false)
KeyHandler.getInstance().reset(lambdaEditor)
if (isPrintableChar(key.keyChar) || activeTemplateWithLeftRightMotion(lambdaEditor, key)) {
injector.changeGroup.insertBeforeCaret(lambdaEditor, lambdaContext)
injector.changeGroup.insertBeforeCursor(lambdaEditor, lambdaContext)
}
}
}
@@ -1302,7 +1302,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
if (type === SelectionType.LINE_WISE) {
// Please don't use `getDocument().getText().isEmpty()` because it converts CharSequence into String
if (editor.fileSize() == 0L) {
insertBeforeCaret(editor, context)
insertBeforeCursor(editor, context)
} else if (after && !editor.endsWithNewLine()) {
insertNewLineBelow(editor, updatedCaret, lp.column)
} else {
@@ -1315,7 +1315,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
editor.vimChangeActionSwitchMode = Mode.INSERT
}
} else {
insertBeforeCaret(editor, context)
insertBeforeCursor(editor, context)
}
return true
}
@@ -1867,22 +1867,14 @@ abstract class VimChangeGroupBase : VimChangeGroup {
caret: VimCaret,
context: ExecutionContext?,
type: VimChangeGroup.ChangeCaseType,
argument: Argument.Motion,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
var range = injector.motion.getMotionRange(
editor, caret, context!!, argument, operatorArguments
val range = injector.motion.getMotionRange(
editor, caret, context!!, argument,
operatorArguments
)
if (range == null) return false
// If the motion is linewise, we need to adjust range.startOffset to match the observed Vim behavior
if (argument.isLinewiseMotion()) {
val pos = editor.offsetToBufferPosition(range.startOffset)
// The leftmost non-whitespace character OR the current caret position, whichever is closer to the left
val start = editor.getLeadingCharacterOffset(pos.line).coerceAtMost(caret.offset)
range = TextRange(start, range.endOffset)
}
return changeCaseRange(editor, caret, range, type)
return range != null && changeCaseRange(editor, caret, range, type)
}
/**
@@ -2048,7 +2040,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
caret.moveToInlayAwareOffset(editor.bufferPositionToOffset(BufferPosition(line, column)))
setInsertRepeat(lines, column, append)
}
insertBeforeCaret(editor, context)
insertBeforeCursor(editor, context)
return true
}

View File

@@ -400,11 +400,12 @@ abstract class VimMotionGroupBase : VimMotionGroup {
// If we are a linewise motion we need to normalize the start and stop then move the start to the beginning
// of the line and move the end to the end of the line.
if (argument.isLinewiseMotion()) {
start = editor.getLineStartForOffset(start)
end = if (caret.getBufferPosition().line != editor.lineCount() - 1) {
min((editor.getLineEndForOffset(end) + 1).toLong(), editor.fileSize()).toInt()
if (caret.getBufferPosition().line != editor.lineCount() - 1) {
start = editor.getLineStartForOffset(start)
end = min((editor.getLineEndForOffset(end) + 1).toLong(), editor.fileSize()).toInt()
} else {
editor.getLineEndForOffset(end)
start = editor.getLineStartForOffset(start)
end = editor.getLineEndForOffset(end)
}
}

View File

@@ -53,11 +53,15 @@ sealed class Argument {
fun getMotionType() = if (isLinewiseMotion()) SelectionType.LINE_WISE else SelectionType.CHARACTER_WISE
fun isLinewiseMotion(): Boolean = when (motion) {
is TextObjectActionHandler -> motion.visualType == TextObjectVisualType.LINE_WISE
is MotionActionHandler -> motion.motionType == MotionType.LINE_WISE
is ExternalActionHandler -> motion.isLinewiseMotion
else -> error("Command is not a motion: $motion")
fun isLinewiseMotion(): Boolean {
return motion.let {
when (it) {
is TextObjectActionHandler -> it.visualType == TextObjectVisualType.LINE_WISE
is MotionActionHandler -> it.motionType == MotionType.LINE_WISE
is ExternalActionHandler -> it.isLinewiseMotion
else -> error("Command is not a motion: $motion")
}
}
}
fun withArgument(argument: Argument) = Motion(motion, argument)

View File

@@ -1106,7 +1106,7 @@
},
{
"keys": "I",
"class": "com.maddyhome.idea.vim.action.change.insert.VisualInsertAction",
"class": "com.maddyhome.idea.vim.action.change.insert.VisualBlockInsertAction",
"modes": "X"
},
{
@@ -1211,7 +1211,7 @@
},
{
"keys": "ZQ",
"class": "com.maddyhome.idea.vim.action.file.FileCloseAction",
"class": "com.maddyhome.idea.vim.action.file.FileSaveCloseAction",
"modes": "N"
},
{