mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-01-11 10:42:47 +01:00
Merge pull request #824 from JetBrains/fleet
Asynchronous key processing for Fleet
This commit is contained in:
commit
00808af569
gradle.properties
src
main/java/com/maddyhome/idea/vim
VimPlugin.javaVimTypedActionHandler.kt
action
command
extension
group
handler
helper
listener
newapi
ui
vimscript/model/options/helpers
test/java/org/jetbrains/plugins/ideavim
action
command
extension
testFixtures/kotlin/org/jetbrains/plugins/ideavim
tests/property-tests/src/test/kotlin/org/jetbrains/plugins/ideavim/propertybased
vim-engine
build.gradle.kts
src/main/kotlin/com/maddyhome/idea/vim
KeyHandler.kt
action
api
VimChangeGroup.ktVimChangeGroupBase.ktVimEditor.ktVimEditorGroup.ktVimMessages.ktVimProcessGroup.ktVimVisualMotionGroupBase.kt
stubs
command
common
group/visual
helper
impl/state
key
register
state
vimscript/model/commands
@ -28,7 +28,7 @@ publishChannels=eap
|
||||
|
||||
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
|
||||
# we exclude this version from the dependency and use our own version of kotlin that is specified above
|
||||
kotlinxSerializationVersion=1.5.1
|
||||
kotlinxSerializationVersion=1.6.2
|
||||
|
||||
slackUrl=
|
||||
youtrackToken=
|
||||
|
@ -211,22 +211,22 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
public static void setEnabled(final boolean enabled) {
|
||||
if (isEnabled() == enabled) return;
|
||||
|
||||
if (!enabled) {
|
||||
getInstance().turnOffPlugin(true);
|
||||
}
|
||||
|
||||
getInstance().enabled = enabled;
|
||||
|
||||
if (enabled) {
|
||||
getInstance().turnOnPlugin();
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
|
||||
} else {
|
||||
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
getInstance().turnOffPlugin(true);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
getInstance().turnOnPlugin();
|
||||
}
|
||||
|
||||
StatusBarIconFactory.Util.INSTANCE.updateIcon();
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
|
||||
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, injector.executionContextManager.onEditor(editor.vim, context.vim))
|
||||
handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim), handler.keyHandlerState)
|
||||
if (startTime != null) {
|
||||
val duration = System.currentTimeMillis() - startTime
|
||||
LOG.info("VimTypedAction '$charTyped': $duration ms")
|
||||
|
@ -80,10 +80,12 @@ public class VimShortcutKeyAction : AnAction(), DumbAware, ActionRemoteBehaviorS
|
||||
// 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(
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(
|
||||
editor.vim,
|
||||
keyStroke,
|
||||
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
|
||||
keyHandler.keyHandlerState,
|
||||
)
|
||||
if (start != null) {
|
||||
val duration = System.currentTimeMillis() - start
|
||||
|
@ -21,7 +21,7 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
public class CommandState(private val machine: VimStateMachine) {
|
||||
|
||||
public val isOperatorPending: Boolean
|
||||
get() = machine.isOperatorPending
|
||||
get() = machine.isOperatorPending(machine.mode)
|
||||
|
||||
public val mode: Mode
|
||||
get() {
|
||||
|
@ -143,7 +143,8 @@ public object VimExtensionFacade {
|
||||
@JvmStatic
|
||||
public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
|
||||
val context = injector.executionContextManager.onEditor(editor.vim)
|
||||
keys.forEach { KeyHandler.getInstance().handleKey(editor.vim, it, context, false, false) }
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
|
||||
}
|
||||
|
||||
/** Returns a single key stroke from the user input similar to 'getchar()'. */
|
||||
@ -159,7 +160,7 @@ public object VimExtensionFacade {
|
||||
LOG.trace("Unit test mode is active")
|
||||
val mappingStack = KeyHandler.getInstance().keyStack
|
||||
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
|
||||
if (editor.vim.vimStateMachine.isRecording) {
|
||||
if (injector.registerGroup.isRecording) {
|
||||
KeyHandler.getInstance().modalEntryKeys += it
|
||||
}
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
|
||||
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
||||
//noinspection DuplicatedCode
|
||||
if (!vimStateMachine.isOperatorPending()) {
|
||||
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
|
||||
editor.nativeCarets().forEach((VimCaret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
|
||||
if (range != null) {
|
||||
|
@ -99,7 +99,7 @@ internal class Matchit : VimExtension {
|
||||
|
||||
// Normally we want to jump to the start of the matching pair. But when moving forward in operator
|
||||
// pending mode, we want to include the entire match. isInOpPending makes that distinction.
|
||||
val isInOpPending = commandState.isOperatorPending
|
||||
val isInOpPending = commandState.isOperatorPending(editor.mode)
|
||||
|
||||
if (isInOpPending) {
|
||||
val matchitAction = MatchitAction()
|
||||
|
@ -31,7 +31,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
|
||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
|
||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
@ -163,13 +162,14 @@ internal class ReplaceWithRegister : VimExtension {
|
||||
caretAfterInsertedText = false,
|
||||
putToLine = -1,
|
||||
)
|
||||
val vimEditor = editor.vim
|
||||
ClipboardOptionHelper.IdeaputDisabler().use {
|
||||
VimPlugin.getPut().putText(
|
||||
IjVimEditor(editor),
|
||||
vimEditor,
|
||||
injector.executionContextManager.onEditor(editor.vim),
|
||||
putData,
|
||||
operatorArguments = OperatorArguments(
|
||||
editor.vimStateMachine?.isOperatorPending ?: false,
|
||||
editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,
|
||||
0,
|
||||
editor.vim.mode,
|
||||
),
|
||||
|
@ -138,7 +138,7 @@ public class VimTextObjEntireExtension implements VimExtension {
|
||||
|
||||
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
|
||||
//noinspection DuplicatedCode
|
||||
if (!vimStateMachine.isOperatorPending()) {
|
||||
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
|
||||
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
|
||||
if (range != null) {
|
||||
|
@ -267,7 +267,7 @@ public class VimIndentObject implements VimExtension {
|
||||
|
||||
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
|
||||
|
||||
if (!vimStateMachine.isOperatorPending()) {
|
||||
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
|
||||
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
|
||||
if (range != null) {
|
||||
|
@ -251,7 +251,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
||||
switchToInsertMode.run();
|
||||
}
|
||||
});
|
||||
updateCaretsVisualAttributes(editor);
|
||||
updateCaretsVisualAttributes(new IjVimEditor(editor));
|
||||
}
|
||||
|
||||
public void editorDeinit(@NotNull Editor editor, boolean isReleased) {
|
||||
@ -288,6 +288,18 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
||||
notifyIdeaJoin(((IjVimEditor) editor).getEditor().getProject(), editor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCaretsVisualAttributes(@NotNull VimEditor editor) {
|
||||
Editor ijEditor = ((IjVimEditor) editor).getEditor();
|
||||
CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCaretsVisualPosition(@NotNull VimEditor editor) {
|
||||
Editor ijEditor = ((IjVimEditor) editor).getEditor();
|
||||
CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
|
||||
}
|
||||
|
||||
public static class NumberChangeListener implements EffectiveOptionValueChangeListener {
|
||||
public static NumberChangeListener INSTANCE = new NumberChangeListener();
|
||||
|
||||
|
@ -15,6 +15,7 @@ import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.util.PotemkinProgress
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
@ -77,11 +78,12 @@ internal class MacroGroup : VimMacroBase() {
|
||||
} catch (e: ProcessCanceledException) {
|
||||
return@runnable
|
||||
}
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
ProgressManager.getInstance().executeNonCancelableSection {
|
||||
// Prevent autocompletion during macros.
|
||||
// See https://github.com/JetBrains/ideavim/pull/772 for details
|
||||
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
|
||||
getInstance().handleKey(editor, key, context)
|
||||
keyHandler.handleKey(editor, key, context, keyHandler.keyHandlerState)
|
||||
}
|
||||
if (injector.messages.isError()) return@runnable
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.util.execution.ParametersListUtil
|
||||
import com.intellij.util.text.CharSequenceReader
|
||||
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
@ -37,7 +38,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
|
||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
||||
import java.io.BufferedWriter
|
||||
@ -85,24 +85,27 @@ public class ProcessGroup : VimProcessGroupBase() {
|
||||
modeBeforeCommandProcessing = currentMode
|
||||
val initText = getRange(editor, cmd)
|
||||
injector.markService.setVisualSelectionMarks(editor)
|
||||
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
|
||||
editor.mode = Mode.CMD_LINE(currentMode)
|
||||
val panel = ExEntryPanel.getInstance()
|
||||
panel.activate(editor.ij, context.ij, ":", initText, 1)
|
||||
}
|
||||
|
||||
public override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean {
|
||||
public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
|
||||
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
|
||||
// is open. So I'll put focus back in the editor and process the key.
|
||||
|
||||
val panel = ExEntryPanel.getInstance()
|
||||
if (panel.isActive) {
|
||||
requestFocus(panel.entry)
|
||||
panel.handleKey(stroke)
|
||||
|
||||
processResultBuilder.addExecutionStep { _, _, _ ->
|
||||
requestFocus(panel.entry)
|
||||
panel.handleKey(stroke)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
getInstance(editor).mode = NORMAL()
|
||||
getInstance().reset(editor)
|
||||
processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
||||
lambdaEditor.mode = NORMAL()
|
||||
getInstance().reset(lambdaEditor)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -112,7 +115,7 @@ public class ProcessGroup : VimProcessGroupBase() {
|
||||
panel.deactivate(true)
|
||||
var res = true
|
||||
try {
|
||||
getInstance(editor).mode = NORMAL()
|
||||
editor.mode = NORMAL()
|
||||
|
||||
logger.debug("processing command")
|
||||
|
||||
@ -152,7 +155,7 @@ public class ProcessGroup : VimProcessGroupBase() {
|
||||
}
|
||||
|
||||
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
|
||||
editor.vimStateMachine.mode = NORMAL()
|
||||
editor.mode = NORMAL()
|
||||
getInstance().reset(editor)
|
||||
val panel = ExEntryPanel.getInstance()
|
||||
panel.deactivate(true, resetCaret)
|
||||
@ -162,7 +165,7 @@ public class ProcessGroup : VimProcessGroupBase() {
|
||||
val initText = getRange(editor, cmd) + "!"
|
||||
val currentMode = editor.mode
|
||||
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
|
||||
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
|
||||
editor.mode = Mode.CMD_LINE(currentMode)
|
||||
val panel = ExEntryPanel.getInstance()
|
||||
panel.activate(editor.ij, context.ij, ":", initText, 1)
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ internal object IdeaSelectionControl {
|
||||
|
||||
logger.debug("Some carets have selection. State before adjustment: ${editor.vim.mode}")
|
||||
|
||||
editor.vim.vimStateMachine.mode = Mode.NORMAL()
|
||||
editor.vim.mode = Mode.NORMAL()
|
||||
|
||||
activateMode(editor, chooseSelectionMode(editor, selectionSource, true))
|
||||
} else {
|
||||
|
@ -339,7 +339,8 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
|
||||
override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
val enterKey = key(key)
|
||||
val context = injector.executionContextManager.onEditor(editor.vim, dataContext?.vim)
|
||||
KeyHandler.getInstance().handleKey(editor.vim, enterKey, context)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState)
|
||||
}
|
||||
|
||||
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
|
||||
|
@ -19,14 +19,17 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.IsReplaceCharListener
|
||||
import com.maddyhome.idea.vim.common.ModeChangeListener
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
|
||||
import com.maddyhome.idea.vim.options.helpers.GuiCursorMode
|
||||
import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper
|
||||
import com.maddyhome.idea.vim.options.helpers.GuiCursorType
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.awt.Color
|
||||
|
||||
@ -138,3 +141,31 @@ private object AttributesCache {
|
||||
|
||||
@TestOnly
|
||||
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
|
||||
|
||||
public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
|
||||
override fun isReplaceCharChanged(editor: VimEditor) {
|
||||
updateCaretsVisual(editor)
|
||||
}
|
||||
|
||||
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
|
||||
updateCaretsVisual(editor)
|
||||
}
|
||||
|
||||
private fun updateCaretsVisual(editor: VimEditor) {
|
||||
if (injector.globalOptions().ideaglobalmode) {
|
||||
updateAllEditorsCaretsVisual()
|
||||
} else {
|
||||
val ijEditor = (editor as IjVimEditor).editor
|
||||
ijEditor.updateCaretsVisualAttributes()
|
||||
ijEditor.updateCaretsVisualPosition()
|
||||
}
|
||||
}
|
||||
|
||||
public fun updateAllEditorsCaretsVisual() {
|
||||
injector.editorGroup.getEditors().forEach { editor ->
|
||||
val ijEditor = (editor as IjVimEditor).editor
|
||||
ijEditor.updateCaretsVisualAttributes()
|
||||
ijEditor.updateCaretsVisualPosition()
|
||||
}
|
||||
}
|
||||
}
|
@ -34,15 +34,15 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
|
||||
val returnTo = this.vim.vimStateMachine.mode.returnTo
|
||||
when (returnTo) {
|
||||
ReturnTo.INSERT -> {
|
||||
this.vim.vimStateMachine.mode = Mode.INSERT
|
||||
this.vim.mode = Mode.INSERT
|
||||
}
|
||||
|
||||
ReturnTo.REPLACE -> {
|
||||
this.vim.vimStateMachine.mode = Mode.REPLACE
|
||||
this.vim.mode = Mode.REPLACE
|
||||
}
|
||||
|
||||
null -> {
|
||||
this.vim.vimStateMachine.mode = Mode.NORMAL()
|
||||
this.vim.mode = Mode.NORMAL()
|
||||
}
|
||||
}
|
||||
SelectionVimListenerSuppressor.lock().use {
|
||||
@ -67,15 +67,15 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
|
||||
val returnTo = this.vimStateMachine.mode.returnTo
|
||||
when (returnTo) {
|
||||
ReturnTo.INSERT -> {
|
||||
this.vimStateMachine.mode = Mode.INSERT
|
||||
this.mode = Mode.INSERT
|
||||
}
|
||||
|
||||
ReturnTo.REPLACE -> {
|
||||
this.vimStateMachine.mode = Mode.REPLACE
|
||||
this.mode = Mode.REPLACE
|
||||
}
|
||||
|
||||
null -> {
|
||||
this.vimStateMachine.mode = Mode.NORMAL()
|
||||
this.mode = Mode.NORMAL()
|
||||
}
|
||||
}
|
||||
SelectionVimListenerSuppressor.lock().use {
|
||||
|
@ -75,7 +75,7 @@ internal object IdeaSpecifics {
|
||||
}
|
||||
}
|
||||
|
||||
if (hostEditor != null && action is ChooseItemAction && hostEditor.vimStateMachine?.isRecording == true) {
|
||||
if (hostEditor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
|
||||
val lookup = LookupManager.getActiveLookup(hostEditor)
|
||||
if (lookup != null) {
|
||||
val charsToRemove = hostEditor.caretModel.primaryCaret.offset - lookup.lookupStart
|
||||
@ -96,7 +96,7 @@ internal object IdeaSpecifics {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
val editor = editor
|
||||
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
|
||||
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
|
||||
val prevDocumentLength = completionPrevDocumentLength
|
||||
val prevDocumentOffset = completionPrevDocumentOffset
|
||||
|
||||
@ -125,7 +125,7 @@ internal object IdeaSpecifics {
|
||||
) {
|
||||
editor?.let {
|
||||
val commandState = it.vim.vimStateMachine
|
||||
commandState.mode = Mode.NORMAL()
|
||||
it.vim.mode = Mode.NORMAL()
|
||||
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
|
||||
KeyHandler.getInstance().reset(it.vim)
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ import com.maddyhome.idea.vim.group.visual.VimVisualTimer
|
||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
|
||||
import com.maddyhome.idea.vim.handler.correctorRequester
|
||||
import com.maddyhome.idea.vim.handler.keyCheckRequests
|
||||
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
|
||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
|
||||
@ -93,11 +94,12 @@ import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
|
||||
import com.maddyhome.idea.vim.vimDisposable
|
||||
import java.awt.event.MouseAdapter
|
||||
@ -137,11 +139,27 @@ internal object VimListenerManager {
|
||||
EditorListeners.addAll()
|
||||
check(correctorRequester.tryEmit(Unit))
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
|
||||
val caretVisualAttributesListener = CaretVisualAttributesListener()
|
||||
injector.listenersNotifier.modeChangeListeners.add(caretVisualAttributesListener)
|
||||
injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
|
||||
caretVisualAttributesListener.updateAllEditorsCaretsVisual()
|
||||
|
||||
val modeWidgetListener = ModeWidgetListener()
|
||||
injector.listenersNotifier.modeChangeListeners.add(modeWidgetListener)
|
||||
injector.listenersNotifier.myEditorListeners.add(modeWidgetListener)
|
||||
injector.listenersNotifier.vimPluginListeners.add(modeWidgetListener)
|
||||
|
||||
val macroWidgetListener = MacroWidgetListener()
|
||||
injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener)
|
||||
injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener)
|
||||
}
|
||||
|
||||
fun turnOff() {
|
||||
GlobalListeners.disable()
|
||||
EditorListeners.removeAll()
|
||||
injector.listenersNotifier.reset()
|
||||
|
||||
check(correctorRequester.tryEmit(Unit))
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ import com.maddyhome.idea.vim.api.VimScrollingModel
|
||||
import com.maddyhome.idea.vim.api.VimSelectionModel
|
||||
import com.maddyhome.idea.vim.api.VimVisualPosition
|
||||
import com.maddyhome.idea.vim.api.VirtualFile
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.EditorLine
|
||||
import com.maddyhome.idea.vim.common.IndentConfig
|
||||
@ -51,13 +52,16 @@ import com.maddyhome.idea.vim.helper.fileSize
|
||||
import com.maddyhome.idea.vim.helper.getTopLevelEditor
|
||||
import com.maddyhome.idea.vim.helper.inExMode
|
||||
import com.maddyhome.idea.vim.helper.isTemplateActive
|
||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
|
||||
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
|
||||
import com.maddyhome.idea.vim.helper.vimLastSelectionType
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
|
||||
import com.maddyhome.idea.vim.impl.state.toMappingMode
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.lang.System.identityHashCode
|
||||
|
||||
@ -243,14 +247,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateCaretsVisualAttributes() {
|
||||
editor.updateCaretsVisualAttributes()
|
||||
}
|
||||
|
||||
override fun updateCaretsVisualPosition() {
|
||||
editor.updateCaretsVisualPosition()
|
||||
}
|
||||
|
||||
override fun offsetToVisualPosition(offset: Int): VimVisualPosition {
|
||||
return editor.offsetToVisualPosition(offset).let { VimVisualPosition(it.line, it.column, it.leansRight) }
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ internal class IjVimInjector : VimInjectorBase() {
|
||||
override fun commandStateFor(editor: VimEditor): VimStateMachine {
|
||||
var res = editor.ij.vimStateMachine
|
||||
if (res == null) {
|
||||
res = VimStateMachineImpl(editor)
|
||||
res = VimStateMachineImpl()
|
||||
editor.ij.vimStateMachine = res
|
||||
}
|
||||
return res
|
||||
|
@ -13,6 +13,7 @@ import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.diagnostic.trace
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import java.awt.KeyEventDispatcher
|
||||
@ -60,7 +61,7 @@ public object ModalEntry {
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
if (editor.vimStateMachine.isRecording) {
|
||||
if (injector.registerGroup.isRecording) {
|
||||
KeyHandler.getInstance().modalEntryKeys += stroke
|
||||
}
|
||||
if (!processor(stroke)) {
|
||||
|
@ -114,7 +114,8 @@ internal class CompleteEntryAction : TextAction(ExEditorKit.CompleteEntry) {
|
||||
// write action
|
||||
// * The key handler routines get the chance to clean up and reset state
|
||||
val entry = ExEntryPanel.getInstance().entry
|
||||
KeyHandler.getInstance().handleKey(entry.editor.vim, stroke, entry.context.vim)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(entry.editor.vim, stroke, entry.context.vim, keyHandler.keyHandlerState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -125,10 +125,12 @@ internal object ExEditorKit : DefaultEditorKit() {
|
||||
if (target.useHandleKeyFromEx) {
|
||||
val entry = ExEntryPanel.getInstance().entry
|
||||
val editor = entry.editor
|
||||
KeyHandler.getInstance().handleKey(
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(
|
||||
editor.vim,
|
||||
key,
|
||||
injector.executionContextManager.onEditor(editor.vim, entry.context.vim),
|
||||
keyHandler.keyHandlerState,
|
||||
)
|
||||
} else {
|
||||
val event = ActionEvent(e.source, e.id, c.toString(), e.getWhen(), e.modifiers)
|
||||
|
@ -36,10 +36,12 @@ internal class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : Dum
|
||||
val keyStroke = getKeyStroke(e)
|
||||
if (keyStroke != null) {
|
||||
val editor = exEntryPanel.entry.editor
|
||||
KeyHandler.getInstance().handleKey(
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(
|
||||
editor.vim,
|
||||
keyStroke,
|
||||
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
|
||||
keyHandler.keyHandlerState
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -8,15 +8,10 @@
|
||||
|
||||
package com.maddyhome.idea.vim.ui.widgets
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.VimPluginListener
|
||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
|
||||
|
||||
public class VimWidgetListener(private val updateWidget: Runnable) : GlobalOptionChangeListener, VimPluginListener {
|
||||
init {
|
||||
injector.listenersNotifier.vimPluginListeners.add(this)
|
||||
}
|
||||
|
||||
public open class VimWidgetListener(private val updateWidget: Runnable) : GlobalOptionChangeListener, VimPluginListener {
|
||||
override fun onGlobalOptionChanged() {
|
||||
updateWidget.run()
|
||||
}
|
||||
|
@ -13,11 +13,13 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.wm.StatusBarWidget
|
||||
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
||||
import com.intellij.openapi.wm.WindowManager
|
||||
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.MacroRecordingListener
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
|
||||
@ -26,21 +28,7 @@ import java.awt.Component
|
||||
|
||||
private const val ID = "IdeaVimMacro"
|
||||
|
||||
internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget {
|
||||
private var content: String = ""
|
||||
|
||||
private val macroRecordingListener = object : MacroRecordingListener {
|
||||
override fun recordingStarted(editor: VimEditor, register: Char) {
|
||||
content = "recording @$register"
|
||||
updateWidgetInStatusBar(ID, editor.ij.project)
|
||||
}
|
||||
|
||||
override fun recordingFinished(editor: VimEditor, register: Char) {
|
||||
content = ""
|
||||
updateWidgetInStatusBar(ID, editor.ij.project)
|
||||
}
|
||||
}
|
||||
|
||||
internal class MacroWidgetFactory : StatusBarWidgetFactory {
|
||||
override fun getId(): String {
|
||||
return ID
|
||||
}
|
||||
@ -50,22 +38,23 @@ internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget {
|
||||
}
|
||||
|
||||
override fun createWidget(project: Project): StatusBarWidget {
|
||||
injector.listenersNotifier.macroRecordingListeners.add(macroRecordingListener)
|
||||
return VimMacroWidget()
|
||||
}
|
||||
|
||||
override fun isAvailable(project: Project): Boolean {
|
||||
return VimPlugin.isEnabled() && injector.globalIjOptions().showmodewidget
|
||||
}
|
||||
}
|
||||
|
||||
private inner class VimMacroWidget : StatusBarWidget {
|
||||
override fun ID(): String {
|
||||
return ID
|
||||
}
|
||||
public class VimMacroWidget : StatusBarWidget, VimStatusBarWidget {
|
||||
public var content: String = ""
|
||||
|
||||
override fun getPresentation(): StatusBarWidget.WidgetPresentation {
|
||||
return VimModeWidgetPresentation()
|
||||
}
|
||||
override fun ID(): String {
|
||||
return ID
|
||||
}
|
||||
|
||||
override fun getPresentation(): StatusBarWidget.WidgetPresentation {
|
||||
return VimModeWidgetPresentation()
|
||||
}
|
||||
|
||||
private inner class VimModeWidgetPresentation : StatusBarWidget.TextPresentation {
|
||||
@ -91,4 +80,30 @@ public fun updateMacroWidget() {
|
||||
}
|
||||
}
|
||||
|
||||
public val macroWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateMacroWidget() }
|
||||
// TODO: At the moment recording macro & RegisterGroup is bound to application, so macro will be recorded even if we
|
||||
// move between projects. BUT it's not a good idea. Maybe RegisterGroup should have it's own project scope instances
|
||||
public class MacroWidgetListener : MacroRecordingListener, VimWidgetListener({ updateMacroWidget() }) {
|
||||
override fun recordingStarted() {
|
||||
for (project in ProjectManager.getInstance().openProjects) {
|
||||
val macroWidget = getWidget(project) ?: continue
|
||||
val register = injector.registerGroup.recordRegister
|
||||
macroWidget.content = "recording @$register"
|
||||
macroWidget.updateWidgetInStatusBar(ID, project)
|
||||
}
|
||||
}
|
||||
|
||||
override fun recordingFinished() {
|
||||
for (project in ProjectManager.getInstance().openProjects) {
|
||||
val macroWidget = getWidget(project) ?: continue
|
||||
macroWidget.content = ""
|
||||
macroWidget.updateWidgetInStatusBar(ID, project)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWidget(project: Project): VimMacroWidget? {
|
||||
val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: return null
|
||||
return statusBar.getWidget(ID) as? VimMacroWidget
|
||||
}
|
||||
}
|
||||
|
||||
public val macroWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateMacroWidget() }
|
||||
|
@ -20,14 +20,9 @@ import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
|
||||
import com.intellij.ui.awt.RelativePoint
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.util.width
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetFocusListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetModeListener
|
||||
import java.awt.Dimension
|
||||
import java.awt.Point
|
||||
import java.awt.event.MouseAdapter
|
||||
@ -54,11 +49,6 @@ public class VimModeWidget(public val project: Project) : CustomStatusBarWidget,
|
||||
val mode = getFocusedEditor(project)?.vim?.mode
|
||||
updateLabel(mode)
|
||||
|
||||
injector.listenersNotifier.apply {
|
||||
modeChangeListeners.add(ModeWidgetModeListener(this@VimModeWidget))
|
||||
myEditorListeners.add(ModeWidgetFocusListener(this@VimModeWidget))
|
||||
}
|
||||
|
||||
label.addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseClicked(e: MouseEvent) {
|
||||
val popup = ModeWidgetPopup.createPopup() ?: return
|
||||
|
@ -11,23 +11,48 @@ package com.maddyhome.idea.vim.ui.widgets.mode.listeners
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.WindowManager
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.common.EditorListener
|
||||
import com.maddyhome.idea.vim.common.ModeChangeListener
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.updateModeWidget
|
||||
|
||||
internal class ModeWidgetFocusListener(private val modeWidget: VimModeWidget): EditorListener {
|
||||
internal class ModeWidgetListener: ModeChangeListener, EditorListener, VimWidgetListener({ updateModeWidget()}) {
|
||||
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
|
||||
val modeWidget = getWidget(editor) ?: return
|
||||
val editorMode = editor.mode
|
||||
if (editorMode !is Mode.OP_PENDING) {
|
||||
modeWidget.updateWidget(editorMode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWidget(editor: VimEditor): VimModeWidget? {
|
||||
val project = (editor as IjVimEditor).editor.project ?: return null
|
||||
return getWidget(project)
|
||||
}
|
||||
|
||||
private fun getWidget(project: Project): VimModeWidget? {
|
||||
val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: return null
|
||||
return statusBar.getWidget(ModeWidgetFactory.ID) as? VimModeWidget
|
||||
}
|
||||
|
||||
override fun created(editor: VimEditor) {
|
||||
updateModeWidget()
|
||||
val modeWidget = getWidget(editor) ?: return
|
||||
val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode
|
||||
modeWidget.updateWidget(mode)
|
||||
}
|
||||
|
||||
override fun released(editor: VimEditor) {
|
||||
updateModeWidget()
|
||||
val modeWidget = getWidget(editor) ?: return
|
||||
val focusedEditor = getFocusedEditorForProject(editor.ij.project)
|
||||
if (focusedEditor == null || focusedEditor == editor.ij) {
|
||||
modeWidget.updateWidget(null)
|
||||
@ -35,18 +60,19 @@ internal class ModeWidgetFocusListener(private val modeWidget: VimModeWidget): E
|
||||
}
|
||||
|
||||
override fun focusGained(editor: VimEditor) {
|
||||
if (editor.ij.project != modeWidget.project) return
|
||||
val modeWidget = getWidget(editor) ?: return
|
||||
val mode = editor.mode
|
||||
modeWidget.updateWidget(mode)
|
||||
}
|
||||
|
||||
override fun focusLost(editor: VimEditor) {
|
||||
val modeWidget = getWidget(editor) ?: return
|
||||
val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode
|
||||
modeWidget.updateWidget(mode)
|
||||
}
|
||||
|
||||
private fun getFocusedEditorForProject(editorProject: Project?): Editor? {
|
||||
if (editorProject != modeWidget.project) return null
|
||||
if (editorProject == null) return null
|
||||
val fileEditorManager = FileEditorManager.getInstance(editorProject)
|
||||
return fileEditorManager.selectedTextEditor
|
||||
}
|
@ -1,25 +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.ui.widgets.mode.listeners
|
||||
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.common.ModeChangeListener
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
|
||||
|
||||
internal class ModeWidgetModeListener(private val modeWidget: VimModeWidget): ModeChangeListener {
|
||||
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
|
||||
val editorMode = editor.mode
|
||||
if (editorMode !is Mode.OP_PENDING && editor.ij.project == modeWidget.project) {
|
||||
modeWidget.updateWidget(editorMode)
|
||||
}
|
||||
}
|
||||
}
|
@ -57,7 +57,7 @@ internal object IdeaRefactorModeHelper {
|
||||
}
|
||||
|
||||
is Action.SetMode -> {
|
||||
editor.vim.vimStateMachine.mode = correction.newMode
|
||||
editor.vim.mode = correction.newMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ package org.jetbrains.plugins.ideavim.action
|
||||
import com.intellij.idea.TestFor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
@ -17,6 +19,7 @@ import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.jetbrains.plugins.ideavim.waitAndAssert
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
@ -144,7 +147,7 @@ class CopyActionTest : VimTestCase() {
|
||||
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertPluginError(true)
|
||||
assertTrue(fixture.editor.vim.vimStateMachine.commandBuilder.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -14,8 +14,6 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.keys
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import org.jetbrains.plugins.ideavim.ExceptionHandler
|
||||
import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
@ -44,9 +42,8 @@ class MacroActionTest : VimTestCase() {
|
||||
// |q|
|
||||
@Test
|
||||
fun testRecordMacro() {
|
||||
val editor = typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "on<caret>e two three\n")
|
||||
val commandState = editor.vim.vimStateMachine
|
||||
kotlin.test.assertFalse(commandState.isRecording)
|
||||
typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "on<caret>e two three\n")
|
||||
kotlin.test.assertFalse(injector.registerGroup.isRecording)
|
||||
assertRegister('a', "3l")
|
||||
}
|
||||
|
||||
@ -101,7 +98,7 @@ class MacroActionTest : VimTestCase() {
|
||||
|
||||
val register = VimPlugin.getRegister().getRegister('a')
|
||||
val registerSize = register!!.keys.size
|
||||
kotlin.test.assertEquals(9, registerSize)
|
||||
assertEquals(9, registerSize)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 org.jetbrains.plugins.ideavim.command
|
||||
|
||||
import com.intellij.openapi.wm.WindowManager
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
|
||||
// TODO it would be cool to test widget, but status bar is not initialized
|
||||
class VimShowModeTest : VimTestCase() {
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in normal`() {
|
||||
// configureByText("123")
|
||||
// val widget = getModeWidget()
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in insert`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("i"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- INSERT --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in replace`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("R"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- REPLACE --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in visual`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("v"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- VISUAL --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in visual line`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("V"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- VISUAL LINE --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in visual block`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("<C-V>"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- VISUAL BLOCK --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in select`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("gh"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- SELECT --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in select line`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("gH"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- SELECT LINE --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in select block`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("g<C-H>"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- SELECT BLOCK --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in one command`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("i<C-O>"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- (insert) --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in one command visual`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("i<C-O>v"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- (insert) VISUAL --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in one command visual block`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("i<C-O><C-V>"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- (insert) VISUAL BLOCK --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in one command visual line`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("i<C-O>V"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- (insert) VISUAL LINE --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in one command select`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("i<C-O>gh"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- (insert) SELECT --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in one command select block`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("i<C-O>g<C-H>"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- (insert) SELECT BLOCK --", statusString)
|
||||
// }
|
||||
//
|
||||
// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
// @Test
|
||||
// fun `test status string in one command select line`() {
|
||||
// configureByText("123")
|
||||
// typeText(injector.parser.parseKeys("i<C-O>gH"))
|
||||
// val statusString = fixture.editor.vim.getStatusString()
|
||||
// kotlin.test.assertEquals("-- (insert) SELECT LINE --", statusString)
|
||||
// }
|
||||
//
|
||||
|
||||
// Always return null
|
||||
private fun getModeWidget(): VimModeWidget? {
|
||||
val project = fixture.editor?.project ?: return null
|
||||
val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: return null
|
||||
return statusBar.getWidget(ModeWidgetFactory.ID) as? VimModeWidget
|
||||
}
|
||||
}
|
@ -1,162 +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 org.jetbrains.plugins.ideavim.command
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class VimStateMachineTest : VimTestCase() {
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in normal`() {
|
||||
configureByText("123")
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in insert`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("i"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- INSERT --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in replace`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("R"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- REPLACE --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in visual`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("v"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- VISUAL --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in visual line`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("V"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- VISUAL LINE --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in visual block`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("<C-V>"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- VISUAL BLOCK --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in select`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("gh"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- SELECT --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in select line`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("gH"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- SELECT LINE --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in select block`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("g<C-H>"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- SELECT BLOCK --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in one command`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("i<C-O>"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- (insert) --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in one command visual`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("i<C-O>v"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- (insert) VISUAL --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in one command visual block`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("i<C-O><C-V>"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- (insert) VISUAL BLOCK --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in one command visual line`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("i<C-O>V"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- (insert) VISUAL LINE --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in one command select`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("i<C-O>gh"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- (insert) SELECT --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in one command select block`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("i<C-O>g<C-H>"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- (insert) SELECT BLOCK --", statusString)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
@Test
|
||||
fun `test status string in one command select line`() {
|
||||
configureByText("123")
|
||||
typeText(injector.parser.parseKeys("i<C-O>gH"))
|
||||
val statusString = fixture.editor.vim.vimStateMachine.getStatusString()
|
||||
kotlin.test.assertEquals("-- (insert) SELECT LINE --", statusString)
|
||||
}
|
||||
}
|
@ -84,7 +84,6 @@ Mode.INSERT,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val code = """
|
||||
fun ${c}sum(x: Int, y: Int, z: Int): Int {
|
||||
return x + y + z
|
||||
|
@ -190,7 +190,7 @@ class VimMultipleCursorsExtensionTest : VimTestCase() {
|
||||
|dfkjsg
|
||||
""".trimMargin()
|
||||
val editor = configureByText(before)
|
||||
editor.vim.vimStateMachine.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE)
|
||||
editor.vim.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE)
|
||||
|
||||
typeText(injector.parser.parseKeys("<A-p>"))
|
||||
|
||||
@ -273,7 +273,7 @@ class VimMultipleCursorsExtensionTest : VimTestCase() {
|
||||
|dfkjsg
|
||||
""".trimMargin()
|
||||
val editor = configureByText(before)
|
||||
editor.vim.vimStateMachine.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE)
|
||||
editor.vim.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE)
|
||||
|
||||
typeText(injector.parser.parseKeys("<A-x>"))
|
||||
assertMode(Mode.VISUAL(SelectionType.CHARACTER_WISE))
|
||||
@ -363,7 +363,7 @@ fun getCellType(${s}pos$se: VisualPosition): CellType {
|
||||
fun `test ignores regex in search pattern`() {
|
||||
val before = "test ${s}t.*st${c}$se toast tallest t.*st"
|
||||
val editor = configureByText(before)
|
||||
editor.vim.vimStateMachine.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE)
|
||||
editor.vim.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE)
|
||||
|
||||
typeText(injector.parser.parseKeys("<A-n><A-n>"))
|
||||
val after = "test ${s}t.*st$se toast tallest ${s}t.*st$se"
|
||||
|
@ -73,7 +73,6 @@ import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper
|
||||
import com.maddyhome.idea.vim.options.helpers.GuiCursorType
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
|
||||
@ -119,6 +118,7 @@ abstract class VimTestCase {
|
||||
if (editor != null) {
|
||||
KeyHandler.getInstance().fullReset(editor.vim)
|
||||
}
|
||||
KeyHandler.getInstance().keyHandlerState.reset(Mode.NORMAL())
|
||||
VimPlugin.getOptionGroup().resetAllOptionsForTesting()
|
||||
VimPlugin.getKey().resetKeyMappings()
|
||||
VimPlugin.getSearch().resetState()
|
||||
@ -157,6 +157,7 @@ abstract class VimTestCase {
|
||||
bookmarksManager?.bookmarks?.forEach { bookmark ->
|
||||
bookmarksManager.remove(bookmark)
|
||||
}
|
||||
fixture.editor?.let { injector.messages.showStatusBarMessage(it.vim, "") }
|
||||
SelectionVimListenerSuppressor.lock().use { fixture.tearDown() }
|
||||
ExEntryPanel.getInstance().deactivate(false)
|
||||
VimPlugin.getVariableService().clear()
|
||||
@ -817,7 +818,7 @@ abstract class VimTestCase {
|
||||
val inputModel = TestInputModel.getInstance(editor)
|
||||
var key = inputModel.nextKeyStroke()
|
||||
while (key != null) {
|
||||
keyHandler.handleKey(editor.vim, key, dataContext)
|
||||
keyHandler.handleKey(editor.vim, key, dataContext, keyHandler.keyHandlerState)
|
||||
key = inputModel.nextKeyStroke()
|
||||
}
|
||||
},
|
||||
|
@ -32,7 +32,6 @@ abstract class VimPropertyTestBase : VimTestCase() {
|
||||
VimPlugin.getRegister().resetRegisters()
|
||||
editor.caretModel.runForEachCaret { it.moveToOffset(0) }
|
||||
|
||||
editor.vim.vimStateMachine.resetDigraph()
|
||||
VimPlugin.getSearch().resetState()
|
||||
VimPlugin.getChange().reset()
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ plugins {
|
||||
kotlin("jvm")
|
||||
// id("org.jlleitschuh.gradle.ktlint")
|
||||
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
|
||||
kotlin("plugin.serialization") version "1.9.22"
|
||||
`maven-publish`
|
||||
antlr
|
||||
}
|
||||
@ -51,7 +52,7 @@ dependencies {
|
||||
antlr("org.antlr:antlr4:4.13.1")
|
||||
|
||||
ksp(project(":annotation-processors"))
|
||||
implementation(project(":annotation-processors"))
|
||||
compileOnly(project(":annotation-processors"))
|
||||
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion")
|
||||
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.0.0")
|
||||
|
@ -12,44 +12,50 @@ import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
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.CommandBuilder
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.command.MappingProcessor
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.CurrentCommandState
|
||||
import com.maddyhome.idea.vim.common.DigraphResult
|
||||
import com.maddyhome.idea.vim.common.argumentCaptured
|
||||
import com.maddyhome.idea.vim.diagnostic.VimLogger
|
||||
import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.trace
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.key.CommandNode
|
||||
import com.maddyhome.idea.vim.impl.state.toMappingMode
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.key.KeyStack
|
||||
import com.maddyhome.idea.vim.key.Node
|
||||
import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.CommandConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.DigraphConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.ModeInputConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.RegisterConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.SelectRegisterConsumer
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
||||
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||
import java.awt.event.InputEvent
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
/**
|
||||
* This handles every keystroke that the user can argType except those that are still valid hotkeys for various Idea
|
||||
* actions. This is a singleton.
|
||||
*/
|
||||
// TODO for future refactorings (PRs are welcome)
|
||||
// 1. avoid using handleKeyRecursionCount & shouldRecord
|
||||
// 2. maybe we can live without allowKeyMappings: Boolean & mappingCompleted: Boolean
|
||||
public class KeyHandler {
|
||||
|
||||
private val keyConsumers: List<KeyConsumer> = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer(), SelectRegisterConsumer(), ModeInputConsumer())
|
||||
private var handleKeyRecursionCount = 0
|
||||
|
||||
public var keyHandlerState: KeyHandlerState = KeyHandlerState()
|
||||
private set
|
||||
|
||||
public val keyStack: KeyStack = KeyStack()
|
||||
public val modalEntryKeys: MutableList<KeyStroke> = ArrayList()
|
||||
|
||||
@ -61,8 +67,8 @@ public class KeyHandler {
|
||||
* @param key The keystroke typed by the user
|
||||
* @param context The data context
|
||||
*/
|
||||
public fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext) {
|
||||
handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false)
|
||||
public fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||
handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false, keyState)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,8 +76,6 @@ public class KeyHandler {
|
||||
*
|
||||
* @param allowKeyMappings - If we allow key mappings or not
|
||||
* @param mappingCompleted - if true, we don't check if the mapping is incomplete
|
||||
*
|
||||
* TODO mappingCompleted and recursionCounter - we should find a more beautiful way to use them
|
||||
*/
|
||||
public fun handleKey(
|
||||
editor: VimEditor,
|
||||
@ -79,119 +83,91 @@ public class KeyHandler {
|
||||
context: ExecutionContext,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyState: KeyHandlerState,
|
||||
) {
|
||||
LOG.trace {
|
||||
"""
|
||||
val result = processKey(key, editor, allowKeyMappings, mappingCompleted, KeyProcessResult.SynchronousKeyProcessBuilder(keyState))
|
||||
if (result is KeyProcessResult.Executable) {
|
||||
result.execute(editor, context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines whether IdeaVim can handle the passed key or not.
|
||||
* For instance, if there is no mapping for <F5>, we should return 'KeyProcessResult.Unknown' to inform the IDE that
|
||||
* we did not process the keypress, and therefore need to propagate it further.
|
||||
* Alternatively, if we understand the key, we return a 'KeyProcessResult.Executable', which contains a runnable that
|
||||
* could execute the key if needed.
|
||||
*/
|
||||
public fun processKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
): KeyProcessResult {
|
||||
synchronized(lock) {
|
||||
LOG.trace {
|
||||
"""
|
||||
------- Key Handler -------
|
||||
Start key processing. allowKeyMappings: $allowKeyMappings, mappingCompleted: $mappingCompleted
|
||||
Key: $key
|
||||
""".trimIndent()
|
||||
}
|
||||
val maxMapDepth = injector.globalOptions().maxmapdepth
|
||||
if (handleKeyRecursionCount >= maxMapDepth) {
|
||||
injector.messages.showStatusBarMessage(editor, injector.messages.message("E223"))
|
||||
injector.messages.indicateError()
|
||||
LOG.warn("Key handling, maximum recursion of the key received. maxdepth=$maxMapDepth")
|
||||
return
|
||||
}
|
||||
}
|
||||
val maxMapDepth = injector.globalOptions().maxmapdepth
|
||||
if (handleKeyRecursionCount >= maxMapDepth) {
|
||||
processBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
||||
LOG.warn("Key handling, maximum recursion of the key received. maxdepth=$maxMapDepth")
|
||||
injector.messages.showStatusBarMessage(lambdaEditor, injector.messages.message("E223"))
|
||||
injector.messages.indicateError()
|
||||
}
|
||||
return processBuilder.build()
|
||||
}
|
||||
|
||||
injector.messages.clearError()
|
||||
val editorState = editor.vimStateMachine
|
||||
val commandBuilder = editorState.commandBuilder
|
||||
injector.messages.clearError()
|
||||
// We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything.
|
||||
val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording)
|
||||
|
||||
// If this is a "regular" character keystroke, get the character
|
||||
val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar
|
||||
|
||||
// We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything.
|
||||
var shouldRecord = handleKeyRecursionCount == 0 && editorState.isRecording
|
||||
handleKeyRecursionCount++
|
||||
try {
|
||||
LOG.trace("Start key processing...")
|
||||
if (!allowKeyMappings || !MappingProcessor.handleKeyMapping(editor, key, context, mappingCompleted)) {
|
||||
LOG.trace("Mappings processed, continue processing key.")
|
||||
if (isCommandCountKey(chKey, editorState)) {
|
||||
commandBuilder.addCountCharacter(key)
|
||||
} else if (isDeleteCommandCountKey(key, editorState)) {
|
||||
commandBuilder.deleteCountCharacter()
|
||||
} else if (isEditorReset(key, editorState)) {
|
||||
handleEditorReset(editor, key, context, editorState)
|
||||
} else if (isExpectingCharArgument(commandBuilder)) {
|
||||
handleCharArgument(key, chKey, editorState)
|
||||
} else if (editorState.isRegisterPending) {
|
||||
LOG.trace("Pending mode.")
|
||||
commandBuilder.addKey(key)
|
||||
handleSelectRegister(editorState, chKey)
|
||||
} else if (!handleDigraph(editor, key, context, editorState)) {
|
||||
LOG.debug("Digraph is NOT processed")
|
||||
|
||||
// Ask the key/action tree if this is an appropriate key at this point in the command and if so,
|
||||
// return the node matching this keystroke
|
||||
val node: Node<LazyVimCommand>? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState)
|
||||
LOG.trace("Get the node for the current mode")
|
||||
|
||||
if (node is CommandNode<LazyVimCommand>) {
|
||||
LOG.trace("Node is a command node")
|
||||
handleCommandNode(editor, context, key, node, editorState)
|
||||
commandBuilder.addKey(key)
|
||||
} else if (node is CommandPartNode<LazyVimCommand>) {
|
||||
LOG.trace("Node is a command part node")
|
||||
commandBuilder.setCurrentCommandPartNode(node)
|
||||
commandBuilder.addKey(key)
|
||||
} else if (isSelectRegister(key, editorState)) {
|
||||
LOG.trace("Select register")
|
||||
editorState.isRegisterPending = true
|
||||
commandBuilder.addKey(key)
|
||||
} else {
|
||||
// node == null
|
||||
LOG.trace("We are not able to find a node for this key")
|
||||
|
||||
// If we are in insert/replace mode send this key in for processing
|
||||
if (editorState.mode == Mode.INSERT || editorState.mode == Mode.REPLACE) {
|
||||
LOG.trace("Process insert or replace")
|
||||
shouldRecord = injector.changeGroup.processKey(editor, context, key) && shouldRecord
|
||||
} else if (editorState.mode is Mode.SELECT) {
|
||||
LOG.trace("Process select")
|
||||
shouldRecord = injector.changeGroup.processKeyInSelectMode(editor, context, key) && shouldRecord
|
||||
} else if (editorState.mappingState.mappingMode == MappingMode.CMD_LINE) {
|
||||
LOG.trace("Process cmd line")
|
||||
shouldRecord = injector.processGroup.processExKey(editor, key) && shouldRecord
|
||||
} else {
|
||||
LOG.trace("Set command state to bad_command")
|
||||
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
|
||||
}
|
||||
partialReset(editor)
|
||||
handleKeyRecursionCount++
|
||||
try {
|
||||
val isProcessed = keyConsumers.any {
|
||||
it.consumeKey(key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord)
|
||||
}
|
||||
if (isProcessed) {
|
||||
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
|
||||
finishedCommandPreparation(lambdaEditor, lambdaContext, key, shouldRecord, lambdaKeyState)
|
||||
}
|
||||
} else {
|
||||
// Key wasn't processed by any of the consumers, so we reset our key state
|
||||
onUnknownKey(editor, processBuilder.state)
|
||||
updateState(processBuilder.state)
|
||||
return KeyProcessResult.Unknown.apply {
|
||||
handleKeyRecursionCount-- // because onFinish will now be executed for unknown
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
processBuilder.onFinish = { handleKeyRecursionCount-- }
|
||||
}
|
||||
finishedCommandPreparation(editor, context, editorState, commandBuilder, key, shouldRecord)
|
||||
} finally {
|
||||
handleKeyRecursionCount--
|
||||
return processBuilder.build()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun finishedCommandPreparation(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
editorState: VimStateMachine,
|
||||
commandBuilder: CommandBuilder,
|
||||
key: KeyStroke?,
|
||||
shouldRecord: Boolean,
|
||||
shouldRecord: MutableBoolean,
|
||||
keyState: KeyHandlerState,
|
||||
) {
|
||||
// Do we have a fully entered command at this point? If so, let's execute it.
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
|
||||
if (commandBuilder.isReady) {
|
||||
LOG.trace("Ready command builder. Execute command.")
|
||||
executeCommand(editor, context, editorState)
|
||||
} else if (commandBuilder.isBad) {
|
||||
LOG.trace("Command builder is set to BAD")
|
||||
editorState.resetOpPending()
|
||||
editorState.resetRegisterPending()
|
||||
editorState.resetReplaceCharacter()
|
||||
injector.messages.indicateError()
|
||||
reset(editor)
|
||||
executeCommand(editor, context, editor.vimStateMachine, keyState)
|
||||
}
|
||||
|
||||
// Don't record the keystroke that stops the recording (unmapped this is `q`)
|
||||
if (shouldRecord && editorState.isRecording && key != null) {
|
||||
if (shouldRecord.value && injector.registerGroup.isRecording && key != null) {
|
||||
injector.registerGroup.recordKeyStroke(key)
|
||||
modalEntryKeys.forEach { injector.registerGroup.recordKeyStroke(it) }
|
||||
modalEntryKeys.clear()
|
||||
@ -202,226 +178,44 @@ public class KeyHandler {
|
||||
LOG.trace("----------- Key Handler Finished -----------")
|
||||
}
|
||||
|
||||
/**
|
||||
* See the description for [com.maddyhome.idea.vim.command.DuplicableOperatorAction]
|
||||
*/
|
||||
private fun mapOpCommand(
|
||||
key: KeyStroke,
|
||||
node: Node<LazyVimCommand>?,
|
||||
editorState: VimStateMachine,
|
||||
): Node<LazyVimCommand>? {
|
||||
return if (editorState.isDuplicateOperatorKeyStroke(key)) {
|
||||
editorState.commandBuilder.getChildNode(KeyStroke.getKeyStroke('_'))
|
||||
} else {
|
||||
node
|
||||
}
|
||||
private fun onUnknownKey(editor: VimEditor, keyState: KeyHandlerState) {
|
||||
keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
|
||||
LOG.trace("Command builder is set to BAD")
|
||||
editor.resetOpPending()
|
||||
editor.vimStateMachine.resetRegisterPending()
|
||||
editor.isReplaceCharacter = false
|
||||
reset(keyState, editor.mode)
|
||||
}
|
||||
|
||||
private fun handleEditorReset(
|
||||
editor: VimEditor,
|
||||
key: KeyStroke,
|
||||
context: ExecutionContext,
|
||||
editorState: VimStateMachine,
|
||||
) {
|
||||
val commandBuilder = editorState.commandBuilder
|
||||
if (commandBuilder.isAwaitingCharOrDigraphArgument()) {
|
||||
editorState.resetReplaceCharacter()
|
||||
}
|
||||
if (commandBuilder.isAtDefaultState) {
|
||||
val register = injector.registerGroup
|
||||
if (register.currentRegister == register.defaultRegister) {
|
||||
var indicateError = true
|
||||
if (key.keyCode == KeyEvent.VK_ESCAPE) {
|
||||
val executed = arrayOf<Boolean?>(null)
|
||||
injector.actionExecutor.executeCommand(
|
||||
editor,
|
||||
{ executed[0] = injector.actionExecutor.executeEsc(context) },
|
||||
"",
|
||||
null,
|
||||
)
|
||||
indicateError = !executed[0]!!
|
||||
}
|
||||
if (indicateError) {
|
||||
injector.messages.indicateError()
|
||||
}
|
||||
}
|
||||
}
|
||||
reset(editor)
|
||||
public fun setBadCommand(editor: VimEditor, keyState: KeyHandlerState) {
|
||||
onUnknownKey(editor, keyState)
|
||||
injector.messages.indicateError()
|
||||
}
|
||||
|
||||
private fun isCommandCountKey(chKey: Char, editorState: VimStateMachine): Boolean {
|
||||
// Make sure to avoid handling '0' as the start of a count.
|
||||
val commandBuilder = editorState.commandBuilder
|
||||
val notRegisterPendingCommand = editorState.mode is Mode.NORMAL && !editorState.isRegisterPending
|
||||
val visualMode = editorState.mode is Mode.VISUAL && !editorState.isRegisterPending
|
||||
val opPendingMode = editorState.mode is Mode.OP_PENDING
|
||||
|
||||
if (notRegisterPendingCommand || visualMode || opPendingMode) {
|
||||
if (commandBuilder.isExpectingCount && Character.isDigit(chKey) && (commandBuilder.count > 0 || chKey != '0')) {
|
||||
LOG.debug("This is a command key count")
|
||||
return true
|
||||
}
|
||||
}
|
||||
LOG.debug("This is NOT a command key count")
|
||||
return false
|
||||
public fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode, keyState: KeyHandlerState): Boolean {
|
||||
return isOperatorPending(mode, keyState) && keyState.commandBuilder.isDuplicateOperatorKeyStroke(key)
|
||||
}
|
||||
|
||||
private fun isDeleteCommandCountKey(key: KeyStroke, editorState: VimStateMachine): Boolean {
|
||||
// See `:help N<Del>`
|
||||
val commandBuilder = editorState.commandBuilder
|
||||
val isDeleteCommandKeyCount =
|
||||
(editorState.mode is Mode.NORMAL || editorState.mode is Mode.VISUAL || editorState.mode is Mode.OP_PENDING) &&
|
||||
commandBuilder.isExpectingCount && commandBuilder.count > 0 && key.keyCode == KeyEvent.VK_DELETE
|
||||
|
||||
LOG.debug { "This is a delete command key count: $isDeleteCommandKeyCount" }
|
||||
return isDeleteCommandKeyCount
|
||||
}
|
||||
|
||||
private fun isEditorReset(key: KeyStroke, editorState: VimStateMachine): Boolean {
|
||||
val editorReset = editorState.mode is Mode.NORMAL && key.isCloseKeyStroke()
|
||||
LOG.debug { "This is editor reset: $editorReset" }
|
||||
return editorReset
|
||||
}
|
||||
|
||||
private fun isSelectRegister(key: KeyStroke, editorState: VimStateMachine): Boolean {
|
||||
if (editorState.mode !is Mode.NORMAL && editorState.mode !is Mode.VISUAL) {
|
||||
return false
|
||||
}
|
||||
return if (editorState.isRegisterPending) {
|
||||
true
|
||||
} else {
|
||||
key.keyChar == '"' && !editorState.isOperatorPending && editorState.commandBuilder.expectedArgumentType == null
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSelectRegister(vimStateMachine: VimStateMachine, chKey: Char) {
|
||||
LOG.trace("Handle select register")
|
||||
vimStateMachine.resetRegisterPending()
|
||||
if (injector.registerGroup.isValid(chKey)) {
|
||||
LOG.trace("Valid register")
|
||||
vimStateMachine.commandBuilder.pushCommandPart(chKey)
|
||||
} else {
|
||||
LOG.trace("Invalid register, set command state to BAD_COMMAND")
|
||||
vimStateMachine.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
|
||||
}
|
||||
}
|
||||
|
||||
private fun isExpectingCharArgument(commandBuilder: CommandBuilder): Boolean {
|
||||
val expectingCharArgument = commandBuilder.expectedArgumentType === Argument.Type.CHARACTER
|
||||
LOG.debug { "Expecting char argument: $expectingCharArgument" }
|
||||
return expectingCharArgument
|
||||
}
|
||||
|
||||
private fun handleCharArgument(key: KeyStroke, chKey: Char, vimStateMachine: VimStateMachine) {
|
||||
var mutableChKey = chKey
|
||||
LOG.trace("Handling char argument")
|
||||
// We are expecting a character argument - is this a regular character the user typed?
|
||||
// Some special keys can be handled as character arguments - let's check for them here.
|
||||
if (mutableChKey.code == 0) {
|
||||
when (key.keyCode) {
|
||||
KeyEvent.VK_TAB -> mutableChKey = '\t'
|
||||
KeyEvent.VK_ENTER -> mutableChKey = '\n'
|
||||
}
|
||||
}
|
||||
val commandBuilder = vimStateMachine.commandBuilder
|
||||
if (mutableChKey.code != 0) {
|
||||
LOG.trace("Add character argument to the current command")
|
||||
// Create the character argument, add it to the current command, and signal we are ready to process the command
|
||||
commandBuilder.completeCommandPart(Argument(mutableChKey))
|
||||
} else {
|
||||
LOG.trace("This is not a valid character argument. Set command state to BAD_COMMAND")
|
||||
// Oops - this isn't a valid character argument
|
||||
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
|
||||
}
|
||||
vimStateMachine.resetReplaceCharacter()
|
||||
}
|
||||
|
||||
private fun handleDigraph(
|
||||
editor: VimEditor,
|
||||
key: KeyStroke,
|
||||
context: ExecutionContext,
|
||||
editorState: VimStateMachine,
|
||||
): Boolean {
|
||||
LOG.debug("Handling digraph")
|
||||
// Support starting a digraph/literal sequence if the operator accepts one as an argument, e.g. 'r' or 'f'.
|
||||
// Normally, we start the sequence (in Insert or CmdLine mode) through a VimAction that can be mapped. Our
|
||||
// VimActions don't work as arguments for operators, so we have to special case here. Helpfully, Vim appears to
|
||||
// hardcode the shortcuts, and doesn't support mapping, so everything works nicely.
|
||||
val commandBuilder = editorState.commandBuilder
|
||||
if (commandBuilder.expectedArgumentType == Argument.Type.DIGRAPH) {
|
||||
LOG.trace("Expected argument is digraph")
|
||||
if (editorState.digraphSequence.isDigraphStart(key)) {
|
||||
editorState.startDigraphSequence()
|
||||
editorState.commandBuilder.addKey(key)
|
||||
return true
|
||||
}
|
||||
if (editorState.digraphSequence.isLiteralStart(key)) {
|
||||
editorState.startLiteralSequence()
|
||||
editorState.commandBuilder.addKey(key)
|
||||
return true
|
||||
}
|
||||
}
|
||||
val res = editorState.processDigraphKey(key, editor)
|
||||
if (injector.exEntryPanel.isActive()) {
|
||||
when (res.result) {
|
||||
DigraphResult.RES_HANDLED -> setPromptCharacterEx(if (commandBuilder.isPuttingLiteral()) '^' else key.keyChar)
|
||||
DigraphResult.RES_DONE, DigraphResult.RES_BAD -> if (key.keyCode == KeyEvent.VK_C && key.modifiers and InputEvent.CTRL_DOWN_MASK != 0) {
|
||||
return false
|
||||
} else {
|
||||
injector.exEntryPanel.clearCurrentAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
when (res.result) {
|
||||
DigraphResult.RES_HANDLED -> {
|
||||
editorState.commandBuilder.addKey(key)
|
||||
return true
|
||||
}
|
||||
DigraphResult.RES_DONE -> {
|
||||
if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
|
||||
commandBuilder.fallbackToCharacterArgument()
|
||||
}
|
||||
val stroke = res.stroke ?: return false
|
||||
editorState.commandBuilder.addKey(key)
|
||||
handleKey(editor, stroke, context)
|
||||
return true
|
||||
}
|
||||
DigraphResult.RES_BAD -> {
|
||||
// BAD is an error. We were expecting a valid character, and we didn't get it.
|
||||
if (commandBuilder.expectedArgumentType != null) {
|
||||
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
|
||||
}
|
||||
return true
|
||||
}
|
||||
DigraphResult.RES_UNHANDLED -> {
|
||||
// UNHANDLED means the keystroke made no sense in the context of a digraph, but isn't an error in the current
|
||||
// state. E.g. waiting for {char} <BS> {char}. Let the key handler have a go at it.
|
||||
if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
|
||||
commandBuilder.fallbackToCharacterArgument()
|
||||
handleKey(editor, key, context)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
public fun isOperatorPending(mode: Mode, keyState: KeyHandlerState): Boolean {
|
||||
return mode is Mode.OP_PENDING && !keyState.commandBuilder.isEmpty
|
||||
}
|
||||
|
||||
private fun executeCommand(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
editorState: VimStateMachine,
|
||||
keyState: KeyHandlerState,
|
||||
) {
|
||||
LOG.trace("Command execution")
|
||||
val command = editorState.commandBuilder.buildCommand()
|
||||
val command = keyState.commandBuilder.buildCommand()
|
||||
val operatorArguments = OperatorArguments(
|
||||
editorState.mappingState.mappingMode == MappingMode.OP_PENDING,
|
||||
editor.mode is Mode.OP_PENDING,
|
||||
command.rawCount,
|
||||
editorState.mode,
|
||||
)
|
||||
|
||||
// If we were in "operator pending" mode, reset back to normal mode.
|
||||
editorState.resetOpPending()
|
||||
editor.resetOpPending()
|
||||
|
||||
// Save off the command we are about to execute
|
||||
editorState.executingCommand = command
|
||||
@ -429,13 +223,13 @@ public class KeyHandler {
|
||||
if (type.isWrite) {
|
||||
if (!editor.isWritable()) {
|
||||
injector.messages.indicateError()
|
||||
reset(editor)
|
||||
reset(keyState, editorState.mode)
|
||||
LOG.warn("File is not writable")
|
||||
return
|
||||
}
|
||||
}
|
||||
if (injector.application.isMainThread()) {
|
||||
val action: Runnable = ActionRunner(editor, context, command, operatorArguments)
|
||||
val action: Runnable = ActionRunner(editor, context, command, keyState, operatorArguments)
|
||||
val cmdAction = command.action
|
||||
val name = cmdAction.id
|
||||
if (type.isWrite) {
|
||||
@ -448,127 +242,6 @@ public class KeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCommandNode(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
key: KeyStroke,
|
||||
node: CommandNode<LazyVimCommand>,
|
||||
editorState: VimStateMachine,
|
||||
) {
|
||||
LOG.trace("Handle command node")
|
||||
// The user entered a valid command. Create the command and add it to the stack.
|
||||
val action = node.actionHolder.instance
|
||||
val commandBuilder = editorState.commandBuilder
|
||||
val expectedArgumentType = commandBuilder.expectedArgumentType
|
||||
commandBuilder.pushCommandPart(action)
|
||||
if (!checkArgumentCompatibility(expectedArgumentType, action)) {
|
||||
LOG.trace("Return from command node handling")
|
||||
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
|
||||
return
|
||||
}
|
||||
if (action.argumentType == null || stopMacroRecord(node, editorState)) {
|
||||
LOG.trace("Set command state to READY")
|
||||
commandBuilder.commandState = CurrentCommandState.READY
|
||||
} else {
|
||||
LOG.trace("Set waiting for the argument")
|
||||
val argumentType = action.argumentType
|
||||
startWaitingForArgument(editor, context, key.keyChar, action, argumentType!!, editorState)
|
||||
partialReset(editor)
|
||||
}
|
||||
|
||||
// TODO In the name of God, get rid of EX_STRING, FLAG_COMPLETE_EX and all the related staff
|
||||
if (expectedArgumentType === Argument.Type.EX_STRING && action.flags.contains(CommandFlags.FLAG_COMPLETE_EX)) {
|
||||
/* The only action that implements FLAG_COMPLETE_EX is ProcessExEntryAction.
|
||||
* When pressing ':', ExEntryAction is chosen as the command. Since it expects no arguments, it is invoked and
|
||||
calls ProcessGroup#startExCommand, pushes CMD_LINE mode, and the action is popped. The ex handler will push
|
||||
the final <CR> through handleKey, which chooses ProcessExEntryAction. Because we're not expecting EX_STRING,
|
||||
this branch does NOT fire, and ProcessExEntryAction handles the ex cmd line entry.
|
||||
* When pressing '/' or '?', SearchEntry(Fwd|Rev)Action is chosen as the command. This expects an argument of
|
||||
EX_STRING, so startWaitingForArgument calls ProcessGroup#startSearchCommand. The ex handler pushes the final
|
||||
<CR> through handleKey, which chooses ProcessExEntryAction, and we hit this branch. We don't invoke
|
||||
ProcessExEntryAction, but pop it, set the search text as an argument on SearchEntry(Fwd|Rev)Action and invoke
|
||||
that instead.
|
||||
* When using '/' or '?' as part of a motion (e.g. "d/foo"), the above happens again, and all is good. Because
|
||||
the text has been applied as an argument on the last command, '.' will correctly repeat it.
|
||||
|
||||
It's hard to see how to improve this. Removing EX_STRING means starting ex input has to happen in ExEntryAction
|
||||
and SearchEntry(Fwd|Rev)Action, and the ex command invoked in ProcessExEntryAction, but that breaks any initial
|
||||
operator, which would be invoked first (e.g. 'd' in "d/foo").
|
||||
*/
|
||||
LOG.trace("Processing ex_string")
|
||||
val text = injector.processGroup.endSearchCommand()
|
||||
commandBuilder.popCommandPart() // Pop ProcessExEntryAction
|
||||
commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action
|
||||
editorState.mode = editorState.mode.returnTo()
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopMacroRecord(node: CommandNode<LazyVimCommand>, editorState: VimStateMachine): Boolean {
|
||||
// TODO
|
||||
// return editorState.isRecording && node.actionHolder.getInstance() is ToggleRecordingAction
|
||||
return editorState.isRecording && node.actionHolder.instance.id == "VimToggleRecordingAction"
|
||||
}
|
||||
|
||||
private fun startWaitingForArgument(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
key: Char,
|
||||
action: EditorActionHandlerBase,
|
||||
argument: Argument.Type,
|
||||
editorState: VimStateMachine,
|
||||
) {
|
||||
val commandBuilder = editorState.commandBuilder
|
||||
when (argument) {
|
||||
Argument.Type.MOTION -> {
|
||||
if (editorState.isDotRepeatInProgress && argumentCaptured != null) {
|
||||
commandBuilder.completeCommandPart(argumentCaptured!!)
|
||||
}
|
||||
editorState.mode = Mode.OP_PENDING(editorState.mode.returnTo)
|
||||
}
|
||||
|
||||
Argument.Type.DIGRAPH -> // Command actions represent the completion of a command. Showcmd relies on this - if the action represents a
|
||||
// part of a command, the showcmd output is reset part way through. This means we need to special case entering
|
||||
// digraph/literal input mode. We have an action that takes a digraph as an argument, and pushes it back through
|
||||
// the key handler when it's complete.
|
||||
|
||||
// TODO
|
||||
// if (action is InsertCompletedDigraphAction) {
|
||||
if (action.id == "VimInsertCompletedDigraphAction") {
|
||||
editorState.startDigraphSequence()
|
||||
setPromptCharacterEx('?')
|
||||
} else if (action.id == "VimInsertCompletedLiteralAction") {
|
||||
editorState.startLiteralSequence()
|
||||
setPromptCharacterEx('^')
|
||||
}
|
||||
|
||||
Argument.Type.EX_STRING -> {
|
||||
// The current Command expects an EX_STRING argument. E.g. SearchEntry(Fwd|Rev)Action. This won't execute until
|
||||
// state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument.
|
||||
injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key)
|
||||
commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
|
||||
val currentMode = editorState.mode
|
||||
check(currentMode is ReturnableFromCmd) { "Cannot enable command line mode $currentMode" }
|
||||
editorState.mode = Mode.CMD_LINE(currentMode)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
// Another special case. Force a mode change to update the caret shape
|
||||
// This was a typed solution
|
||||
// if (action is ChangeCharacterAction || action is ChangeVisualCharacterAction)
|
||||
if (action.id == "VimChangeCharacterAction" || action.id == "VimChangeVisualCharacterAction") {
|
||||
editorState.isReplaceCharacter = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkArgumentCompatibility(
|
||||
expectedArgumentType: Argument.Type?,
|
||||
action: EditorActionHandlerBase,
|
||||
): Boolean {
|
||||
return !(expectedArgumentType === Argument.Type.MOTION && action.type !== Command.Type.MOTION)
|
||||
}
|
||||
|
||||
/**
|
||||
* Partially resets the state of this handler. Resets the command count, clears the key list, resets the key tree
|
||||
* node to the root for the current mode we are in.
|
||||
@ -576,9 +249,7 @@ public class KeyHandler {
|
||||
* @param editor The editor to reset.
|
||||
*/
|
||||
public fun partialReset(editor: VimEditor) {
|
||||
val editorState = VimStateMachine.getInstance(editor)
|
||||
editorState.mappingState.resetMappingSequence()
|
||||
editorState.commandBuilder.resetInProgressCommandPart(getKeyRoot(editorState.mappingState.mappingMode))
|
||||
keyHandlerState.partialReset(editor.mode)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -587,15 +258,23 @@ public class KeyHandler {
|
||||
* @param editor The editor to reset.
|
||||
*/
|
||||
public fun reset(editor: VimEditor) {
|
||||
partialReset(editor)
|
||||
val editorState = VimStateMachine.getInstance(editor)
|
||||
editorState.commandBuilder.resetAll(getKeyRoot(editorState.mappingState.mappingMode))
|
||||
keyHandlerState.partialReset(editor.mode)
|
||||
keyHandlerState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode()))
|
||||
}
|
||||
|
||||
public fun reset(keyState: KeyHandlerState, mode: Mode) {
|
||||
keyHandlerState.partialReset(mode)
|
||||
keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode()))
|
||||
}
|
||||
|
||||
private fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand> {
|
||||
return injector.keyGroup.getKeyRoot(mappingMode)
|
||||
}
|
||||
|
||||
public fun updateState(keyState: KeyHandlerState) {
|
||||
this.keyHandlerState = keyState
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely resets the state of this handler. Resets the command mode to normal, resets, and clears the selected
|
||||
* register.
|
||||
@ -604,13 +283,13 @@ public class KeyHandler {
|
||||
*/
|
||||
public fun fullReset(editor: VimEditor) {
|
||||
injector.messages.clearError()
|
||||
VimStateMachine.getInstance(editor).reset()
|
||||
reset(editor)
|
||||
editor.resetState()
|
||||
reset(keyHandlerState, editor.mode)
|
||||
injector.registerGroupIfCreated?.resetRegister()
|
||||
editor.removeSelection()
|
||||
}
|
||||
|
||||
private fun setPromptCharacterEx(promptCharacter: Char) {
|
||||
public fun setPromptCharacterEx(promptCharacter: Char) {
|
||||
val exEntryPanel = injector.exEntryPanel
|
||||
if (exEntryPanel.isActive()) {
|
||||
exEntryPanel.setCurrentActionPromptCharacter(promptCharacter)
|
||||
@ -624,11 +303,12 @@ public class KeyHandler {
|
||||
val editor: VimEditor,
|
||||
val context: ExecutionContext,
|
||||
val cmd: Command,
|
||||
val keyState: KeyHandlerState,
|
||||
val operatorArguments: OperatorArguments,
|
||||
) : Runnable {
|
||||
override fun run() {
|
||||
val editorState = VimStateMachine.getInstance(editor)
|
||||
editorState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
|
||||
keyState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
|
||||
val register = cmd.register
|
||||
if (register != null) {
|
||||
injector.registerGroup.selectRegister(register)
|
||||
@ -653,21 +333,22 @@ public class KeyHandler {
|
||||
if (myMode is Mode.NORMAL && returnTo != null && !cmd.flags.contains(CommandFlags.FLAG_EXPECT_MORE)) {
|
||||
when (returnTo) {
|
||||
ReturnTo.INSERT -> {
|
||||
editorState.mode = Mode.INSERT
|
||||
editor.mode = Mode.INSERT
|
||||
}
|
||||
|
||||
ReturnTo.REPLACE -> {
|
||||
editorState.mode = Mode.REPLACE
|
||||
editor.mode = Mode.REPLACE
|
||||
}
|
||||
}
|
||||
}
|
||||
if (editorState.commandBuilder.isDone()) {
|
||||
getInstance().reset(editor)
|
||||
if (keyState.commandBuilder.isDone()) {
|
||||
getInstance().reset(keyState, editorState.mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public val lock: Any = Object()
|
||||
private val LOG: VimLogger = vimLogger<KeyHandler>()
|
||||
|
||||
internal fun <T> isPrefix(list1: List<T>, list2: List<T>): Boolean {
|
||||
@ -687,4 +368,113 @@ public class KeyHandler {
|
||||
@JvmStatic
|
||||
public fun getInstance(): KeyHandler = instance
|
||||
}
|
||||
|
||||
public data class MutableBoolean(public var value: Boolean)
|
||||
}
|
||||
|
||||
/**
|
||||
* This class was created to manage Fleet input processing.
|
||||
* Fleet needs to synchronously determine if the key will be handled by the plugin or should be passed elsewhere.
|
||||
* The key processing itself will be executed asynchronously at a later time.
|
||||
*/
|
||||
public sealed interface KeyProcessResult {
|
||||
/**
|
||||
* Key input that is not recognized by IdeaVim and should be passed to IDE.
|
||||
*/
|
||||
public object Unknown: KeyProcessResult
|
||||
|
||||
/**
|
||||
* Key input that is recognized by IdeaVim and can be executed.
|
||||
* Key handling is a two-step process:
|
||||
* 1. Determine if the key should be processed and how (is it a command, mapping, or something else).
|
||||
* 2. Execute the recognized command.
|
||||
* This class should be returned after the first step is complete.
|
||||
* It will continue the key handling and finish the process.
|
||||
*/
|
||||
public class Executable(
|
||||
private val originalState: KeyHandlerState,
|
||||
private val preProcessState: KeyHandlerState,
|
||||
private val processing: KeyProcessing,
|
||||
): KeyProcessResult {
|
||||
|
||||
public companion object {
|
||||
private val logger = vimLogger<KeyProcessResult>()
|
||||
}
|
||||
|
||||
public fun execute(editor: VimEditor, context: ExecutionContext) {
|
||||
synchronized(KeyHandler.lock) {
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
if (keyHandler.keyHandlerState != originalState) {
|
||||
logger.error("Unexpected editor state. Aborting command execution.")
|
||||
}
|
||||
processing(preProcessState, editor, context)
|
||||
keyHandler.updateState(preProcessState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class serves as a wrapper around the key handling algorithm and should be used with care:
|
||||
* We process keys in two steps:
|
||||
* 1. We first determine if IdeaVim can handle the key or not. At this stage, you should avoid modifying anything
|
||||
* except state: KeyHandlerState. This is because it is not guaranteed that the key will be handled by IdeaVim at
|
||||
* all, and we want to minimize possible side effects.
|
||||
* 2. If it's confirmed that the key will be handled, add all the key handling processes as execution steps,
|
||||
* slated for later execution.
|
||||
*
|
||||
* Please note that execution steps could depend on KeyHandlerState, and because of that we cannot change the state
|
||||
* after adding an execution step. This is because an execution step does not anticipate changes to the state.
|
||||
* If there's need to alter the state following any of the execution steps, wrap the state modification as an
|
||||
* execution step. This will allow state modification to occur later rather than immediately.
|
||||
*/
|
||||
public abstract class KeyProcessResultBuilder {
|
||||
public abstract val state: KeyHandlerState
|
||||
protected val processings: MutableList<KeyProcessing> = mutableListOf()
|
||||
public var onFinish: (() -> Unit)? = null // FIXME I'm a dirty hack to support recursion counter
|
||||
|
||||
public fun addExecutionStep(keyProcessing: KeyProcessing) {
|
||||
processings.add(keyProcessing)
|
||||
}
|
||||
|
||||
public abstract fun build(): KeyProcessResult
|
||||
}
|
||||
|
||||
// Works with existing state and modifies it during execution
|
||||
// It's the way IdeaVim worked for the long time and for this class we do not create
|
||||
// unnecessary objects and assume that the code will be executed immediately
|
||||
public class SynchronousKeyProcessBuilder(public override val state: KeyHandlerState): KeyProcessResultBuilder() {
|
||||
public override fun build(): KeyProcessResult {
|
||||
return Executable(state, state) { keyHandlerState, vimEditor, executionContext ->
|
||||
try {
|
||||
for (processing in processings) {
|
||||
processing(keyHandlerState, vimEditor, executionContext)
|
||||
}
|
||||
} finally {
|
||||
onFinish?.let { it() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Works with a clone of current state, nothing is modified during the builder work (key processing)
|
||||
// The new state will be applied later, when we run Executable.execute() (it may not be run at all)
|
||||
public class AsyncKeyProcessBuilder(originalState: KeyHandlerState): KeyProcessResultBuilder() {
|
||||
private val originalState: KeyHandlerState = KeyHandler.getInstance().keyHandlerState
|
||||
public override val state: KeyHandlerState = originalState.clone()
|
||||
|
||||
public override fun build(): KeyProcessResult {
|
||||
return Executable(originalState, state) { keyHandlerState, vimEditor, executionContext ->
|
||||
try {
|
||||
for (processing in processings) {
|
||||
processing(keyHandlerState, vimEditor, executionContext)
|
||||
}
|
||||
} finally {
|
||||
onFinish?.let { it() }
|
||||
KeyHandler.getInstance().updateState(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias KeyProcessing = (KeyHandlerState, VimEditor, ExecutionContext) -> Unit
|
||||
|
@ -8,11 +8,11 @@
|
||||
|
||||
package com.maddyhome.idea.vim.action
|
||||
|
||||
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
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import java.io.InputStream
|
||||
@ -44,3 +44,6 @@ public interface CommandProvider {
|
||||
?: throw RuntimeException("Failed to fetch ex commands from ${javaClass.name}")
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
public data class CommandBean(val keys: String, val `class`: String, val modes: String)
|
||||
|
@ -13,11 +13,31 @@ import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.vimscript.model.LazyInstance
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class LazyVimCommand(
|
||||
public open class LazyVimCommand(
|
||||
public val keys: Set<List<KeyStroke>>,
|
||||
public val modes: Set<MappingMode>,
|
||||
className: String,
|
||||
classLoader: ClassLoader,
|
||||
) : LazyInstance<EditorActionHandlerBase>(className, classLoader) {
|
||||
public val actionId: String = EditorActionHandlerBase.getActionId(className)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as LazyVimCommand
|
||||
|
||||
if (keys != other.keys) return false
|
||||
if (modes != other.modes) return false
|
||||
if (actionId != other.actionId) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = keys.hashCode()
|
||||
result = 31 * result + modes.hashCode()
|
||||
result = 31 * result + actionId.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -27,7 +27,8 @@ public class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
// The converted digraph character has been captured as an argument, push it back through key handler
|
||||
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
|
||||
KeyHandler.getInstance().handleKey(editor, keyStroke, context)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ public class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
// The converted literal character has been captured as an argument, push it back through key handler
|
||||
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
|
||||
KeyHandler.getInstance().handleKey(editor, keyStroke, context)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,12 @@ public class ToggleRecordingAction : VimActionHandler.SingleExecution() {
|
||||
override val argumentType: Argument.Type = Argument.Type.CHARACTER
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
return if (!editor.vimStateMachine.isRecording) {
|
||||
return if (!injector.registerGroup.isRecording) {
|
||||
val argument = cmd.argument ?: return false
|
||||
val reg = argument.character
|
||||
injector.registerGroup.startRecording(editor, reg)
|
||||
injector.registerGroup.startRecording(reg)
|
||||
} else {
|
||||
injector.registerGroup.finishRecording(editor)
|
||||
injector.registerGroup.finishRecording()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ public class SelectToggleVisualMode : VimActionHandler.SingleExecution() {
|
||||
val commandState = editor.vimStateMachine
|
||||
val myMode = commandState.mode
|
||||
if (myMode is com.maddyhome.idea.vim.state.mode.Mode.VISUAL) {
|
||||
commandState.setSelectMode(myMode.selectionType)
|
||||
editor.setSelectMode(myMode.selectionType)
|
||||
if (myMode.selectionType != SelectionType.LINE_WISE) {
|
||||
editor.nativeCarets().forEach {
|
||||
if (it.offset.point + injector.visualMotionGroup.selectionAdj == it.selectionEnd) {
|
||||
@ -54,7 +54,7 @@ public class SelectToggleVisualMode : VimActionHandler.SingleExecution() {
|
||||
}
|
||||
}
|
||||
} else if (myMode is com.maddyhome.idea.vim.state.mode.Mode.SELECT) {
|
||||
commandState.pushVisualMode(myMode.selectionType)
|
||||
editor.pushVisualMode(myMode.selectionType)
|
||||
if (myMode.selectionType != SelectionType.LINE_WISE) {
|
||||
editor.nativeCarets().forEach {
|
||||
if (it.offset.point == it.selectionEnd && it.visualLineStart <= it.offset.point - injector.visualMotionGroup.selectionAdj) {
|
||||
|
@ -37,7 +37,7 @@ public class VisualSelectPreviousAction : VimActionHandler.SingleExecution() {
|
||||
|
||||
if (caretToSelectionInfo.any { it.second.start == null || it.second.end == null }) return false
|
||||
|
||||
editor.vimStateMachine.mode = com.maddyhome.idea.vim.state.mode.Mode.VISUAL(selectionType)
|
||||
editor.mode = com.maddyhome.idea.vim.state.mode.Mode.VISUAL(selectionType)
|
||||
|
||||
for ((caret, selectionInfo) in caretToSelectionInfo) {
|
||||
val startOffset = editor.bufferPositionToOffset(selectionInfo.start!!)
|
||||
|
@ -53,7 +53,7 @@ private fun swapVisualSelections(editor: VimEditor): Boolean {
|
||||
editor.vimLastSelectionType = mode.selectionType
|
||||
injector.markService.setVisualSelectionMarks(primaryCaret, TextRange(vimSelectionStart, primaryCaret.offset.point))
|
||||
|
||||
editor.vimStateMachine.mode = mode.copy(selectionType = lastSelectionType)
|
||||
editor.mode = mode.copy(selectionType = lastSelectionType)
|
||||
primaryCaret.vimSetSelection(lastVisualRange.startOffset, lastVisualRange.endOffset, true)
|
||||
|
||||
injector.scroll.scrollCaretIntoView(editor)
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.api
|
||||
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
@ -65,9 +66,9 @@ public interface VimChangeGroup {
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean
|
||||
|
||||
public fun processKey(editor: VimEditor, context: ExecutionContext, key: KeyStroke): Boolean
|
||||
public fun processKey(editor: VimEditor, key: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
|
||||
|
||||
public fun processKeyInSelectMode(editor: VimEditor, context: ExecutionContext, key: KeyStroke): Boolean
|
||||
public fun processKeyInSelectMode(editor: VimEditor, key: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
|
||||
|
||||
public fun deleteLine(editor: VimEditor, caret: VimCaret, count: Int, operatorArguments: OperatorArguments): Boolean
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
package com.maddyhome.idea.vim.api
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
@ -36,7 +37,6 @@ import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGI
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.toReturnTo
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.awt.event.KeyEvent
|
||||
@ -439,7 +439,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
}
|
||||
val cmd = state.executingCommand
|
||||
if (cmd != null && state.isDotRepeatInProgress) {
|
||||
state.mode = mode
|
||||
editor.mode = mode
|
||||
if (mode == Mode.REPLACE) {
|
||||
editor.insertMode = false
|
||||
}
|
||||
@ -465,7 +465,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
if (mode == Mode.REPLACE) {
|
||||
editor.insertMode = true
|
||||
}
|
||||
state.mode = Mode.NORMAL()
|
||||
editor.mode = Mode.NORMAL()
|
||||
} else {
|
||||
lastInsert = cmd
|
||||
strokes.clear()
|
||||
@ -480,7 +480,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
vimDocument!!.addChangeListener(myChangeListener)
|
||||
oldOffset = editor.currentCaret().offset.point
|
||||
editor.insertMode = mode == Mode.INSERT
|
||||
state.mode = mode
|
||||
editor.mode = mode
|
||||
}
|
||||
notifyListeners(editor)
|
||||
}
|
||||
@ -560,8 +560,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
|
||||
// The change pos '.' mark is the offset AFTER processing escape, and after switching to overtype
|
||||
markGroup.setMark(editor, MARK_CHANGE_POS)
|
||||
getInstance(editor).mode = Mode.NORMAL()
|
||||
editor.vimStateMachine.mode = Mode.NORMAL()
|
||||
editor.mode = Mode.NORMAL()
|
||||
}
|
||||
|
||||
private fun updateLastInsertedTextRegister() {
|
||||
@ -640,7 +639,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
* @param editor The editor to put into NORMAL mode for one command
|
||||
*/
|
||||
override fun processSingleCommand(editor: VimEditor) {
|
||||
getInstance(editor).mode = Mode.NORMAL(returnTo = editor.mode.toReturnTo)
|
||||
editor.mode = Mode.NORMAL(returnTo = editor.mode.toReturnTo)
|
||||
clearStrokes(editor)
|
||||
}
|
||||
|
||||
@ -711,24 +710,23 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
* This processes all "regular" keystrokes entered while in insert/replace mode
|
||||
*
|
||||
* @param editor The editor the character was typed into
|
||||
* @param context The data context
|
||||
* @param key The user entered keystroke
|
||||
* @return true if this was a regular character, false if not
|
||||
*/
|
||||
override fun processKey(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
key: KeyStroke,
|
||||
processResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
): Boolean {
|
||||
logger.debug { "processKey($key)" }
|
||||
if (key.keyChar != KeyEvent.CHAR_UNDEFINED) {
|
||||
type(editor, context, key.keyChar)
|
||||
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, key.keyChar) }
|
||||
return true
|
||||
}
|
||||
|
||||
// Shift-space
|
||||
if (key.keyCode == 32 && key.modifiers and KeyEvent.SHIFT_DOWN_MASK != 0) {
|
||||
type(editor, context, ' ')
|
||||
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, ' ') }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -736,16 +734,18 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
|
||||
override fun processKeyInSelectMode(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
key: KeyStroke,
|
||||
processResultBuilder: KeyProcessResult.KeyProcessResultBuilder
|
||||
): Boolean {
|
||||
var res: Boolean
|
||||
SelectionVimListenerSuppressor.lock().use {
|
||||
res = processKey(editor, context, key)
|
||||
editor.exitSelectModeNative(false)
|
||||
KeyHandler.getInstance().reset(editor)
|
||||
if (isPrintableChar(key.keyChar) || activeTemplateWithLeftRightMotion(editor, key)) {
|
||||
injector.changeGroup.insertBeforeCursor(editor, context)
|
||||
res = processKey(editor, key, processResultBuilder)
|
||||
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext ->
|
||||
lambdaEditor.exitSelectModeNative(false)
|
||||
KeyHandler.getInstance().reset(lambdaEditor)
|
||||
if (isPrintableChar(key.keyChar) || activeTemplateWithLeftRightMotion(lambdaEditor, key)) {
|
||||
injector.changeGroup.insertBeforeCursor(lambdaEditor, lambdaContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
|
@ -14,8 +14,13 @@ import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.common.Offset
|
||||
import com.maddyhome.idea.vim.common.Pointer
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
|
||||
import com.maddyhome.idea.vim.impl.state.toMappingMode
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||
|
||||
/**
|
||||
* Every line in [VimEditor] ends with a new line TODO <- this is probably not true already
|
||||
@ -124,6 +129,24 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
* ([VimVisualPosition] should be phased out if possible, as it is an IntelliJ concept, not a Vim concept.)
|
||||
*/
|
||||
public interface VimEditor {
|
||||
public var mode: Mode
|
||||
get() = vimStateMachine.mode
|
||||
set(value) {
|
||||
if (vimStateMachine.mode == value) return
|
||||
|
||||
val oldValue = vimStateMachine.mode
|
||||
(vimStateMachine as VimStateMachineImpl).mode = value
|
||||
injector.listenersNotifier.notifyModeChanged(this, oldValue)
|
||||
}
|
||||
|
||||
public var isReplaceCharacter: Boolean
|
||||
get() = vimStateMachine.isReplaceCharacter
|
||||
set(value) {
|
||||
if (value != vimStateMachine.isReplaceCharacter) {
|
||||
(vimStateMachine as VimStateMachineImpl).isReplaceCharacter = value
|
||||
injector.listenersNotifier.notifyIsReplaceCharChanged(this)
|
||||
}
|
||||
}
|
||||
|
||||
public val lfMakesNewLine: Boolean
|
||||
public var vimChangeActionSwitchMode: Mode?
|
||||
@ -196,9 +219,6 @@ public interface VimEditor {
|
||||
shiftType: LineDeleteShift,
|
||||
): Pair<Pair<Offset, Offset>, LineDeleteShift>?
|
||||
|
||||
public fun updateCaretsVisualAttributes()
|
||||
public fun updateCaretsVisualPosition()
|
||||
|
||||
public fun offsetToBufferPosition(offset: Int): BufferPosition
|
||||
public fun bufferPositionToOffset(position: BufferPosition): Int
|
||||
|
||||
@ -276,6 +296,29 @@ public interface VimEditor {
|
||||
* instance and need to search for a new version.
|
||||
*/
|
||||
public fun <T : ImmutableVimCaret> findLastVersionOfCaret(caret: T): T?
|
||||
|
||||
/**
|
||||
* Resets the command, mode, visual mode, and mapping mode to initial values.
|
||||
*/
|
||||
public fun resetState() {
|
||||
mode = Mode.NORMAL()
|
||||
vimStateMachine.executingCommand = null
|
||||
vimStateMachine.digraphSequence.reset()
|
||||
vimStateMachine.commandBuilder.resetInProgressCommandPart(
|
||||
injector.keyGroup.getKeyRoot(mode.toMappingMode())
|
||||
)
|
||||
}
|
||||
|
||||
public fun resetOpPending() {
|
||||
if (this.mode is Mode.OP_PENDING) {
|
||||
val returnTo = this.mode.returnTo
|
||||
mode = when (returnTo) {
|
||||
ReturnTo.INSERT -> Mode.INSERT
|
||||
ReturnTo.REPLACE -> Mode.INSERT
|
||||
null -> Mode.NORMAL()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface MutableVimEditor : VimEditor {
|
||||
|
@ -55,4 +55,8 @@ public interface VimEditorGroup {
|
||||
* hidden editors that are used to handle requests from Code With Me guests.
|
||||
*/
|
||||
public fun getEditors(buffer: VimDocument): Collection<VimEditor>
|
||||
|
||||
// TODO find a better place for methods below. Maybe make CaretVisualAttributesHelper abstract?
|
||||
public fun updateCaretsVisualAttributes(editor: VimEditor)
|
||||
public fun updateCaretsVisualPosition(editor: VimEditor)
|
||||
}
|
||||
|
@ -17,8 +17,4 @@ public interface VimMessages {
|
||||
public fun message(key: String, vararg params: Any): String
|
||||
|
||||
public fun updateStatusBar(editor: VimEditor)
|
||||
|
||||
public fun showMode(editor: VimEditor?, msg: String) {
|
||||
showStatusBarMessage(editor, msg)
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.api
|
||||
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import javax.swing.KeyStroke
|
||||
@ -18,7 +19,7 @@ public interface VimProcessGroup {
|
||||
|
||||
public fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char)
|
||||
public fun endSearchCommand(): String
|
||||
public fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean
|
||||
public fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
|
||||
public fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
|
||||
public fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
|
||||
public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean
|
||||
|
@ -32,7 +32,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
|
||||
get() = if (exclusiveSelection) 0 else 1
|
||||
|
||||
override fun enterSelectMode(editor: VimEditor, subMode: SelectionType): Boolean {
|
||||
editor.vimStateMachine.setSelectMode(subMode)
|
||||
editor.setSelectMode(subMode)
|
||||
editor.forEachCaret { it.vimSelectionStart = it.vimLeadSelectionOffset }
|
||||
return true
|
||||
}
|
||||
@ -55,7 +55,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
|
||||
// Enable visual subMode
|
||||
if (rawCount > 0) {
|
||||
val primarySubMode = editor.primaryCaret().vimLastVisualOperatorRange?.type ?: selectionType
|
||||
editor.vimStateMachine.pushVisualMode(primarySubMode)
|
||||
editor.pushVisualMode(primarySubMode)
|
||||
|
||||
editor.forEachCaret {
|
||||
val range = it.vimLastVisualOperatorRange ?: VisualChange.default(selectionType)
|
||||
@ -71,7 +71,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
|
||||
it.vimLastColumn = intendedColumn
|
||||
}
|
||||
} else {
|
||||
editor.vimStateMachine.mode = Mode.VISUAL(
|
||||
editor.mode = Mode.VISUAL(
|
||||
selectionType,
|
||||
returnTo ?: editor.vimStateMachine.mode.returnTo
|
||||
)
|
||||
@ -90,7 +90,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
|
||||
check(mode is Mode.VISUAL)
|
||||
|
||||
// Update visual subMode with new sub subMode
|
||||
editor.vimStateMachine.mode = mode.copy(selectionType = selectionType)
|
||||
editor.mode = mode.copy(selectionType = selectionType)
|
||||
for (caret in editor.carets()) {
|
||||
if (!caret.isValid) continue
|
||||
caret.vimUpdateEditorSelection()
|
||||
@ -162,7 +162,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
|
||||
*/
|
||||
override fun enterVisualMode(editor: VimEditor, subMode: SelectionType?): Boolean {
|
||||
val autodetectedSubMode = subMode ?: autodetectVisualSubmode(editor)
|
||||
editor.vimStateMachine.mode = Mode.VISUAL(autodetectedSubMode)
|
||||
editor.mode = Mode.VISUAL(autodetectedSubMode)
|
||||
// editor.vimStateMachine.setMode(VimStateMachine.Mode.VISUAL, autodetectedSubMode)
|
||||
if (autodetectedSubMode == SelectionType.BLOCK_WISE) {
|
||||
editor.primaryCaret().run { vimSelectionStart = vimLeadSelectionOffset }
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
package com.maddyhome.idea.vim.api.stubs
|
||||
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimProcessGroupBase
|
||||
@ -36,7 +37,7 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean {
|
||||
override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,10 @@ import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.key.Node
|
||||
import com.maddyhome.idea.vim.key.RootNode
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.util.*
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class CommandBuilder(private var currentCommandPartNode: CommandPartNode<LazyVimCommand>) {
|
||||
private val commandParts = ArrayDeque<Command>()
|
||||
public class CommandBuilder(private var currentCommandPartNode: CommandPartNode<LazyVimCommand>): Cloneable {
|
||||
private var commandParts = ArrayDeque<Command>()
|
||||
private var keyList = mutableListOf<KeyStroke>()
|
||||
|
||||
public var commandState: CurrentCommandState = CurrentCommandState.NEW_COMMAND
|
||||
@ -66,7 +65,7 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode<
|
||||
|
||||
public fun popCommandPart(): Command {
|
||||
val command = commandParts.removeLast()
|
||||
expectedArgumentType = if (commandParts.size > 0) commandParts.peekLast().action.argumentType else null
|
||||
expectedArgumentType = if (commandParts.size > 0) commandParts.last().action.argumentType else null
|
||||
return command
|
||||
}
|
||||
|
||||
@ -107,7 +106,7 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode<
|
||||
|
||||
public fun isAwaitingCharOrDigraphArgument(): Boolean {
|
||||
if (commandParts.size == 0) return false
|
||||
val argumentType = commandParts.peekLast().action.argumentType
|
||||
val argumentType = commandParts.last().action.argumentType
|
||||
val awaiting = argumentType == Argument.Type.CHARACTER || argumentType == Argument.Type.DIGRAPH
|
||||
LOG.debug { "Awaiting char of digraph: $awaiting" }
|
||||
return awaiting
|
||||
@ -125,7 +124,7 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode<
|
||||
}
|
||||
|
||||
public fun isPuttingLiteral(): Boolean {
|
||||
return !commandParts.isEmpty() && commandParts.last.action.id == "VimInsertCompletedLiteralAction"
|
||||
return !commandParts.isEmpty() && commandParts.last().action.id == "VimInsertCompletedLiteralAction"
|
||||
}
|
||||
|
||||
public fun isDone(): Boolean {
|
||||
@ -133,21 +132,21 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode<
|
||||
}
|
||||
|
||||
public fun completeCommandPart(argument: Argument) {
|
||||
commandParts.peekLast().argument = argument
|
||||
commandParts.last().argument = argument
|
||||
commandState = CurrentCommandState.READY
|
||||
}
|
||||
|
||||
public fun isDuplicateOperatorKeyStroke(key: KeyStroke): Boolean {
|
||||
val action = commandParts.peekLast().action as? DuplicableOperatorAction
|
||||
val action = commandParts.last().action as? DuplicableOperatorAction
|
||||
return action?.duplicateWith == key.keyChar
|
||||
}
|
||||
|
||||
public fun hasCurrentCommandPartArgument(): Boolean {
|
||||
return commandParts.peek()?.argument != null
|
||||
return commandParts.firstOrNull()?.argument != null
|
||||
}
|
||||
|
||||
public fun buildCommand(): Command {
|
||||
if (commandParts.last.action.id == "VimInsertCompletedDigraphAction" || commandParts.last.action.id == "VimResetModeAction") {
|
||||
if (commandParts.last().action.id == "VimInsertCompletedDigraphAction" || commandParts.last().action.id == "VimResetModeAction") {
|
||||
expectedArgumentType = prevExpectedArgumentType
|
||||
prevExpectedArgumentType = null
|
||||
return commandParts.removeLast()
|
||||
@ -191,6 +190,45 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode<
|
||||
|
||||
@TestOnly
|
||||
public fun getCurrentTrie(): CommandPartNode<LazyVimCommand> = currentCommandPartNode
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as CommandBuilder
|
||||
|
||||
if (currentCommandPartNode != other.currentCommandPartNode) return false
|
||||
if (commandParts != other.commandParts) return false
|
||||
if (keyList != other.keyList) return false
|
||||
if (commandState != other.commandState) return false
|
||||
if (count != other.count) return false
|
||||
if (expectedArgumentType != other.expectedArgumentType) return false
|
||||
if (prevExpectedArgumentType != other.prevExpectedArgumentType) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = currentCommandPartNode.hashCode()
|
||||
result = 31 * result + commandParts.hashCode()
|
||||
result = 31 * result + keyList.hashCode()
|
||||
result = 31 * result + commandState.hashCode()
|
||||
result = 31 * result + count
|
||||
result = 31 * result + (expectedArgumentType?.hashCode() ?: 0)
|
||||
result = 31 * result + (prevExpectedArgumentType?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
public override fun clone(): CommandBuilder {
|
||||
val result = CommandBuilder(currentCommandPartNode)
|
||||
result.commandParts = ArrayDeque(commandParts)
|
||||
result.keyList = keyList.toMutableList()
|
||||
result.commandState = commandState
|
||||
result.count = count
|
||||
result.expectedArgumentType = expectedArgumentType
|
||||
result.prevExpectedArgumentType = prevExpectedArgumentType
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public companion object {
|
||||
private val LOG = vimLogger<CommandBuilder>()
|
||||
|
@ -9,6 +9,7 @@
|
||||
package com.maddyhome.idea.vim.command
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
@ -17,27 +18,35 @@ import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.trace
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.impl.state.toMappingMode
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.key.KeyMappingLayer
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.key.MappingInfoLayer
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public object MappingProcessor {
|
||||
public object MappingProcessor: KeyConsumer {
|
||||
|
||||
private val log = vimLogger<MappingProcessor>()
|
||||
|
||||
internal fun handleKeyMapping(
|
||||
editor: VimEditor,
|
||||
public override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
context: ExecutionContext,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
if (!allowKeyMappings) return false
|
||||
|
||||
log.debug("Start processing key mappings.")
|
||||
val keyState = keyProcessResultBuilder.state
|
||||
val commandState = editor.vimStateMachine
|
||||
val mappingState = commandState.mappingState
|
||||
val commandBuilder = commandState.commandBuilder
|
||||
val mappingState = keyState.mappingState
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
if (commandBuilder.isAwaitingCharOrDigraphArgument() ||
|
||||
commandBuilder.isBuildingMultiKeyCommand() ||
|
||||
isMappingDisabledForKey(key, commandState) ||
|
||||
isMappingDisabledForKey(key, keyState) ||
|
||||
commandState.isRegisterPending
|
||||
) {
|
||||
log.debug("Finish key processing, returning false")
|
||||
@ -48,32 +57,32 @@ public object MappingProcessor {
|
||||
// Save the unhandled keystrokes until we either complete or abandon the sequence.
|
||||
log.trace("Add key to mapping state")
|
||||
mappingState.addKey(key)
|
||||
val mapping = injector.keyGroup.getKeyMappingLayer(mappingState.mappingMode)
|
||||
log.trace { "Get keys for mapping mode. mode = " + mappingState.mappingMode }
|
||||
val mappingMode = editor.mode.toMappingMode()
|
||||
val mapping = injector.keyGroup.getKeyMappingLayer(mappingMode)
|
||||
log.trace { "Get keys for mapping mode. mode = $mappingMode" }
|
||||
|
||||
// Returns true if any of these methods handle the key. False means that the key is unrelated to mapping and should
|
||||
// be processed as normal.
|
||||
val mappingProcessed =
|
||||
handleUnfinishedMappingSequence(editor, mappingState, mapping, mappingCompleted) ||
|
||||
handleCompleteMappingSequence(editor, context, mappingState, mapping, key) ||
|
||||
handleAbandonedMappingSequence(editor, mappingState, context)
|
||||
handleUnfinishedMappingSequence(keyProcessResultBuilder, mapping, mappingCompleted) ||
|
||||
handleCompleteMappingSequence(keyProcessResultBuilder, mapping, key) ||
|
||||
handleAbandonedMappingSequence(keyProcessResultBuilder)
|
||||
log.debug { "Finish mapping processing. Return $mappingProcessed" }
|
||||
|
||||
return mappingProcessed
|
||||
}
|
||||
|
||||
private fun isMappingDisabledForKey(key: KeyStroke, vimStateMachine: VimStateMachine): Boolean {
|
||||
private fun isMappingDisabledForKey(key: KeyStroke, keyState: KeyHandlerState): Boolean {
|
||||
// "0" can be mapped, but the mapping isn't applied when entering a count. Other digits are always mapped, even when
|
||||
// entering a count.
|
||||
// See `:help :map-modes`
|
||||
val isMappingDisabled = key.keyChar == '0' && vimStateMachine.commandBuilder.count > 0
|
||||
val isMappingDisabled = key.keyChar == '0' && keyState.commandBuilder.count > 0
|
||||
log.debug { "Mapping disabled for key: $isMappingDisabled" }
|
||||
return isMappingDisabled
|
||||
}
|
||||
|
||||
private fun handleUnfinishedMappingSequence(
|
||||
editor: VimEditor,
|
||||
mappingState: MappingState,
|
||||
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
mapping: KeyMappingLayer,
|
||||
mappingCompleted: Boolean,
|
||||
): Boolean {
|
||||
@ -88,7 +97,7 @@ public object MappingProcessor {
|
||||
// mapping is a prefix, it will get evaluated when the next character is entered.
|
||||
// Note that currentlyUnhandledKeySequence is the same as the state after commandState.getMappingKeys().add(key). It
|
||||
// would be nice to tidy ths up
|
||||
if (!mapping.isPrefix(mappingState.keys)) {
|
||||
if (!mapping.isPrefix(processBuilder.state.mappingState.keys)) {
|
||||
log.debug("There are no mappings that start with the current sequence. Returning false.")
|
||||
return false
|
||||
}
|
||||
@ -97,6 +106,11 @@ public object MappingProcessor {
|
||||
// Every time a key is pressed and handled, the timer is stopped. E.g. if there is a mapping for "dweri", and the
|
||||
// user has typed "dw" wait for the timeout, and then replay "d" and "w" without any mapping (which will of course
|
||||
// delete a word)
|
||||
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ -> processUnfinishedMappingSequence(lambdaEditor, lambdaKeyState) }
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processUnfinishedMappingSequence(editor: VimEditor, keyState: KeyHandlerState) {
|
||||
if (injector.options(editor).timeout) {
|
||||
log.trace("timeout is set. schedule a mapping timer")
|
||||
// XXX There is a strange issue that reports that mapping state is empty at the moment of the function call.
|
||||
@ -104,6 +118,7 @@ public object MappingProcessor {
|
||||
// but before invoke later is handled. This is a rare case, so I'll just add a check to isPluginMapping.
|
||||
// But this "unexpected behaviour" exists, and it would be better not to relay on mutable state with delays.
|
||||
// https://youtrack.jetbrains.com/issue/VIM-2392
|
||||
val mappingState = keyState.mappingState
|
||||
mappingState.startMappingTimer {
|
||||
injector.application.invokeLater(
|
||||
{
|
||||
@ -122,12 +137,14 @@ public object MappingProcessor {
|
||||
// of waiting for `abc` mapping.
|
||||
val lastKeyInSequence = index == unhandledKeys.lastIndex
|
||||
|
||||
KeyHandler.getInstance().handleKey(
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(
|
||||
editor,
|
||||
keyStroke,
|
||||
injector.executionContextManager.onEditor(editor),
|
||||
allowKeyMappings = true,
|
||||
mappingCompleted = lastKeyInSequence,
|
||||
keyState,
|
||||
)
|
||||
}
|
||||
},
|
||||
@ -136,18 +153,16 @@ public object MappingProcessor {
|
||||
}
|
||||
}
|
||||
log.trace("Unfinished mapping processing finished")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleCompleteMappingSequence(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
mappingState: MappingState,
|
||||
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
mapping: KeyMappingLayer,
|
||||
key: KeyStroke,
|
||||
): Boolean {
|
||||
log.trace("Processing complete mapping sequence...")
|
||||
// The current sequence isn't a prefix, check to see if it's a completed sequence.
|
||||
val mappingState = processBuilder.state.mappingState
|
||||
val currentMappingInfo = mapping.getLayer(mappingState.keys)
|
||||
var mappingInfo = currentMappingInfo
|
||||
if (mappingInfo == null) {
|
||||
@ -173,12 +188,25 @@ public object MappingProcessor {
|
||||
log.trace("Cannot find any mapping info for the sequence. Return false.")
|
||||
return false
|
||||
}
|
||||
processBuilder.addExecutionStep { b, c, d -> processCompleteMappingSequence(key, b, c, d, mappingInfo, currentMappingInfo) }
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processCompleteMappingSequence(
|
||||
key: KeyStroke,
|
||||
keyState: KeyHandlerState,
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
mappingInfo: MappingInfoLayer,
|
||||
currentMappingInfo: MappingInfoLayer?,
|
||||
) {
|
||||
val mappingState = keyState.mappingState
|
||||
mappingState.resetMappingSequence()
|
||||
val currentContext = context.updateEditor(editor)
|
||||
log.trace("Executing mapping info")
|
||||
try {
|
||||
mappingState.startMapExecution()
|
||||
mappingInfo.execute(editor, context)
|
||||
mappingInfo.execute(editor, context, keyState)
|
||||
} catch (e: Exception) {
|
||||
injector.messages.showStatusBarMessage(editor, e.message)
|
||||
injector.messages.indicateError()
|
||||
@ -206,22 +234,17 @@ public object MappingProcessor {
|
||||
// If we've just evaluated the previous key sequence, make sure to also handle the current key
|
||||
if (mappingInfo !== currentMappingInfo) {
|
||||
log.trace("Evaluating the current key")
|
||||
KeyHandler.getInstance().handleKey(editor, key, currentContext, allowKeyMappings = true, false)
|
||||
KeyHandler.getInstance().handleKey(editor, key, currentContext, allowKeyMappings = true, false, keyState)
|
||||
}
|
||||
log.trace("Success processing of mapping")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleAbandonedMappingSequence(
|
||||
editor: VimEditor,
|
||||
mappingState: MappingState,
|
||||
context: ExecutionContext,
|
||||
): Boolean {
|
||||
private fun handleAbandonedMappingSequence(processBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
|
||||
log.debug("Processing abandoned mapping sequence")
|
||||
// The user has terminated a mapping sequence with an unexpected key
|
||||
// E.g. if there is a mapping for "hello" and user enters command "help" the processing of "h", "e" and "l" will be
|
||||
// prevented by this handler. Make sure the currently unhandled keys are processed as normal.
|
||||
val unhandledKeyStrokes = mappingState.detachKeys()
|
||||
val unhandledKeyStrokes = processBuilder.state.mappingState.detachKeys()
|
||||
|
||||
// If there is only the current key to handle, do nothing
|
||||
if (unhandledKeyStrokes.size == 1) {
|
||||
@ -236,6 +259,12 @@ public object MappingProcessor {
|
||||
// If user enters `dI`, the first `d` will be caught be this handler because it's a prefix for `ds` command.
|
||||
// After the user enters `I`, the caught `d` should be processed without mapping, and the rest of keys
|
||||
// should be processed with mappings (to make I work)
|
||||
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
|
||||
processAbondonedMappingSequence(unhandledKeyStrokes, lambdaEditor, lambdaContext, lambdaKeyState) }
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processAbondonedMappingSequence(unhandledKeyStrokes: List<KeyStroke>, editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||
if (isPluginMapping(unhandledKeyStrokes)) {
|
||||
log.trace("This is a plugin mapping, process it")
|
||||
KeyHandler.getInstance().handleKey(
|
||||
@ -244,18 +273,18 @@ public object MappingProcessor {
|
||||
context,
|
||||
allowKeyMappings = true,
|
||||
mappingCompleted = false,
|
||||
keyState,
|
||||
)
|
||||
} else {
|
||||
log.trace("Process abandoned keys.")
|
||||
KeyHandler.getInstance()
|
||||
.handleKey(editor, unhandledKeyStrokes[0], context, allowKeyMappings = false, mappingCompleted = false)
|
||||
.handleKey(editor, unhandledKeyStrokes[0], context, allowKeyMappings = false, mappingCompleted = false, keyState)
|
||||
for (keyStroke in unhandledKeyStrokes.subList(1, unhandledKeyStrokes.size)) {
|
||||
KeyHandler.getInstance()
|
||||
.handleKey(editor, keyStroke, context, allowKeyMappings = true, mappingCompleted = false)
|
||||
.handleKey(editor, keyStroke, context, allowKeyMappings = true, mappingCompleted = false, keyState)
|
||||
}
|
||||
}
|
||||
log.trace("Return true from abandoned keys processing.")
|
||||
return true
|
||||
}
|
||||
|
||||
// The <Plug>mappings are not executed if they fail to map to something.
|
||||
|
@ -16,7 +16,7 @@ import java.awt.event.ActionListener
|
||||
import javax.swing.KeyStroke
|
||||
import javax.swing.Timer
|
||||
|
||||
public class MappingState {
|
||||
public class MappingState: Cloneable {
|
||||
// Map command depth. 0 - if it is not a map command. 1 - regular map command. 2+ - nested map commands
|
||||
private var mapDepth = 0
|
||||
|
||||
@ -35,12 +35,7 @@ public class MappingState {
|
||||
public val keys: Iterable<KeyStroke>
|
||||
get() = keyList
|
||||
|
||||
public var mappingMode: MappingMode = MappingMode.NORMAL
|
||||
set(value) {
|
||||
field = value
|
||||
}
|
||||
|
||||
private val timer = Timer(injector.globalOptions().timeoutlen, null)
|
||||
private var timer = VimTimer(injector.globalOptions().timeoutlen)
|
||||
private var keyList = mutableListOf<KeyStroke>()
|
||||
|
||||
init {
|
||||
@ -77,7 +72,54 @@ public class MappingState {
|
||||
// NOTE: We intentionally don't reset mapping mode here
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as MappingState
|
||||
|
||||
if (mapDepth != other.mapDepth) return false
|
||||
if (timer != other.timer) return false
|
||||
if (keyList != other.keyList) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = mapDepth
|
||||
result = 31 * result + timer.hashCode()
|
||||
result = 31 * result + keyList.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
public override fun clone(): MappingState {
|
||||
val result = MappingState()
|
||||
result.timer = timer
|
||||
result.mapDepth = mapDepth
|
||||
result.keyList = keyList.toMutableList()
|
||||
return result
|
||||
}
|
||||
|
||||
public companion object {
|
||||
private val LOG = vimLogger<MappingState>()
|
||||
}
|
||||
}
|
||||
|
||||
public class VimTimer(delay: Int) : Timer(delay, null) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as VimTimer
|
||||
|
||||
if (delay != other.delay) return false
|
||||
if (initialDelay != other.initialDelay) return false
|
||||
if (isRunning != other.isRunning) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return javaClass.hashCode()
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class DigraphSequence {
|
||||
public class DigraphSequence: Cloneable {
|
||||
private var digraphState = DIG_STATE_PENDING
|
||||
private var digraphChar = 0.toChar()
|
||||
private lateinit var codeChars: CharArray
|
||||
@ -222,6 +222,46 @@ public class DigraphSequence {
|
||||
codeChars = CharArray(8)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as DigraphSequence
|
||||
|
||||
if (digraphState != other.digraphState) return false
|
||||
if (digraphChar != other.digraphChar) return false
|
||||
if (!codeChars.contentEquals(other.codeChars)) return false
|
||||
if (codeCnt != other.codeCnt) return false
|
||||
if (codeType != other.codeType) return false
|
||||
if (codeMax != other.codeMax) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = digraphState
|
||||
result = 31 * result + digraphChar.hashCode()
|
||||
result = 31 * result + codeChars.contentHashCode()
|
||||
result = 31 * result + codeCnt
|
||||
result = 31 * result + codeType
|
||||
result = 31 * result + codeMax
|
||||
return result
|
||||
}
|
||||
|
||||
public override fun clone(): DigraphSequence {
|
||||
val result = DigraphSequence()
|
||||
result.digraphState = digraphState
|
||||
result.digraphChar = digraphChar
|
||||
if (::codeChars.isInitialized) {
|
||||
result.codeChars = codeChars.copyOf()
|
||||
}
|
||||
result.codeCnt = codeCnt
|
||||
result.codeType = codeType
|
||||
result.codeMax = codeMax
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public companion object {
|
||||
private const val DIG_STATE_PENDING = 1
|
||||
private const val DIG_STATE_DIG_ONE = 2
|
||||
|
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.common
|
||||
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
|
||||
public interface IsReplaceCharListener {
|
||||
public fun isReplaceCharChanged(editor: VimEditor)
|
||||
}
|
@ -8,9 +8,7 @@
|
||||
|
||||
package com.maddyhome.idea.vim.common
|
||||
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
|
||||
public interface MacroRecordingListener {
|
||||
public fun recordingStarted(editor: VimEditor, register: Char)
|
||||
public fun recordingFinished(editor: VimEditor, register: Char)
|
||||
public fun recordingStarted()
|
||||
public fun recordingFinished()
|
||||
}
|
@ -19,6 +19,7 @@ public class VimListenersNotifier {
|
||||
public val myEditorListeners: MutableCollection<EditorListener> = ConcurrentLinkedDeque()
|
||||
public val macroRecordingListeners: MutableCollection<MacroRecordingListener> = ConcurrentLinkedDeque()
|
||||
public val vimPluginListeners: MutableCollection<VimPluginListener> = ConcurrentLinkedDeque()
|
||||
public val isReplaceCharListeners: MutableCollection<IsReplaceCharListener> = ConcurrentLinkedDeque()
|
||||
|
||||
public fun notifyModeChanged(editor: VimEditor, oldMode: Mode) {
|
||||
modeChangeListeners.forEach { it.modeChanged(editor, oldMode) }
|
||||
@ -40,12 +41,12 @@ public class VimListenersNotifier {
|
||||
myEditorListeners.forEach { it.focusLost(editor) }
|
||||
}
|
||||
|
||||
public fun notifyMacroRecordingStarted(editor: VimEditor, register: Char) {
|
||||
macroRecordingListeners.forEach { it.recordingStarted(editor, register) }
|
||||
public fun notifyMacroRecordingStarted() {
|
||||
macroRecordingListeners.forEach { it.recordingStarted() }
|
||||
}
|
||||
|
||||
public fun notifyMacroRecordingFinished(editor: VimEditor, register: Char) {
|
||||
macroRecordingListeners.forEach { it.recordingFinished(editor, register) }
|
||||
public fun notifyMacroRecordingFinished() {
|
||||
macroRecordingListeners.forEach { it.recordingFinished() }
|
||||
}
|
||||
|
||||
public fun notifyPluginTurnedOn() {
|
||||
@ -55,4 +56,16 @@ public class VimListenersNotifier {
|
||||
public fun notifyPluginTurnedOff() {
|
||||
vimPluginListeners.forEach { it.turnedOff() }
|
||||
}
|
||||
|
||||
public fun notifyIsReplaceCharChanged(editor: VimEditor) {
|
||||
isReplaceCharListeners.forEach { it.isReplaceCharChanged(editor) }
|
||||
}
|
||||
|
||||
public fun reset() {
|
||||
modeChangeListeners.clear()
|
||||
myEditorListeners.clear()
|
||||
macroRecordingListeners.clear()
|
||||
vimPluginListeners.clear()
|
||||
isReplaceCharListeners.clear()
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ public fun setVisualSelection(selectionStart: Int, selectionEnd: Int, caret: Vim
|
||||
editor.vimSetSystemBlockSelectionSilently(blockStart, blockEnd)
|
||||
|
||||
// We've just added secondary carets again, hide them to better emulate block selection
|
||||
editor.updateCaretsVisualAttributes()
|
||||
injector.editorGroup.updateCaretsVisualAttributes(editor)
|
||||
|
||||
for (aCaret in editor.nativeCarets()) {
|
||||
if (!aCaret.isValid) continue
|
||||
|
@ -54,10 +54,10 @@ public inline fun <reified T : Enum<T>> enumSetOf(vararg value: T): EnumSet<T> =
|
||||
else -> EnumSet.of(value[0], *value.slice(1..value.lastIndex).toTypedArray())
|
||||
}
|
||||
|
||||
public fun VimStateMachine.setSelectMode(submode: SelectionType) {
|
||||
public fun VimEditor.setSelectMode(submode: SelectionType) {
|
||||
mode = Mode.SELECT(submode, this.mode.returnTo)
|
||||
}
|
||||
|
||||
public fun VimStateMachine.pushVisualMode(submode: SelectionType) {
|
||||
public fun VimEditor.pushVisualMode(submode: SelectionType) {
|
||||
mode = Mode.VISUAL(submode, this.mode.returnTo)
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
|
||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
|
||||
@ -37,15 +36,15 @@ public fun VimEditor.exitVisualMode() {
|
||||
val returnTo = this.vimStateMachine.mode.returnTo
|
||||
when (returnTo) {
|
||||
ReturnTo.INSERT -> {
|
||||
this.vimStateMachine.mode = Mode.INSERT
|
||||
this.mode = Mode.INSERT
|
||||
}
|
||||
|
||||
ReturnTo.REPLACE -> {
|
||||
this.vimStateMachine.mode = Mode.REPLACE
|
||||
this.mode = Mode.REPLACE
|
||||
}
|
||||
|
||||
null -> {
|
||||
this.vimStateMachine.mode = Mode.NORMAL()
|
||||
this.mode = Mode.NORMAL()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,8 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.impl.state
|
||||
|
||||
import com.maddyhome.idea.vim.action.change.LazyVimCommand
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.CommandBuilder
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
@ -18,51 +16,29 @@ import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.command.MappingState
|
||||
import com.maddyhome.idea.vim.common.DigraphResult
|
||||
import com.maddyhome.idea.vim.common.DigraphSequence
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.helper.noneOfEnum
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||
import org.jetbrains.annotations.Contract
|
||||
import java.util.*
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
/**
|
||||
* Used to maintain state before and while entering a Vim command (operator, motion, text object, etc.)
|
||||
*
|
||||
* // TODO: 21.02.2022 This constructor should be empty
|
||||
*/
|
||||
public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachine {
|
||||
override val commandBuilder: CommandBuilder = CommandBuilder(getKeyRootNode(MappingMode.NORMAL))
|
||||
override var mode: Mode = Mode.NORMAL()
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
public class VimStateMachineImpl : VimStateMachine {
|
||||
@Deprecated("Please use KeyHandlerState instead")
|
||||
override val commandBuilder: CommandBuilder = KeyHandler.getInstance().keyHandlerState.commandBuilder
|
||||
@Deprecated("Please use KeyHandlerState instead")
|
||||
override val mappingState: MappingState = KeyHandler.getInstance().keyHandlerState.mappingState
|
||||
@Deprecated("Please use KeyHandlerState instead")
|
||||
override val digraphSequence: DigraphSequence = KeyHandler.getInstance().keyHandlerState.digraphSequence
|
||||
|
||||
val oldValue = field
|
||||
field = value
|
||||
setMappingMode()
|
||||
if (editor != null) {
|
||||
injector.listenersNotifier.notifyModeChanged(editor, oldValue)
|
||||
}
|
||||
onModeChanged()
|
||||
}
|
||||
override val mappingState: MappingState = MappingState()
|
||||
override val digraphSequence: DigraphSequence = DigraphSequence()
|
||||
override var isRecording: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
doShowMode()
|
||||
}
|
||||
override var mode: Mode = Mode.NORMAL()
|
||||
override var isDotRepeatInProgress: Boolean = false
|
||||
override var isRegisterPending: Boolean = false
|
||||
override var isReplaceCharacter: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
onModeChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* The currently executing command
|
||||
@ -76,32 +52,19 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi
|
||||
*/
|
||||
override var executingCommand: Command? = null
|
||||
|
||||
override val isOperatorPending: Boolean
|
||||
get() = mappingState.mappingMode == MappingMode.OP_PENDING && !commandBuilder.isEmpty
|
||||
override fun isOperatorPending(mode: Mode): Boolean {
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
return keyHandler.isOperatorPending(mode, keyHandler.keyHandlerState)
|
||||
}
|
||||
|
||||
override fun isDuplicateOperatorKeyStroke(key: KeyStroke?): Boolean {
|
||||
return isOperatorPending && commandBuilder.isDuplicateOperatorKeyStroke(key!!)
|
||||
override fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode): Boolean {
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
return keyHandler.isDuplicateOperatorKeyStroke(key, mode, keyHandler.keyHandlerState)
|
||||
}
|
||||
|
||||
override val executingCommandFlags: EnumSet<CommandFlags>
|
||||
get() = executingCommand?.flags ?: noneOfEnum()
|
||||
|
||||
override fun resetOpPending() {
|
||||
if (this.mode is Mode.OP_PENDING) {
|
||||
val returnTo = this.mode.returnTo
|
||||
mode = when (returnTo) {
|
||||
ReturnTo.INSERT -> Mode.INSERT
|
||||
ReturnTo.REPLACE -> Mode.INSERT
|
||||
null -> Mode.NORMAL()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetReplaceCharacter() {
|
||||
if (isReplaceCharacter) {
|
||||
isReplaceCharacter = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetRegisterPending() {
|
||||
if (isRegisterPending) {
|
||||
@ -109,44 +72,19 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetModes() {
|
||||
// modeStates.clear()
|
||||
mode = Mode.NORMAL()
|
||||
onModeChanged()
|
||||
setMappingMode()
|
||||
}
|
||||
|
||||
private fun onModeChanged() {
|
||||
if (editor != null) {
|
||||
editor.updateCaretsVisualAttributes()
|
||||
editor.updateCaretsVisualPosition()
|
||||
} else {
|
||||
injector.editorGroup.getEditors().forEach { editor ->
|
||||
editor.updateCaretsVisualAttributes()
|
||||
editor.updateCaretsVisualPosition()
|
||||
}
|
||||
}
|
||||
doShowMode()
|
||||
}
|
||||
|
||||
private fun setMappingMode() {
|
||||
mappingState.mappingMode = modeToMappingMode(this.mode)
|
||||
}
|
||||
|
||||
override fun startDigraphSequence() {
|
||||
digraphSequence.startDigraphSequence()
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.keyHandlerState.digraphSequence.startDigraphSequence()
|
||||
}
|
||||
|
||||
override fun startLiteralSequence() {
|
||||
digraphSequence.startLiteralSequence()
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.keyHandlerState.digraphSequence.startLiteralSequence()
|
||||
}
|
||||
|
||||
override fun processDigraphKey(key: KeyStroke, editor: VimEditor): DigraphResult {
|
||||
return digraphSequence.processKey(key, editor)
|
||||
}
|
||||
|
||||
override fun resetDigraph() {
|
||||
digraphSequence.reset()
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
return keyHandler.keyHandlerState.digraphSequence.processKey(key, editor)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,74 +104,7 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the command, mode, visual mode, and mapping mode to initial values.
|
||||
*/
|
||||
override fun reset() {
|
||||
executingCommand = null
|
||||
resetModes()
|
||||
commandBuilder.resetInProgressCommandPart(getKeyRootNode(mappingState.mappingMode))
|
||||
digraphSequence.reset()
|
||||
}
|
||||
|
||||
private fun doShowMode() {
|
||||
val msg = StringBuilder()
|
||||
if (injector.globalOptions().showmode) {
|
||||
msg.append(getStatusString())
|
||||
}
|
||||
if (isRecording) {
|
||||
if (msg.isNotEmpty()) {
|
||||
msg.append(" - ")
|
||||
}
|
||||
msg.append(injector.messages.message("show.mode.recording"))
|
||||
}
|
||||
injector.messages.showMode(editor, msg.toString())
|
||||
}
|
||||
|
||||
override fun getStatusString(): String {
|
||||
val modeState = this.mode
|
||||
return buildString {
|
||||
when (modeState) {
|
||||
is Mode.NORMAL -> {
|
||||
if (modeState.returnTo != null) append("-- (insert) --")
|
||||
}
|
||||
|
||||
Mode.INSERT -> append("-- INSERT --")
|
||||
Mode.REPLACE -> append("-- REPLACE --")
|
||||
is Mode.VISUAL -> {
|
||||
val inInsert = if (modeState.returnTo != null) "(insert) " else ""
|
||||
append("-- ${inInsert}VISUAL")
|
||||
when (modeState.selectionType) {
|
||||
SelectionType.LINE_WISE -> append(" LINE")
|
||||
SelectionType.BLOCK_WISE -> append(" BLOCK")
|
||||
else -> Unit
|
||||
}
|
||||
append(" --")
|
||||
}
|
||||
|
||||
is Mode.SELECT -> {
|
||||
val inInsert = if (modeState.returnTo != null) "(insert) " else ""
|
||||
append("-- ${inInsert}SELECT")
|
||||
when (modeState.selectionType) {
|
||||
SelectionType.LINE_WISE -> append(" LINE")
|
||||
SelectionType.BLOCK_WISE -> append(" BLOCK")
|
||||
else -> Unit
|
||||
}
|
||||
append(" --")
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
private val logger = vimLogger<VimStateMachine>()
|
||||
|
||||
private fun getKeyRootNode(mappingMode: MappingMode): CommandPartNode<LazyVimCommand> {
|
||||
return injector.keyGroup.getKeyRoot(mappingMode)
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public fun modeToMappingMode(mode: Mode): MappingMode {
|
||||
return when (mode) {
|
||||
@ -247,3 +118,14 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun Mode.toMappingMode(): MappingMode {
|
||||
return when (this) {
|
||||
is Mode.NORMAL -> MappingMode.NORMAL
|
||||
Mode.INSERT, Mode.REPLACE -> MappingMode.INSERT
|
||||
is Mode.VISUAL -> MappingMode.VISUAL
|
||||
is Mode.SELECT -> MappingMode.SELECT
|
||||
is Mode.CMD_LINE -> MappingMode.CMD_LINE
|
||||
is Mode.OP_PENDING -> MappingMode.OP_PENDING
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public interface KeyConsumer {
|
||||
/**
|
||||
* @return true if consumed key and could do something meaningful wit it
|
||||
*/
|
||||
public fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean
|
||||
}
|
@ -26,11 +26,10 @@ import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.group.visual.VimSelection.Companion.create
|
||||
import com.maddyhome.idea.vim.helper.VimNlsSafe
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||
@ -50,7 +49,7 @@ public sealed class MappingInfo(
|
||||
@VimNlsSafe
|
||||
abstract override fun getPresentableString(): String
|
||||
|
||||
abstract override fun execute(editor: VimEditor, context: ExecutionContext)
|
||||
abstract override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState)
|
||||
|
||||
override fun compareTo(other: MappingInfo): Int {
|
||||
val size = fromKeys.size
|
||||
@ -86,7 +85,7 @@ public class ToKeysMappingInfo(
|
||||
) : MappingInfo(fromKeys, isRecursive, owner) {
|
||||
override fun getPresentableString(): String = injector.parser.toKeyNotation(toKeys)
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext) {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||
LOG.debug("Executing 'ToKeys' mapping info...")
|
||||
val editorDataContext = injector.executionContextManager.onEditor(editor, context)
|
||||
val fromIsPrefix = KeyHandler.isPrefix(fromKeys, toKeys)
|
||||
@ -98,7 +97,7 @@ public class ToKeysMappingInfo(
|
||||
while (keyHandler.keyStack.hasStroke()) {
|
||||
val keyStroke = keyHandler.keyStack.feedStroke()
|
||||
val recursive = isRecursive && !(first && fromIsPrefix)
|
||||
keyHandler.handleKey(editor, keyStroke, editorDataContext, recursive, false)
|
||||
keyHandler.handleKey(editor, keyStroke, editorDataContext, recursive, false, keyState)
|
||||
first = false
|
||||
}
|
||||
} finally {
|
||||
@ -124,7 +123,7 @@ public class ToExpressionMappingInfo(
|
||||
) : MappingInfo(fromKeys, isRecursive, owner) {
|
||||
override fun getPresentableString(): String = originalString
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext) {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||
LOG.debug("Executing 'ToExpression' mapping info...")
|
||||
val editorDataContext = injector.executionContextManager.onEditor(editor, context)
|
||||
val toKeys = injector.parser.parseKeys(toExpression.evaluate(editor, context, CommandLineVimLContext).toString())
|
||||
@ -133,7 +132,7 @@ public class ToExpressionMappingInfo(
|
||||
for (keyStroke in toKeys) {
|
||||
val recursive = isRecursive && !(first && fromIsPrefix)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(editor, keyStroke, editorDataContext, recursive, false)
|
||||
keyHandler.handleKey(editor, keyStroke, editorDataContext, recursive, false, keyState)
|
||||
first = false
|
||||
}
|
||||
}
|
||||
@ -151,14 +150,14 @@ public class ToHandlerMappingInfo(
|
||||
) : MappingInfo(fromKeys, isRecursive, owner) {
|
||||
override fun getPresentableString(): String = "call ${extensionHandler.javaClass.canonicalName}"
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext) {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||
LOG.debug("Executing 'ToHandler' mapping info...")
|
||||
val vimStateMachine = VimStateMachine.getInstance(editor)
|
||||
|
||||
// Cache isOperatorPending in case the extension changes the mode while moving the caret
|
||||
// See CommonExtensionTest
|
||||
// TODO: Is this legal? Should we assert in this case?
|
||||
val shouldCalculateOffsets: Boolean = vimStateMachine.isOperatorPending
|
||||
val shouldCalculateOffsets: Boolean = vimStateMachine.isOperatorPending(editor.mode)
|
||||
|
||||
val startOffsets: Map<ImmutableVimCaret, Offset> = editor.carets().associateWith { it.offset }
|
||||
|
||||
@ -169,24 +168,24 @@ public class ToHandlerMappingInfo(
|
||||
val handler = extensionHandler
|
||||
if (handler is ExtensionHandler.WithCallback) {
|
||||
handler._backingFunction = Runnable {
|
||||
myFun(shouldCalculateOffsets, editor, startOffsets)
|
||||
myFun(shouldCalculateOffsets, editor, startOffsets, keyState)
|
||||
|
||||
if (shouldCalculateOffsets) {
|
||||
injector.application.invokeLater {
|
||||
KeyHandler.getInstance().finishedCommandPreparation(
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.finishedCommandPreparation(
|
||||
editor,
|
||||
context,
|
||||
VimStateMachine.getInstance(editor),
|
||||
VimStateMachine.getInstance(editor).commandBuilder,
|
||||
null,
|
||||
false,
|
||||
KeyHandler.MutableBoolean(false),
|
||||
keyState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val operatorArguments = OperatorArguments(vimStateMachine.isOperatorPending, vimStateMachine.commandBuilder.count, vimStateMachine.mode)
|
||||
val operatorArguments = OperatorArguments(vimStateMachine.isOperatorPending(editor.mode), keyState.commandBuilder.count, vimStateMachine.mode)
|
||||
injector.actionExecutor.executeCommand(
|
||||
editor,
|
||||
{ extensionHandler.execute(editor, context, operatorArguments) },
|
||||
@ -201,7 +200,7 @@ public class ToHandlerMappingInfo(
|
||||
}
|
||||
|
||||
if (handler !is ExtensionHandler.WithCallback) {
|
||||
myFun(shouldCalculateOffsets, editor, startOffsets)
|
||||
myFun(shouldCalculateOffsets, editor, startOffsets, keyState)
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,9 +211,9 @@ public class ToHandlerMappingInfo(
|
||||
shouldCalculateOffsets: Boolean,
|
||||
editor: VimEditor,
|
||||
startOffsets: Map<ImmutableVimCaret, Offset>,
|
||||
keyState: KeyHandlerState,
|
||||
) {
|
||||
val commandState = editor.vimStateMachine
|
||||
if (shouldCalculateOffsets && !commandState.commandBuilder.hasCurrentCommandPartArgument()) {
|
||||
if (shouldCalculateOffsets && !keyState.commandBuilder.hasCurrentCommandPartArgument()) {
|
||||
val offsets: MutableMap<ImmutableVimCaret, VimSelection> = HashMap()
|
||||
for (caret in editor.carets()) {
|
||||
var startOffset = startOffsets[caret]
|
||||
@ -222,7 +221,7 @@ public class ToHandlerMappingInfo(
|
||||
val vimSelection =
|
||||
create(caret.vimSelectionStart, caret.offset.point, editor.mode.selectionType ?: CHARACTER_WISE, editor)
|
||||
offsets[caret] = vimSelection
|
||||
commandState.mode = Mode.NORMAL()
|
||||
editor.mode = Mode.NORMAL()
|
||||
} else if (startOffset != null && startOffset.point != caret.offset.point) {
|
||||
// Command line motions are always characterwise exclusive
|
||||
var endOffset = caret.offset
|
||||
@ -240,7 +239,7 @@ public class ToHandlerMappingInfo(
|
||||
}
|
||||
}
|
||||
if (offsets.isNotEmpty()) {
|
||||
commandState.commandBuilder.completeCommandPart(Argument(offsets))
|
||||
keyState.commandBuilder.completeCommandPart(Argument(offsets))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -255,7 +254,7 @@ public class ToActionMappingInfo(
|
||||
) : MappingInfo(fromKeys, isRecursive, owner) {
|
||||
override fun getPresentableString(): String = "action $action"
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext) {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||
LOG.debug("Executing 'ToAction' mapping...")
|
||||
val editorDataContext = injector.executionContextManager.onEditor(editor, context)
|
||||
val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext)
|
||||
|
@ -10,8 +10,9 @@ package com.maddyhome.idea.vim.key
|
||||
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
|
||||
public interface MappingInfoLayer {
|
||||
public fun getPresentableString(): String
|
||||
public fun execute(editor: VimEditor, context: ExecutionContext)
|
||||
public fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState)
|
||||
}
|
||||
|
@ -42,10 +42,21 @@ import javax.swing.KeyStroke
|
||||
public interface Node<T>
|
||||
|
||||
/** Represents a complete command */
|
||||
public class CommandNode<T>(public val actionHolder: T) : Node<T>
|
||||
public data class CommandNode<T>(public val actionHolder: T) : Node<T>
|
||||
|
||||
/** Represents a part of the command */
|
||||
public open class CommandPartNode<T> : Node<T>, HashMap<KeyStroke, Node<T>>()
|
||||
public open class CommandPartNode<T> : Node<T>, HashMap<KeyStroke, Node<T>>() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return super.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a root node for the mode */
|
||||
public class RootNode<T> : CommandPartNode<T>()
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.CommandBuilder
|
||||
import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class CharArgumentConsumer: KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<CharArgumentConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
if (!isExpectingCharArgument(keyProcessResultBuilder.state.commandBuilder)) return false
|
||||
|
||||
val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar
|
||||
handleCharArgument(key, chKey, keyProcessResultBuilder)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isExpectingCharArgument(commandBuilder: CommandBuilder): Boolean {
|
||||
val expectingCharArgument = commandBuilder.expectedArgumentType === Argument.Type.CHARACTER
|
||||
logger.debug { "Expecting char argument: $expectingCharArgument" }
|
||||
return expectingCharArgument
|
||||
}
|
||||
|
||||
private fun handleCharArgument(key: KeyStroke, chKey: Char, processBuilder: KeyProcessResult.KeyProcessResultBuilder) {
|
||||
var mutableChKey = chKey
|
||||
logger.trace("Handling char argument")
|
||||
// We are expecting a character argument - is this a regular character the user typed?
|
||||
// Some special keys can be handled as character arguments - let's check for them here.
|
||||
if (mutableChKey.code == 0) {
|
||||
when (key.keyCode) {
|
||||
KeyEvent.VK_TAB -> mutableChKey = '\t'
|
||||
KeyEvent.VK_ENTER -> mutableChKey = '\n'
|
||||
}
|
||||
}
|
||||
val commandBuilder = processBuilder.state.commandBuilder
|
||||
if (mutableChKey.code != 0) {
|
||||
processBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
||||
// Create the character argument, add it to the current command, and signal we are ready to process the command
|
||||
logger.trace("Add character argument to the current command")
|
||||
commandBuilder.completeCommandPart(Argument(mutableChKey))
|
||||
lambdaEditor.isReplaceCharacter = false
|
||||
}
|
||||
} else {
|
||||
logger.trace("This is not a valid character argument. Set command state to BAD_COMMAND")
|
||||
// Oops - this isn't a valid character argument
|
||||
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ ->
|
||||
KeyHandler.getInstance().setBadCommand(lambdaEditor, lambdaKeyState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.action.change.LazyVimCommand
|
||||
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.CommandFlags
|
||||
import com.maddyhome.idea.vim.common.CurrentCommandState
|
||||
import com.maddyhome.idea.vim.common.argumentCaptured
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.key.CommandNode
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.key.Node
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
||||
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class CommandConsumer : KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<CommandConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
val commandBuilder = keyProcessResultBuilder.state.commandBuilder
|
||||
// Ask the key/action tree if this is an appropriate key at this point in the command and if so,
|
||||
// return the node matching this keystroke
|
||||
val node: Node<LazyVimCommand>? = mapOpCommand(key, commandBuilder.getChildNode(key), editor.mode, keyProcessResultBuilder.state)
|
||||
logger.trace("Get the node for the current mode")
|
||||
|
||||
when (node) {
|
||||
is CommandNode<LazyVimCommand> -> {
|
||||
logger.trace("Node is a command node")
|
||||
handleCommandNode(key, node, keyProcessResultBuilder)
|
||||
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ -> lambdaKeyState.commandBuilder.addKey(key) }
|
||||
return true
|
||||
}
|
||||
|
||||
is CommandPartNode<LazyVimCommand> -> {
|
||||
logger.trace("Node is a command part node")
|
||||
commandBuilder.setCurrentCommandPartNode(node)
|
||||
commandBuilder.addKey(key)
|
||||
return true
|
||||
}
|
||||
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See the description for [com.maddyhome.idea.vim.command.DuplicableOperatorAction]
|
||||
*/
|
||||
private fun mapOpCommand(
|
||||
key: KeyStroke,
|
||||
node: Node<LazyVimCommand>?,
|
||||
mode: Mode,
|
||||
keyState: KeyHandlerState,
|
||||
): Node<LazyVimCommand>? {
|
||||
return if (KeyHandler.getInstance().isDuplicateOperatorKeyStroke(key, mode, keyState)) {
|
||||
keyState.commandBuilder.getChildNode(KeyStroke.getKeyStroke('_'))
|
||||
} else {
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCommandNode(
|
||||
key: KeyStroke,
|
||||
node: CommandNode<LazyVimCommand>,
|
||||
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
) {
|
||||
logger.trace("Handle command node")
|
||||
// The user entered a valid command. Create the command and add it to the stack.
|
||||
val action = node.actionHolder.instance
|
||||
val keyState = processBuilder.state
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
val expectedArgumentType = commandBuilder.expectedArgumentType
|
||||
commandBuilder.pushCommandPart(action)
|
||||
if (!checkArgumentCompatibility(expectedArgumentType, action)) {
|
||||
logger.trace("Return from command node handling")
|
||||
processBuilder.addExecutionStep { lamdaKeyState, lambdaEditor, _ ->
|
||||
KeyHandler.getInstance().setBadCommand(lambdaEditor, lamdaKeyState)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (action.argumentType == null || stopMacroRecord(node)) {
|
||||
logger.trace("Set command state to READY")
|
||||
commandBuilder.commandState = CurrentCommandState.READY
|
||||
} else {
|
||||
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
|
||||
logger.trace("Set waiting for the argument")
|
||||
val argumentType = action.argumentType
|
||||
val editorState = lambdaEditor.vimStateMachine
|
||||
startWaitingForArgument(lambdaEditor, lambdaContext, key.keyChar, action, argumentType!!, lambdaKeyState, editorState)
|
||||
lambdaKeyState.partialReset(editorState.mode)
|
||||
}
|
||||
}
|
||||
|
||||
processBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
||||
// TODO In the name of God, get rid of EX_STRING, FLAG_COMPLETE_EX and all the related staff
|
||||
if (expectedArgumentType === Argument.Type.EX_STRING && action.flags.contains(CommandFlags.FLAG_COMPLETE_EX)) {
|
||||
/* The only action that implements FLAG_COMPLETE_EX is ProcessExEntryAction.
|
||||
* When pressing ':', ExEntryAction is chosen as the command. Since it expects no arguments, it is invoked and
|
||||
calls ProcessGroup#startExCommand, pushes CMD_LINE mode, and the action is popped. The ex handler will push
|
||||
the final <CR> through handleKey, which chooses ProcessExEntryAction. Because we're not expecting EX_STRING,
|
||||
this branch does NOT fire, and ProcessExEntryAction handles the ex cmd line entry.
|
||||
* When pressing '/' or '?', SearchEntry(Fwd|Rev)Action is chosen as the command. This expects an argument of
|
||||
EX_STRING, so startWaitingForArgument calls ProcessGroup#startSearchCommand. The ex handler pushes the final
|
||||
<CR> through handleKey, which chooses ProcessExEntryAction, and we hit this branch. We don't invoke
|
||||
ProcessExEntryAction, but pop it, set the search text as an argument on SearchEntry(Fwd|Rev)Action and invoke
|
||||
that instead.
|
||||
* When using '/' or '?' as part of a motion (e.g. "d/foo"), the above happens again, and all is good. Because
|
||||
the text has been applied as an argument on the last command, '.' will correctly repeat it.
|
||||
|
||||
It's hard to see how to improve this. Removing EX_STRING means starting ex input has to happen in ExEntryAction
|
||||
and SearchEntry(Fwd|Rev)Action, and the ex command invoked in ProcessExEntryAction, but that breaks any initial
|
||||
operator, which would be invoked first (e.g. 'd' in "d/foo").
|
||||
*/
|
||||
logger.trace("Processing ex_string")
|
||||
val text = injector.processGroup.endSearchCommand()
|
||||
commandBuilder.popCommandPart() // Pop ProcessExEntryAction
|
||||
commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action
|
||||
lambdaEditor.mode = lambdaEditor.mode.returnTo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopMacroRecord(node: CommandNode<LazyVimCommand>): Boolean {
|
||||
return injector.registerGroup.isRecording && node.actionHolder.instance.id == "VimToggleRecordingAction"
|
||||
}
|
||||
|
||||
private fun startWaitingForArgument(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
key: Char,
|
||||
action: EditorActionHandlerBase,
|
||||
argument: Argument.Type,
|
||||
keyState: KeyHandlerState,
|
||||
editorState: VimStateMachine,
|
||||
) {
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
when (argument) {
|
||||
Argument.Type.MOTION -> {
|
||||
if (editorState.isDotRepeatInProgress && argumentCaptured != null) {
|
||||
commandBuilder.completeCommandPart(argumentCaptured!!)
|
||||
}
|
||||
editor.mode = Mode.OP_PENDING(editorState.mode.returnTo)
|
||||
}
|
||||
|
||||
Argument.Type.DIGRAPH -> // Command actions represent the completion of a command. Showcmd relies on this - if the action represents a
|
||||
// part of a command, the showcmd output is reset part way through. This means we need to special case entering
|
||||
// digraph/literal input mode. We have an action that takes a digraph as an argument, and pushes it back through
|
||||
// the key handler when it's complete.
|
||||
|
||||
// TODO
|
||||
// if (action is InsertCompletedDigraphAction) {
|
||||
if (action.id == "VimInsertCompletedDigraphAction") {
|
||||
keyState.digraphSequence.startDigraphSequence()
|
||||
KeyHandler.getInstance().setPromptCharacterEx('?')
|
||||
} else if (action.id == "VimInsertCompletedLiteralAction") {
|
||||
keyState.digraphSequence.startLiteralSequence()
|
||||
KeyHandler.getInstance().setPromptCharacterEx('^')
|
||||
}
|
||||
|
||||
Argument.Type.EX_STRING -> {
|
||||
// The current Command expects an EX_STRING argument. E.g. SearchEntry(Fwd|Rev)Action. This won't execute until
|
||||
// state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument.
|
||||
injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key)
|
||||
commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
|
||||
val currentMode = editorState.mode
|
||||
check(currentMode is ReturnableFromCmd) { "Cannot enable command line mode $currentMode" }
|
||||
editor.mode = Mode.CMD_LINE(currentMode)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
// Another special case. Force a mode change to update the caret shape
|
||||
// This was a typed solution
|
||||
// if (action is ChangeCharacterAction || action is ChangeVisualCharacterAction)
|
||||
if (action.id == "VimChangeCharacterAction" || action.id == "VimChangeVisualCharacterAction") {
|
||||
editor.isReplaceCharacter = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkArgumentCompatibility(
|
||||
expectedArgumentType: Argument.Type?,
|
||||
action: EditorActionHandlerBase,
|
||||
): Boolean {
|
||||
return !(expectedArgumentType === Argument.Type.MOTION && action.type !== Command.Type.MOTION)
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class CommandCountConsumer : KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<CommandCountConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar
|
||||
if (!isCommandCountKey(chKey, keyProcessResultBuilder.state, editor)) return false
|
||||
|
||||
keyProcessResultBuilder.state.commandBuilder.addCountCharacter(key)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isCommandCountKey(chKey: Char, keyState: KeyHandlerState, editor: VimEditor): Boolean {
|
||||
// Make sure to avoid handling '0' as the start of a count.
|
||||
val editorState = editor.vimStateMachine
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
val notRegisterPendingCommand = editorState.mode is Mode.NORMAL && !editorState.isRegisterPending
|
||||
val visualMode = editorState.mode is Mode.VISUAL && !editorState.isRegisterPending
|
||||
val opPendingMode = editorState.mode is Mode.OP_PENDING
|
||||
|
||||
if (notRegisterPendingCommand || visualMode || opPendingMode) {
|
||||
if (commandBuilder.isExpectingCount && Character.isDigit(chKey) && (commandBuilder.count > 0 || chKey != '0')) {
|
||||
logger.debug("This is a command key count")
|
||||
return true
|
||||
}
|
||||
}
|
||||
logger.debug("This is NOT a command key count")
|
||||
return false
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class DeleteCommandConsumer : KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<DeleteCommandConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
if (!isDeleteCommandCountKey(key, keyProcessResultBuilder.state, editor.mode)) return false
|
||||
keyProcessResultBuilder.state.commandBuilder.deleteCountCharacter()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isDeleteCommandCountKey(key: KeyStroke, keyState: KeyHandlerState, mode: Mode): Boolean {
|
||||
// See `:help N<Del>`
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
val isDeleteCommandKeyCount =
|
||||
(mode is Mode.NORMAL || mode is Mode.VISUAL || mode is Mode.OP_PENDING) &&
|
||||
commandBuilder.isExpectingCount && commandBuilder.count > 0 && key.keyCode == KeyEvent.VK_DELETE
|
||||
|
||||
logger.debug { "This is a delete command key count: $isDeleteCommandKeyCount" }
|
||||
return isDeleteCommandKeyCount
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
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.common.DigraphResult
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import java.awt.event.InputEvent
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class DigraphConsumer : KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<DigraphConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
logger.debug("Handling digraph")
|
||||
// Support starting a digraph/literal sequence if the operator accepts one as an argument, e.g. 'r' or 'f'.
|
||||
// Normally, we start the sequence (in Insert or CmdLine mode) through a VimAction that can be mapped. Our
|
||||
// VimActions don't work as arguments for operators, so we have to special case here. Helpfully, Vim appears to
|
||||
// hardcode the shortcuts, and doesn't support mapping, so everything works nicely.
|
||||
val keyState = keyProcessResultBuilder.state
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
val digraphSequence = keyState.digraphSequence
|
||||
if (commandBuilder.expectedArgumentType == Argument.Type.DIGRAPH) {
|
||||
logger.trace("Expected argument is digraph")
|
||||
if (digraphSequence.isDigraphStart(key)) {
|
||||
digraphSequence.startDigraphSequence()
|
||||
commandBuilder.addKey(key)
|
||||
return true
|
||||
}
|
||||
if (digraphSequence.isLiteralStart(key)) {
|
||||
digraphSequence.startLiteralSequence()
|
||||
commandBuilder.addKey(key)
|
||||
return true
|
||||
}
|
||||
}
|
||||
val res = digraphSequence.processKey(key, editor)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
when (res.result) {
|
||||
DigraphResult.RES_HANDLED -> {
|
||||
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ ->
|
||||
if (injector.exEntryPanel.isActive()) {
|
||||
keyHandler.setPromptCharacterEx(if (lambdaKeyState.commandBuilder.isPuttingLiteral()) '^' else key.keyChar)
|
||||
}
|
||||
lambdaKeyState.commandBuilder.addKey(key)
|
||||
}
|
||||
return true
|
||||
}
|
||||
DigraphResult.RES_DONE -> {
|
||||
if (injector.exEntryPanel.isActive()) {
|
||||
if (key.keyCode == KeyEvent.VK_C && key.modifiers and InputEvent.CTRL_DOWN_MASK != 0) {
|
||||
return false
|
||||
} else {
|
||||
keyProcessResultBuilder.addExecutionStep { _, _, _ ->
|
||||
injector.exEntryPanel.clearCurrentAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ ->
|
||||
if (lambdaKeyState.commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
|
||||
lambdaKeyState.commandBuilder.fallbackToCharacterArgument()
|
||||
}
|
||||
}
|
||||
val stroke = res.stroke ?: return false
|
||||
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditorState, lambdaContext ->
|
||||
lambdaKeyState.commandBuilder.addKey(key)
|
||||
keyHandler.handleKey(lambdaEditorState, stroke, lambdaContext, lambdaKeyState)
|
||||
}
|
||||
return true
|
||||
}
|
||||
DigraphResult.RES_BAD -> {
|
||||
if (injector.exEntryPanel.isActive()) {
|
||||
if (key.keyCode == KeyEvent.VK_C && key.modifiers and InputEvent.CTRL_DOWN_MASK != 0) {
|
||||
return false
|
||||
} else {
|
||||
keyProcessResultBuilder.addExecutionStep { _, _, _ ->
|
||||
injector.exEntryPanel.clearCurrentAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ ->
|
||||
// BAD is an error. We were expecting a valid character, and we didn't get it.
|
||||
if (lambdaKeyState.commandBuilder.expectedArgumentType != null) {
|
||||
KeyHandler.getInstance().setBadCommand(lambdaEditor, lambdaKeyState)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
DigraphResult.RES_UNHANDLED -> {
|
||||
// UNHANDLED means the keystroke made no sense in the context of a digraph, but isn't an error in the current
|
||||
// state. E.g. waiting for {char} <BS> {char}. Let the key handler have a go at it.
|
||||
if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
|
||||
commandBuilder.fallbackToCharacterArgument()
|
||||
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
|
||||
keyHandler.handleKey(lambdaEditor, key, lambdaContext, lambdaKeyState)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
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.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class EditorResetConsumer : KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<EditorResetConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
if (!isEditorReset(key, editor)) return false
|
||||
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> handleEditorReset(lambdaEditor, key, lambdaKeyState, lambdaContext) }
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isEditorReset(key: KeyStroke, editor: VimEditor): Boolean {
|
||||
val editorReset = editor.mode is Mode.NORMAL && key.isCloseKeyStroke()
|
||||
logger.debug { "This is editor reset: $editorReset" }
|
||||
return editorReset
|
||||
}
|
||||
|
||||
private fun handleEditorReset(
|
||||
editor: VimEditor,
|
||||
key: KeyStroke,
|
||||
keyState: KeyHandlerState,
|
||||
context: ExecutionContext,
|
||||
) {
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
if (commandBuilder.isAwaitingCharOrDigraphArgument()) {
|
||||
editor.isReplaceCharacter = false
|
||||
}
|
||||
if (commandBuilder.isAtDefaultState) {
|
||||
val register = injector.registerGroup
|
||||
if (register.currentRegister == register.defaultRegister) {
|
||||
var indicateError = true
|
||||
if (key.keyCode == KeyEvent.VK_ESCAPE) {
|
||||
val executed = arrayOf<Boolean?>(null)
|
||||
injector.actionExecutor.executeCommand(
|
||||
editor,
|
||||
{ executed[0] = injector.actionExecutor.executeEsc(context) },
|
||||
"",
|
||||
null,
|
||||
)
|
||||
indicateError = !executed[0]!!
|
||||
}
|
||||
if (indicateError) {
|
||||
injector.messages.indicateError()
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyHandler.getInstance().reset(keyState, editor.mode)
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class ModeInputConsumer: KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<ModeInputConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
val isProcessed = when (editor.mode) {
|
||||
Mode.INSERT, Mode.REPLACE -> {
|
||||
logger.trace("Process insert or replace")
|
||||
val keyProcessed = injector.changeGroup.processKey(editor, key, keyProcessResultBuilder)
|
||||
shouldRecord.value = keyProcessed && shouldRecord.value
|
||||
keyProcessed
|
||||
}
|
||||
is Mode.SELECT -> {
|
||||
logger.trace("Process select")
|
||||
val keyProcessed = injector.changeGroup.processKeyInSelectMode(editor, key, keyProcessResultBuilder)
|
||||
shouldRecord.value = keyProcessed && shouldRecord.value
|
||||
keyProcessed
|
||||
}
|
||||
is Mode.CMD_LINE -> {
|
||||
logger.trace("Process cmd line")
|
||||
val keyProcessed = injector.processGroup.processExKey(editor, key, keyProcessResultBuilder)
|
||||
shouldRecord.value = keyProcessed && shouldRecord.value
|
||||
keyProcessed
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
if (isProcessed) {
|
||||
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ ->
|
||||
lambdaKeyState.partialReset(lambdaEditor.mode)
|
||||
}
|
||||
}
|
||||
return isProcessed
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class RegisterConsumer : KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<CharArgumentConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
if (!editor.vimStateMachine.isRegisterPending) return false
|
||||
|
||||
logger.trace("Pending mode.")
|
||||
keyProcessResultBuilder.state.commandBuilder.addKey(key)
|
||||
|
||||
val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar
|
||||
handleSelectRegister(editor, chKey, keyProcessResultBuilder)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleSelectRegister(editor: VimEditor, chKey: Char, processBuilder: KeyProcessResult.KeyProcessResultBuilder) {
|
||||
logger.trace("Handle select register")
|
||||
editor.vimStateMachine.resetRegisterPending()
|
||||
if (injector.registerGroup.isValid(chKey)) {
|
||||
logger.trace("Valid register")
|
||||
processBuilder.state.commandBuilder.pushCommandPart(chKey)
|
||||
} else {
|
||||
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ ->
|
||||
logger.trace("Invalid register, set command state to BAD_COMMAND")
|
||||
KeyHandler.getInstance().setBadCommand(lambdaEditor, lambdaKeyState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key.consumers
|
||||
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class SelectRegisterConsumer : KeyConsumer {
|
||||
private companion object {
|
||||
private val logger = vimLogger<SelectRegisterConsumer>()
|
||||
}
|
||||
|
||||
override fun consumeKey(
|
||||
key: KeyStroke,
|
||||
editor: VimEditor,
|
||||
allowKeyMappings: Boolean,
|
||||
mappingCompleted: Boolean,
|
||||
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
|
||||
shouldRecord: KeyHandler.MutableBoolean,
|
||||
): Boolean {
|
||||
val state = keyProcessResultBuilder.state
|
||||
if (!isSelectRegister(key, state, editor.vimStateMachine)) return false
|
||||
|
||||
logger.trace("Select register")
|
||||
state.commandBuilder.addKey(key)
|
||||
keyProcessResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
||||
lambdaEditor.vimStateMachine.isRegisterPending = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isSelectRegister(key: KeyStroke, keyState: KeyHandlerState, editorState: VimStateMachine): Boolean {
|
||||
if (editorState.mode !is Mode.NORMAL && editorState.mode !is Mode.VISUAL) {
|
||||
return false
|
||||
}
|
||||
return if (editorState.isRegisterPending) {
|
||||
true
|
||||
} else {
|
||||
key.keyChar == '"' && !KeyHandler.getInstance().isOperatorPending(editorState.mode, keyState) && keyState.commandBuilder.expectedArgumentType == null
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,9 @@ public interface VimRegisterGroup {
|
||||
public var lastRegisterChar: Char
|
||||
public val currentRegister: Char
|
||||
|
||||
public val isRecording: Boolean
|
||||
public val recordRegister: Char?
|
||||
|
||||
/**
|
||||
* When we access last register, it can be e.g. " because of two reasons:
|
||||
* 1. Because the default register value was used
|
||||
@ -79,13 +82,13 @@ public interface VimRegisterGroup {
|
||||
public fun getRegister(r: Char): Register?
|
||||
public fun getRegisters(): List<Register>
|
||||
public fun saveRegister(r: Char, register: Register)
|
||||
public fun startRecording(editor: VimEditor, register: Char): Boolean
|
||||
public fun startRecording(register: Char): Boolean
|
||||
|
||||
public fun getPlaybackRegister(r: Char): Register?
|
||||
public fun recordText(text: String)
|
||||
public fun setKeys(register: Char, keys: List<KeyStroke>)
|
||||
public fun setKeys(register: Char, keys: List<KeyStroke>, type: SelectionType)
|
||||
public fun finishRecording(editor: VimEditor)
|
||||
public fun finishRecording()
|
||||
public fun getCurrentRegisterForMulticaret(): Char // `set clipbaard+=unnamedplus` should not make system register the default one when working with multiple carets VIM-2804
|
||||
public fun isSystemClipboard(register: Char): Boolean
|
||||
public fun isPrimaryRegisterSupported(): Boolean
|
||||
|
@ -36,9 +36,18 @@ import com.maddyhome.idea.vim.register.RegisterConstants.WRITABLE_REGISTERS
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public abstract class VimRegisterGroupBase : VimRegisterGroup {
|
||||
override val isRecording: Boolean
|
||||
get() = recordRegister != null
|
||||
|
||||
@JvmField
|
||||
protected var recordRegister: Char = 0.toChar()
|
||||
public override var recordRegister: Char? = null
|
||||
set(value) {
|
||||
field = value
|
||||
if (value != null) {
|
||||
injector.listenersNotifier.notifyMacroRecordingStarted()
|
||||
} else {
|
||||
injector.listenersNotifier.notifyMacroRecordingFinished()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
protected var recordList: MutableList<KeyStroke>? = null
|
||||
@ -127,7 +136,7 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup {
|
||||
|
||||
override fun recordKeyStroke(key: KeyStroke) {
|
||||
val myRecordList = recordList
|
||||
if (recordRegister != 0.toChar() && myRecordList != null) {
|
||||
if (isRecording && myRecordList != null) {
|
||||
myRecordList.add(key)
|
||||
}
|
||||
}
|
||||
@ -475,12 +484,10 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup {
|
||||
myRegisters[myR] = register
|
||||
}
|
||||
|
||||
override fun startRecording(editor: VimEditor, register: Char): Boolean {
|
||||
override fun startRecording(register: Char): Boolean {
|
||||
return if (RECORDABLE_REGISTERS.indexOf(register) != -1) {
|
||||
VimStateMachine.getInstance(editor).isRecording = true
|
||||
recordRegister = register
|
||||
recordList = ArrayList()
|
||||
injector.listenersNotifier.notifyMacroRecordingStarted(editor, register)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@ -493,7 +500,7 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup {
|
||||
|
||||
override fun recordText(text: String) {
|
||||
val myRecordList = recordList
|
||||
if (recordRegister != 0.toChar() && myRecordList != null) {
|
||||
if (isRecording && myRecordList != null) {
|
||||
myRecordList.addAll(injector.parser.stringToKeys(text))
|
||||
}
|
||||
}
|
||||
@ -506,27 +513,25 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup {
|
||||
myRegisters[register] = Register(register, type, keys.toMutableList())
|
||||
}
|
||||
|
||||
override fun finishRecording(editor: VimEditor) {
|
||||
if (recordRegister != 0.toChar()) {
|
||||
override fun finishRecording() {
|
||||
val register = recordRegister
|
||||
if (register != null) {
|
||||
var reg: Register? = null
|
||||
if (Character.isUpperCase(recordRegister)) {
|
||||
reg = getRegister(recordRegister)
|
||||
if (Character.isUpperCase(register)) {
|
||||
reg = getRegister(register)
|
||||
}
|
||||
|
||||
val myRecordList = recordList
|
||||
if (myRecordList != null) {
|
||||
if (reg == null) {
|
||||
reg = Register(Character.toLowerCase(recordRegister), SelectionType.CHARACTER_WISE, myRecordList)
|
||||
myRegisters[Character.toLowerCase(recordRegister)] = reg
|
||||
reg = Register(Character.toLowerCase(register), SelectionType.CHARACTER_WISE, myRecordList)
|
||||
myRegisters[Character.toLowerCase(register)] = reg
|
||||
} else {
|
||||
reg.addKeys(myRecordList)
|
||||
}
|
||||
}
|
||||
VimStateMachine.getInstance(editor).isRecording = false
|
||||
injector.listenersNotifier.notifyMacroRecordingFinished(editor, recordRegister)
|
||||
}
|
||||
|
||||
recordRegister = 0.toChar()
|
||||
recordRegister = null
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.state
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.CommandBuilder
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.command.MappingState
|
||||
import com.maddyhome.idea.vim.common.DigraphSequence
|
||||
import com.maddyhome.idea.vim.impl.state.toMappingMode
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
|
||||
public data class KeyHandlerState(
|
||||
public val mappingState: MappingState,
|
||||
public val digraphSequence: DigraphSequence,
|
||||
public val commandBuilder: CommandBuilder,
|
||||
): Cloneable {
|
||||
public constructor() : this(MappingState(), DigraphSequence(), CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL)))
|
||||
|
||||
public fun partialReset(mode: Mode) {
|
||||
mappingState.resetMappingSequence()
|
||||
commandBuilder.resetInProgressCommandPart(injector.keyGroup.getKeyRoot(mode.toMappingMode()))
|
||||
}
|
||||
|
||||
public fun reset(mode: Mode) {
|
||||
digraphSequence.reset()
|
||||
mappingState.resetMappingSequence()
|
||||
commandBuilder.resetAll(injector.keyGroup.getKeyRoot(mode.toMappingMode()))
|
||||
}
|
||||
|
||||
public override fun clone(): KeyHandlerState {
|
||||
return KeyHandlerState(
|
||||
mappingState.clone(),
|
||||
digraphSequence.clone(),
|
||||
commandBuilder.clone()
|
||||
)
|
||||
}
|
||||
}
|
@ -26,14 +26,17 @@ import javax.swing.KeyStroke
|
||||
* Used to maintain state before and while entering a Vim command (operator, motion, text object, etc.)
|
||||
*/
|
||||
public interface VimStateMachine {
|
||||
@Deprecated("Please use KeyHandlerState instead")
|
||||
public val commandBuilder: CommandBuilder
|
||||
public var mode: Mode
|
||||
@Deprecated("Please use KeyHandlerState instead")
|
||||
public val mappingState: MappingState
|
||||
@Deprecated("Please use KeyHandlerState instead")
|
||||
public val digraphSequence: DigraphSequence
|
||||
public var isRecording: Boolean
|
||||
|
||||
public val mode: Mode
|
||||
public var isDotRepeatInProgress: Boolean
|
||||
public var isRegisterPending: Boolean
|
||||
public var isReplaceCharacter: Boolean
|
||||
public val isReplaceCharacter: Boolean
|
||||
|
||||
/**
|
||||
* The currently executing command
|
||||
@ -46,17 +49,14 @@ public interface VimStateMachine {
|
||||
* This field is reset after the command has been executed.
|
||||
*/
|
||||
public var executingCommand: Command?
|
||||
public val isOperatorPending: Boolean
|
||||
public fun isOperatorPending(mode: Mode): Boolean
|
||||
public val executingCommandFlags: EnumSet<CommandFlags>
|
||||
|
||||
public fun isDuplicateOperatorKeyStroke(key: KeyStroke?): Boolean
|
||||
public fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode): Boolean
|
||||
|
||||
public fun resetOpPending()
|
||||
public fun resetReplaceCharacter()
|
||||
public fun resetRegisterPending()
|
||||
public fun startLiteralSequence()
|
||||
public fun processDigraphKey(key: KeyStroke, editor: VimEditor): DigraphResult
|
||||
public fun resetDigraph()
|
||||
|
||||
/**
|
||||
* Toggles the insert/overwrite state. If currently insert, goto replace mode. If currently replace, goto insert
|
||||
@ -64,16 +64,12 @@ public interface VimStateMachine {
|
||||
*/
|
||||
public fun toggleInsertOverwrite()
|
||||
|
||||
/**
|
||||
* Resets the command, mode, visual mode, and mapping mode to initial values.
|
||||
*/
|
||||
public fun reset()
|
||||
public fun getStatusString(): String
|
||||
public fun startDigraphSequence()
|
||||
|
||||
public companion object {
|
||||
private val globalState = VimStateMachineImpl(null)
|
||||
private val globalState = VimStateMachineImpl()
|
||||
|
||||
// TODO do we really need this method? Can't we use editor.vimStateMachine?
|
||||
public fun getInstance(editor: Any?): VimStateMachine {
|
||||
return if (editor == null || injector.globalOptions().ideaglobalmode) {
|
||||
globalState
|
||||
|
@ -24,7 +24,6 @@ import com.maddyhome.idea.vim.ex.NoRangeAllowedException
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||
import com.maddyhome.idea.vim.helper.Msg
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.helper.noneOfEnum
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.vimscript.model.Executable
|
||||
@ -71,7 +70,7 @@ public sealed class Command(public var commandRanges: Ranges, public val command
|
||||
}
|
||||
|
||||
val operatorArguments = OperatorArguments(
|
||||
editor.vimStateMachine.isOperatorPending,
|
||||
editor.vimStateMachine.isOperatorPending(editor.mode),
|
||||
0,
|
||||
editor.mode,
|
||||
)
|
||||
|
@ -73,7 +73,7 @@ public data class NormalCommand(val ranges: Ranges, val argument: String) : Comm
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.reset(editor)
|
||||
for (key in keys) {
|
||||
keyHandler.handleKey(editor, key, context, useMappings, true)
|
||||
keyHandler.handleKey(editor, key, context, useMappings, true, keyHandler.keyHandlerState)
|
||||
}
|
||||
|
||||
// Exit if state leaves as insert or cmd_line
|
||||
|
Loading…
Reference in New Issue
Block a user