diff --git a/annotation-processors/src/main/kotlin/com/intellij/vim/processors/CommandBean.kt b/annotation-processors/src/main/kotlin/com/intellij/vim/processors/CommandBean.kt new file mode 100644 index 0000000000..0a479a7c2a --- /dev/null +++ b/annotation-processors/src/main/kotlin/com/intellij/vim/processors/CommandBean.kt @@ -0,0 +1,14 @@ +/* + * 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.intellij.vim.processors + +import kotlinx.serialization.Serializable + +@Serializable +data class CommandBean(val keys: String, val `class`: String, val modes: String) \ No newline at end of file diff --git a/annotation-processors/src/main/kotlin/com/intellij/vim/processors/CommandOrMotionProcessor.kt b/annotation-processors/src/main/kotlin/com/intellij/vim/processors/CommandOrMotionProcessor.kt index 5a603cce5e..579dc51954 100644 --- a/annotation-processors/src/main/kotlin/com/intellij/vim/processors/CommandOrMotionProcessor.kt +++ b/annotation-processors/src/main/kotlin/com/intellij/vim/processors/CommandOrMotionProcessor.kt @@ -18,7 +18,6 @@ import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSFile import com.google.devtools.ksp.symbol.KSVisitorVoid import com.intellij.vim.annotations.CommandOrMotion -import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlin.io.path.Path @@ -55,6 +54,4 @@ class CommandOrMotionProcessor(private val environment: SymbolProcessorEnvironme } } - @Serializable - data class CommandBean(val keys: String, val `class`: String, val modes: String) } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 1b98b4c2a3..06378a5e40 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -137,7 +137,7 @@ dependencies { api(project(":vim-engine")) ksp(project(":annotation-processors")) - compileOnly(project(":annotation-processors")) + implementation(project(":annotation-processors")) testApi("com.squareup.okhttp3:okhttp:4.11.0") diff --git a/src/main/java/com/maddyhome/idea/vim/RegisterActions.kt b/src/main/java/com/maddyhome/idea/vim/RegisterActions.kt index 1aeed9d9ae..458c052e5c 100644 --- a/src/main/java/com/maddyhome/idea/vim/RegisterActions.kt +++ b/src/main/java/com/maddyhome/idea/vim/RegisterActions.kt @@ -9,10 +9,14 @@ package com.maddyhome.idea.vim import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.extensions.ExtensionPointName +import com.maddyhome.idea.vim.action.EngineCommandProvider +import com.maddyhome.idea.vim.action.IntellijCommandProvider +import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.handler.ActionBeanClass import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.key.MappingOwner import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator +import com.maddyhome.idea.vim.newapi.globalIjOptions import java.awt.event.KeyEvent import javax.swing.KeyStroke @@ -26,8 +30,10 @@ public object RegisterActions { @JvmStatic public fun registerActions() { registerVimCommandActions() - registerEmptyShortcuts() - registerEpListener() + if (!injector.globalIjOptions().commandOrMotionAnnotation) { + registerEmptyShortcuts() + registerEpListener() + } } @Deprecated("Moving to annotations approach instead of xml") @@ -41,10 +47,16 @@ public object RegisterActions { } public fun findAction(id: String): EditorActionHandlerBase? { - return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream() - .filter { vimActionBean: ActionBeanClass -> vimActionBean.actionId == id } - .findFirst().map { obj: ActionBeanClass -> obj.instance } - .orElse(null) + if (injector.globalIjOptions().commandOrMotionAnnotation) { + val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id } + ?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null + return commandBean.instance + } else { + return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream() + .filter { vimActionBean: ActionBeanClass -> vimActionBean.actionId == id } + .findFirst().map { obj: ActionBeanClass -> obj.instance } + .orElse(null) + } } public fun findActionOrDie(id: String): EditorActionHandlerBase { @@ -59,18 +71,24 @@ public object RegisterActions { private fun registerVimCommandActions() { val parser = VimPlugin.getKey() - VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map { bean: ActionBeanClass? -> - IjVimActionsInitiator( - bean!! - ) - } - .forEach { actionHolder: IjVimActionsInitiator? -> - parser.registerCommandAction( - actionHolder!! + if (injector.globalIjOptions().commandOrMotionAnnotation) { + EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) } + IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) } + } else { + VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map { bean: ActionBeanClass? -> + IjVimActionsInitiator( + bean!! ) } + .forEach { actionHolder: IjVimActionsInitiator? -> + parser.registerCommandAction( + actionHolder!! + ) + } + } } + // todo do we really need this? private fun registerEmptyShortcuts() { val parser = VimPlugin.getKey() diff --git a/src/main/java/com/maddyhome/idea/vim/group/KeyGroup.java b/src/main/java/com/maddyhome/idea/vim/group/KeyGroup.java index 225ddd96a1..be95eef354 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/KeyGroup.java +++ b/src/main/java/com/maddyhome/idea/vim/group/KeyGroup.java @@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.action.ComplicatedKeysAction; import com.maddyhome.idea.vim.action.VimShortcutKeyAction; +import com.maddyhome.idea.vim.action.change.LazyVimCommand; import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.command.MappingMode; import com.maddyhome.idea.vim.ex.ExOutputModel; @@ -208,6 +209,25 @@ public void registerShortcutWithoutAction(KeyStroke keyStroke, MappingOwner owne registerRequiredShortcut(Collections.singletonList(keyStroke), owner); } + public void registerCommandAction(@NotNull LazyVimCommand command) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + initIdentityChecker(); + for (List keys : command.getKeys()) { + checkCommand(command.getModes(), command, keys); + } + } + + for (List keyStrokes : command.getKeys()) { + registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE); + + for (MappingMode mappingMode : command.getModes()) { + Node node = getKeyRoot(mappingMode); + NodesKt.addLeafs(node, keyStrokes, command); + } + } + } + + @Deprecated public void registerCommandAction(@NotNull VimActionsInitiator actionHolder) { IjVimActionsInitiator holder = (IjVimActionsInitiator)actionHolder; diff --git a/src/test/java/org/jetbrains/plugins/ideavim/RegisterActionsTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/RegisterActionsTest.kt index 883028dd25..047e411cca 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/RegisterActionsTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/RegisterActionsTest.kt @@ -10,11 +10,13 @@ package org.jetbrains.plugins.ideavim import com.maddyhome.idea.vim.RegisterActions.VIM_ACTIONS_EP import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.handler.ActionBeanClass import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.CommandPartNode +import com.maddyhome.idea.vim.newapi.globalIjOptions import org.jetbrains.plugins.ideavim.impl.OptionTest import org.jetbrains.plugins.ideavim.impl.VimOption import org.junit.jupiter.api.Test @@ -84,6 +86,7 @@ class RegisterActionsTest : VimTestCase() { VimOption(TestOptionConstants.whichwrap, doesntAffectTest = true), ) fun `test unregister extension`() { + if (injector.globalIjOptions().commandOrMotionAnnotation) return val before = "I ${c}found it in a legendary land" val after = "I f${c}ound it in a legendary land" var motionRightAction: ActionBeanClass? = null diff --git a/vim-engine/build.gradle.kts b/vim-engine/build.gradle.kts index 73e956e797..0f28c995e9 100644 --- a/vim-engine/build.gradle.kts +++ b/vim-engine/build.gradle.kts @@ -45,7 +45,7 @@ dependencies { compileOnly("org.jetbrains:annotations:24.0.1") ksp(project(":annotation-processors")) - compileOnly(project(":annotation-processors")) + implementation(project(":annotation-processors")) compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0") } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt index a37b6ef1df..67901be534 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt @@ -8,7 +8,7 @@ package com.maddyhome.idea.vim.action -import com.intellij.vim.processors.CommandOrMotionProcessor +import com.intellij.vim.processors.CommandBean import com.maddyhome.idea.vim.action.change.LazyVimCommand import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.MappingMode @@ -23,7 +23,7 @@ public interface CommandProvider { @OptIn(ExperimentalSerializationApi::class) public fun getCommands(): Collection { val classLoader = this.javaClass.classLoader - val commands: List = Json.decodeFromStream(getFile()) + val commands: List = Json.decodeFromStream(getFile()) return commands .groupBy { it.`class` } .map { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt index a61d976f62..7441f086e1 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt @@ -8,6 +8,7 @@ package com.maddyhome.idea.vim.action.change +import com.maddyhome.idea.vim.api.VimActionsInitiator import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.vimscript.model.LazyInstance @@ -18,6 +19,10 @@ public class LazyVimCommand( public val modes: Set, className: String, classLoader: ClassLoader, -) : LazyInstance(className, classLoader) { +) : LazyInstance(className, classLoader), VimActionsInitiator { public val actionId: String = EditorActionHandlerBase.getActionId(className) + + override fun getInstance(): EditorActionHandlerBase { + return instance + } } \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimActionsInitiator.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimActionsInitiator.kt index 3e95491692..7a27edf417 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimActionsInitiator.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimActionsInitiator.kt @@ -9,7 +9,10 @@ package com.maddyhome.idea.vim.api import com.maddyhome.idea.vim.handler.EditorActionHandlerBase +import org.jetbrains.annotations.ApiStatus +@Deprecated(message = "Replace it with LazyVimCommand") +@ApiStatus.ScheduledForRemoval(inVersion = "2.9.0") public interface VimActionsInitiator { public fun getInstance(): EditorActionHandlerBase } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimKeyGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimKeyGroupBase.kt index bf32deee67..f44fbbce4a 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimKeyGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimKeyGroupBase.kt @@ -8,6 +8,7 @@ package com.maddyhome.idea.vim.api +import com.maddyhome.idea.vim.action.change.LazyVimCommand import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.handler.EditorActionHandlerBase @@ -65,6 +66,7 @@ public abstract class VimKeyGroupBase : VimKeyGroup { override fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer = getKeyMapping(mode) + @Deprecated("Initialization EditorActionHandlerBase for this method breaks the point of lazy initialization") protected fun checkCommand( mappingModes: Set, action: EditorActionHandlerBase, @@ -76,6 +78,13 @@ public abstract class VimKeyGroupBase : VimKeyGroup { checkCorrectCombination(action, keys) } + protected fun checkCommand(mappingModes: Set, command: LazyVimCommand, keys: List) { + for (mappingMode in mappingModes) { + checkIdentity(mappingMode, command.actionId, keys) + } + checkCorrectCombination(command, keys) + } + private fun checkIdentity(mappingMode: MappingMode, actName: String, keys: List) { val keySets = identityChecker!!.getOrPut(mappingMode) { HashSet() } if (keys in keySets) { @@ -84,6 +93,7 @@ public abstract class VimKeyGroupBase : VimKeyGroup { keySets.add(keys.toMutableList()) } + @Deprecated("Initialization EditorActionHandlerBase for this method breaks the point of lazy initialization") private fun checkCorrectCombination(action: EditorActionHandlerBase, keys: List) { for (entry in prefixes!!.entries) { val prefix = entry.key @@ -111,6 +121,33 @@ public abstract class VimKeyGroupBase : VimKeyGroup { prefixes!![keys.toMutableList()] = action.id } + private fun checkCorrectCombination(command: LazyVimCommand, keys: List) { + for (entry in prefixes!!.entries) { + val prefix = entry.key + if (prefix.size == keys.size) continue + val shortOne = min(prefix.size, keys.size) + var i = 0 + while (i < shortOne) { + if (prefix[i] != keys[i]) break + i++ + } + + val actionExceptions = listOf( + "VimInsertDeletePreviousWordAction", + "VimInsertAfterCursorAction", + "VimInsertBeforeCursorAction", + "VimFilterVisualLinesAction", + "VimAutoIndentMotionAction", + ) + if (i == shortOne && command.actionId !in actionExceptions && entry.value !in actionExceptions) { + throw RuntimeException( + "Prefix found! $keys in command ${command.actionId} is the same as ${prefix.joinToString(", ") { it.toString() }} in ${entry.value}", + ) + } + } + prefixes!![keys.toMutableList()] = command.actionId + } + override val savedShortcutConflicts: MutableMap get() = myShortcutConflicts