1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-05-14 15:34:06 +02:00

[New Typing Handler]: Switch j command to new typing handler

This commit is contained in:
Alex Plate 2022-09-05 15:22:10 +03:00
parent 2829a13187
commit 43a79dbad4
No known key found for this signature in database
GPG Key ID: 0B97153C8FFEC09F
16 changed files with 277 additions and 43 deletions

View File

@ -242,7 +242,7 @@ tasks {
// Don't forget to update plugin.xml
patchPluginXml {
sinceBuild.set("222")
sinceBuild.set("222.4167.9")
}
}

View File

@ -56,6 +56,11 @@ class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandle
return
}
if (useNewHandler(editor.vim, charTyped)) {
(KeyHandlerKeeper.getInstance().originalHandler as? TypedActionHandlerEx)?.beforeExecute(editor, charTyped, context, plan)
return
}
LOG.trace("Executing before execute")
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
@ -80,12 +85,17 @@ class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandle
return
}
if (useNewHandler(editor.vim, charTyped)) {
KeyHandlerKeeper.getInstance().originalHandler.execute(editor, charTyped, context)
return
}
try {
LOG.trace("Executing typed action")
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
val startTime = if (traceTime) System.currentTimeMillis() else null
handler.handleKey(editor.vim, keyStroke, EditorDataContext.init(editor, context).vim)
handler.handleKeyInitial(editor.vim, keyStroke, EditorDataContext.init(editor, context).vim)
if (startTime != null) {
val duration = System.currentTimeMillis() - startTime
LOG.info("VimTypedAction '$charTyped': $duration ms")

View File

@ -0,0 +1,99 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2022 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim
import com.intellij.codeInsight.editorActions.TypedHandlerDelegate
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.helper.EditorDataContext
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.inNormalMode
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.newapi.vim
import javax.swing.KeyStroke
class VimTypedDelegateHandler : TypedHandlerDelegate() {
lateinit var stateUpdateResult: KeyHandler.StateUpdateResult
override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, file: PsiFile): Result {
if (!VimPlugin.isEnabled()) return Result.CONTINUE
if (editor.isIdeaVimDisabledHere) return Result.CONTINUE
if (!useNewHandler(editor.vim, charTyped)) return Result.CONTINUE
return if (editor.inInsertMode) Result.CONTINUE else Result.STOP
}
override fun newTypingStarted(c: Char, editor: Editor, context: DataContext) {
if (!VimPlugin.isEnabled()) return
if (editor.isIdeaVimDisabledHere) return
if (!useNewHandler(editor.vim, c)) return
val keyStroke = KeyStroke.getKeyStroke(c)
val content = EditorDataContext.init(editor, context)
content.newTypingDelegate = true
stateUpdateResult = KeyHandler.getInstance()
.handleKeyInitial(editor.vim, keyStroke, content.vim, execute = false)
}
override fun beforeCharTyped(c: Char, project: Project, editor: Editor, file: PsiFile, fileType: FileType): Result {
if (!VimPlugin.isEnabled()) return Result.CONTINUE
if (editor.isIdeaVimDisabledHere) return Result.CONTINUE
if (!useNewHandler(editor.vim, c)) return Result.CONTINUE
val keyStroke = KeyStroke.getKeyStroke(c)
val context = EditorDataContext.init(editor)
context.newTypingDelegate = true
if (stateUpdateResult.continueExecution) {
KeyHandler.getInstance().finishedCommandPreparation(editor.vim, context.vim, keyStroke, stateUpdateResult.shouldRecord)
}
return Result.STOP
}
/*
override fun charTyped(c: Char, project: Project, editor: Editor, file: PsiFile): Result {
if (editor.isIdeaVimDisabledHere) return Result.CONTINUE
if (c !in charsByDelegate) return Result.CONTINUE
val keyStroke = KeyStroke.getKeyStroke(c)
KeyHandler.getInstance().handleKey(editor.vim, keyStroke, EditorDataContext.init(editor).vim)
return Result.STOP
}
*/
override fun isImmediatePaintingEnabled(editor: Editor, c: Char, context: DataContext): Boolean {
if (!VimPlugin.isEnabled()) return true
if (editor.isIdeaVimDisabledHere) return true
if (!useNewHandler(editor.vim, c)) return true
return editor.inInsertMode
}
}
internal val charsByDelegate = setOf('j')
internal fun useNewHandler(editor: VimEditor, c: Char): Boolean {
return c in charsByDelegate && editor.inNormalMode
}

View File

@ -82,7 +82,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
// Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler?
try {
val start = if (traceTime) System.currentTimeMillis() else null
KeyHandler.getInstance().handleKey(editor.vim, keyStroke, EditorDataContext.init(editor, e.dataContext).vim)
KeyHandler.getInstance().handleKeyInitial(editor.vim, keyStroke, EditorDataContext.init(editor, e.dataContext).vim)
if (start != null) {
val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut update '$keyStroke': $duration ms")

View File

@ -73,7 +73,7 @@ public class MacroGroup extends VimMacroBase {
final Runnable run = () -> {
// Handle one keystroke then queue up the next key
if (keyStack.hasStroke()) {
KeyHandler.getInstance().handleKey(editor, keyStack.feedStroke(), context);
KeyHandler.getInstance().handleKey(editor, keyStack.feedStroke(), context, true, false, true);
}
if (keyStack.hasStroke()) {
playbackKeys(editor, context, cnt, total);
@ -105,7 +105,7 @@ public class MacroGroup extends VimMacroBase {
return;
}
ProgressManager.getInstance().executeNonCancelableSection(() -> {
KeyHandler.getInstance().handleKey(editor, key, context);
KeyHandler.getInstance().handleKey(editor, key, context, true, false, true);
});
}
keyStack.resetFirst();

View File

@ -18,6 +18,7 @@
package com.maddyhome.idea.vim.helper
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.DataKey
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.Key
@ -27,6 +28,9 @@ class EditorDataContext @Deprecated("Please use `init` method") constructor(
private val editor: Editor,
private val contextDelegate: DataContext? = null,
) : DataContext, UserDataHolder {
internal var newTypingDelegate = false
/**
* Returns the object corresponding to the specified data identifier. Some of the supported data identifiers are
* defined in the [PlatformDataKeys] class.
@ -38,6 +42,7 @@ class EditorDataContext @Deprecated("Please use `init` method") constructor(
PlatformDataKeys.EDITOR.name == dataId -> editor
PlatformDataKeys.PROJECT.name == dataId -> editor.project
PlatformDataKeys.VIRTUAL_FILE.name == dataId -> EditorHelper.getVirtualFile(editor)
NEW_DELEGATE.name == dataId -> newTypingDelegate
else -> contextDelegate?.getData(dataId)
}
@ -71,3 +76,5 @@ class EditorDataContext @Deprecated("Please use `init` method") constructor(
}
}
}
internal val NEW_DELEGATE = DataKey.create<Boolean>("IdeaVim.NEW_DELEGATE")

View File

@ -22,11 +22,16 @@ import com.intellij.openapi.actionSystem.DataContext
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.helper.EditorDataContext
import com.maddyhome.idea.vim.helper.NEW_DELEGATE
class IjExecutionContext(override val context: DataContext) : ExecutionContext {
override fun updateEditor(editor: VimEditor): ExecutionContext {
return IjExecutionContext(EditorDataContext.init((editor as IjVimEditor).editor, context))
}
override fun isNewDelegate(): Boolean {
return context.getData(NEW_DELEGATE) ?: false
}
}
val DataContext.vim

View File

@ -126,7 +126,7 @@
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
<!-- Check for [Version Update] tag in YouTrack as well -->
<!-- Also, please update the value in build.gradle.kts file-->
<idea-version since-build="222"/>
<idea-version since-build="222.4167.9"/>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
<depends>com.intellij.modules.platform</depends>
@ -193,6 +193,8 @@
<statistics.applicationUsagesCollector implementation="com.maddyhome.idea.vim.statistic.ShortcutConflictState"/>
<statistics.counterUsagesCollector implementationClass="com.maddyhome.idea.vim.statistic.ActionTracker"/>
<typedHandler implementation="com.maddyhome.idea.vim.VimTypedDelegateHandler" order="first, before completionAutoPopup"/>
</extensions>
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>

View File

@ -99,7 +99,7 @@ public abstract class JavaVimTestCase extends JavaCodeInsightFixtureTestCase {
exEntryPanel.handleKey(key);
}
else {
keyHandler.handleKey(new IjVimEditor(editor), key, new IjExecutionContext(dataContext));
keyHandler.handleKey(new IjVimEditor(editor), key, new IjExecutionContext(dataContext), true);
}
}
}, null, null);

View File

@ -29,6 +29,36 @@ import org.jetbrains.plugins.ideavim.VimTestCase
* @author Alex Plate
*/
class MotionDownActionTest : VimTestCase() {
fun `test simple motion down`() {
val keys = injector.parser.parseKeys("j")
val before = """
I found it in a ${c}legendary land
all rocks and lavender and tufted grass,
""".trimIndent()
val after = """
I found it in a legendary land
all rocks and la${c}vender and tufted grass,
""".trimIndent()
configureByText(before)
typeText(keys)
assertState(after)
}
fun `test simple motion down multicaret`() {
val keys = injector.parser.parseKeys("j")
val before = """
I found it in a ${c}legendary ${c}land
all rocks and lavender and tufted grass,
""".trimIndent()
val after = """
I found it in a legendary land
all rocks and la${c}vender and${c} tufted grass,
""".trimIndent()
configureByText(before)
typeText(keys)
assertState(after)
}
fun `test motion down in visual block mode`() {
val keys = "<C-V>2kjjj"
val before = """

View File

@ -56,6 +56,11 @@ import java.awt.event.KeyEvent
import java.util.function.Consumer
import javax.swing.KeyStroke
data class KeyRoundInfo(
var commandState: CurrentCommandState,
)
/**
* This handles every keystroke that the user can argType except those that are still valid hotkeys for various Idea
* actions. This is a singleton.
@ -67,6 +72,13 @@ class KeyHandler {
val keyStack = KeyStack()
val modalEntryKeys: MutableList<KeyStroke> = ArrayList()
fun handleKeyInitial(editor: VimEditor, key: KeyStroke, context: ExecutionContext, execute: Boolean = true): StateUpdateResult {
if (editor.vimStateMachine.commandBuilder.isReady) {
editor.vimStateMachine.commandBuilder.resetAll(getKeyRoot(editor.vimStateMachine.mappingState.mappingMode))
}
return handleKey(editor, key, context, execute)
}
/**
* This is the main key handler for the Vim plugin. Every keystroke not handled directly by Idea is sent here for
* processing.
@ -75,10 +87,15 @@ class KeyHandler {
* @param key The keystroke typed by the user
* @param context The data context
*/
fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext) {
handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false)
fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext, execute: Boolean = true): StateUpdateResult {
return handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false, execute)
}
data class StateUpdateResult(
val continueExecution: Boolean,
val shouldRecord: Boolean,
)
/**
* Handling input keys with additional parameters
*
@ -93,7 +110,8 @@ class KeyHandler {
context: ExecutionContext,
allowKeyMappings: Boolean,
mappingCompleted: Boolean,
) {
execute: Boolean = true,
): StateUpdateResult {
LOG.trace {
"""
------- Key Handler -------
@ -112,7 +130,7 @@ class KeyHandler {
injector.messages.showStatusBarMessage(injector.messages.message("E223"))
injector.messages.indicateError()
LOG.warn("Key handling, maximum recursion of the key received. maxdepth=$mapMapDepth")
return
return StateUpdateResult(false, false)
}
injector.messages.clearError()
@ -186,17 +204,20 @@ class KeyHandler {
} finally {
handleKeyRecursionCount--
}
finishedCommandPreparation(editor, context, editorState, commandBuilder, key, shouldRecord)
if (execute) {
finishedCommandPreparation(editor, context, key, shouldRecord)
}
return StateUpdateResult(true, shouldRecord)
}
fun finishedCommandPreparation(
editor: VimEditor,
context: ExecutionContext,
editorState: VimStateMachine,
commandBuilder: CommandBuilder,
key: KeyStroke?,
shouldRecord: Boolean,
) {
val editorState = editor.vimStateMachine
val commandBuilder = editorState.commandBuilder
// Do we have a fully entered command at this point? If so, let's execute it.
if (commandBuilder.isReady) {
LOG.trace("Ready command builder. Execute command.")
@ -657,7 +678,7 @@ class KeyHandler {
editorState: VimStateMachine,
) {
LOG.trace("Command execution")
val command = editorState.commandBuilder.buildCommand()
val command = editorState.commandBuilder.buildCommand(context)
val operatorArguments = OperatorArguments(
editorState.mappingState.mappingMode == MappingMode.OP_PENDING,
command.rawCount, editorState.mode, editorState.subMode
@ -866,7 +887,9 @@ class KeyHandler {
) : Runnable {
override fun run() {
val editorState = getInstance(editor)
editorState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
if (!context.isNewDelegate()) {
editorState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
}
val register = cmd.register
if (register != null) {
injector.registerGroup.selectRegister(register)

View File

@ -27,6 +27,7 @@ interface ExecutionContext {
// TODO: 10.02.2022 Not sure about this method
fun updateEditor(editor: VimEditor): ExecutionContext
fun isNewDelegate(): Boolean
}
interface ExecutionContextManager {

View File

@ -18,6 +18,7 @@
package com.maddyhome.idea.vim.command
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimActionsInitiator
import com.maddyhome.idea.vim.common.CurrentCommandState
import com.maddyhome.idea.vim.diagnostic.debug
@ -156,13 +157,14 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode<VimActi
return commandParts.peek()?.argument != null
}
fun buildCommand(): Command {
fun buildCommand(context: ExecutionContext): Command {
if (commandParts.last.action.id == "VimInsertCompletedDigraphAction" || commandParts.last.action.id == "VimResetModeAction") {
expectedArgumentType = prevExpectedArgumentType
prevExpectedArgumentType = null
return commandParts.removeLast()
}
val original = ArrayDeque(commandParts)
var command: Command = commandParts.removeFirst()
while (commandParts.size > 0) {
val next = commandParts.removeFirst()
@ -174,17 +176,22 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode<VimActi
command = next
} else {
command.argument = Argument(next)
assert(commandParts.size == 0)
// assert(commandParts.size == 0)
}
}
if (context.isNewDelegate()) {
commandParts.addAll(original)
}
expectedArgumentType = null
return command
}
fun resetAll(commandPartNode: CommandPartNode<VimActionsInitiator>) {
fun resetAll(commandPartNode: CommandPartNode<VimActionsInitiator>, setNewCommand: Boolean = true) {
resetInProgressCommandPart(commandPartNode)
commandState = CurrentCommandState.NEW_COMMAND
commandParts.clear()
if (setNewCommand) {
commandState = CurrentCommandState.NEW_COMMAND
commandParts.clear()
}
keyList.clear()
expectedArgumentType = null
prevExpectedArgumentType = null

View File

@ -76,10 +76,14 @@ abstract class EditorActionHandlerBase(private val myRunForEachCaret: Boolean) {
fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val action = { caret: VimCaret -> doExecute(editor, caret, context, operatorArguments) }
if (myRunForEachCaret) {
editor.forEachCaret(action)
if (!context.isNewDelegate()) {
if (myRunForEachCaret) {
editor.forEachCaret(action)
} else {
action(editor.primaryCaret())
}
} else {
action(editor.primaryCaret())
action(editor.currentCaret())
}
}

View File

@ -152,6 +152,7 @@ sealed class MotionActionHandler : EditorActionHandlerBase(false) {
when (this) {
is SingleExecution -> run {
if (context.isNewDelegate() && !(caret == editor.currentCaret())) return@run
if (!preOffsetComputation(editor, context, cmd)) return@run
val offset = getOffset(editor, context, cmd.argument, operatorArguments)
@ -177,34 +178,77 @@ sealed class MotionActionHandler : EditorActionHandlerBase(false) {
}
}
is ForEachCaret -> run {
when {
blockSubmodeActive || editor.carets().size == 1 -> {
val primaryCaret = editor.primaryCaret()
doExecuteForEach(editor, primaryCaret, context, cmd, operatorArguments)
}
else -> {
try {
editor.addCaretListener(CaretMergingWatcher)
editor.forEachCaret { caret ->
doExecuteForEach(
editor,
caret,
context,
cmd,
operatorArguments
)
if (!context.isNewDelegate()) {
when {
blockSubmodeActive || editor.carets().size == 1 -> {
val primaryCaret = editor.primaryCaret()
doExecuteForEach(editor, primaryCaret, context, cmd, operatorArguments)
}
else -> {
try {
editor.addCaretListener(CaretMergingWatcher)
editor.forEachCaret { caret ->
doExecuteForEach(
editor,
caret,
context,
cmd,
operatorArguments
)
}
} finally {
editor.removeCaretListener(CaretMergingWatcher)
}
} finally {
editor.removeCaretListener(CaretMergingWatcher)
}
}
}
else {
doExecuteForEach(
editor,
caret,
context,
cmd,
operatorArguments
)
}
}
}
return true
}
private fun SingleExecution.singleAction(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
) {
if (!preOffsetComputation(editor, context, cmd)) return
val offset = getOffset(editor, context, cmd.argument, operatorArguments)
when (offset) {
is Motion.AbsoluteOffset -> {
var resultOffset = offset.offset
if (resultOffset < 0) {
logger.error("Offset is less than 0. $resultOffset. ${this.javaClass.name}")
}
if (CommandFlags.FLAG_SAVE_JUMP in cmd.flags) {
injector.markGroup.saveJumpLocation(editor)
}
if (!editor.isEndAllowed) {
resultOffset = injector.engineEditorHelper.normalizeOffset(editor, resultOffset, false)
}
preMove(editor, context, cmd)
editor.primaryCaret().moveToOffset(resultOffset)
postMove(editor, context, cmd)
}
is Motion.Error -> injector.messages.indicateError()
is Motion.NoMotion -> Unit
}
}
private fun doExecuteForEach(
editor: VimEditor,
caret: VimCaret,

View File

@ -175,7 +175,9 @@ class ToHandlerMappingInfo(
injector.application.invokeLater {
KeyHandler.getInstance().finishedCommandPreparation(
editor,
context, VimStateMachine.getInstance(editor), VimStateMachine.getInstance(editor).commandBuilder, null, false
context,
null,
false
)
}
}