1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-11-24 22:42:53 +01:00

Compare commits

..

34 Commits

Author SHA1 Message Date
9101ca1afc
Set plugin version to chylex-37 2024-07-09 05:56:31 +02:00
7e493e0188
Revert per-caret registers 2024-07-09 05:56:31 +02:00
78b5540636
Revert "Factor disposable objects on editor opening"
This reverts commit 1fa78935
2024-07-09 05:48:41 +02:00
97ba6ae997
Fix(VIM-3364): Exception with mapped Generate action 2024-07-09 05:48:41 +02:00
9ac1a14604
Apply scrolloff after executing native IDEA actions 2024-07-09 05:48:35 +02:00
3c20feba8d
Stay on same line after reindenting 2024-07-09 05:48:29 +02:00
d23475818a
Update search register when using f/t 2024-07-09 05:48:29 +02:00
7f5c9b56ef
Automatically add unambiguous imports after running a macro 2024-07-09 05:48:29 +02:00
c3f5cb4866
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-07-09 05:48:29 +02:00
7a25fbfd31
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-07-09 05:48:29 +02:00
444880b153
Add support for count for visual and line motion surround 2024-07-09 05:48:29 +02:00
0ae06e46da
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-07-09 05:48:29 +02:00
98e085e636
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-07-09 05:48:28 +02:00
67c60ae8cc
Respect count with <Action> mappings 2024-07-09 05:48:28 +02:00
ffd2433da3
Change matchit plugin to use HTML patterns in unrecognized files 2024-07-09 05:48:28 +02:00
cfe219250c
Reset insert mode when switching active editor 2024-07-09 05:48:28 +02:00
68bdae1afc
Remove update checker 2024-07-09 05:48:28 +02:00
77859e03e7
Set custom plugin version 2024-07-09 05:48:28 +02:00
cfd05be288
Revert "Migrate to IntelliJ Platform Gradle Plugin 2.0"
This reverts commit 4913b13a
2024-07-09 05:48:28 +02:00
filipp
c0419d6018 Fix project leak 2024-07-08 13:09:08 +03:00
filipp
ea98e50f65 Sometimes safety is a bad thing 2024-07-08 13:09:08 +03:00
filipp
168174e383 Add missing resetOpPending to KeyHandler.reset 2024-07-08 13:09:08 +03:00
filipp
53cd4e1b88 Remove KeyHandlerStateResetter
It's an oneliner that we can live without
2024-07-08 13:09:08 +03:00
filipp
27e3561bb8 Safer VimListenersNotifier 2024-07-08 13:09:08 +03:00
filipp
9bb9cb13e3 Fix possible bug in the ExOutputModel.show() method and add documentation 2024-07-08 13:09:08 +03:00
filipp
16455f7241 Remove the update() method from VimOutputPanel 2024-07-08 13:09:08 +03:00
filipp
82225aa519 Use new interface instead of the old one 2024-07-08 13:09:08 +03:00
Filipp Vakhitov
5f2baefc6c Introduce new interface for VimOutputPanel to support the output panel in Fleet
Why is the old interface bad?
- it is not obvious. You cannot create a new panel or check if it is already created. Only "getOrCreate" it
- output panel is bound to editor while in Vim it is global
- we have the `isActive` field and the `clear()` method at the same time, because interface implies that you store the same instance of the panel and reactivate it for each output and I don't like it. We also can forget to call `clear()` after reusing panel
- we cannot "build" output before showing to make the panel more efficient. With multiple carets we can only cal `output(oldText + newText)` for each caret, and it is bad. (imagine we have global command with a lot of matches and for each time we will need to call the `output(oldText + newText)`)
- the `output()` method shows panel, activates it and updates it
- there are more things that I do not like, but the points above should be already enough
2024-07-08 13:09:08 +03:00
Filipp Vakhitov
cedcf39723 Fix(VIM-3461): Focus regression 2024-07-08 13:09:08 +03:00
Filipp Vakhitov
4925d9aada Remove ideaglobalmode option
Mode is global now
2024-07-08 13:09:08 +03:00
Filipp Vakhitov
f3e6df32d0 Fix(VIM-3462): IdeaVim not responsive occasionally
This bug was caused by two reasons:
1. KeyHandler state is not longer per-editor and we can't reset it on editor creation
2. We do not need to do such things on editor creation. What actually matters is focusing the editor
2024-07-08 13:09:08 +03:00
Filipp Vakhitov
5aaa8752af Move to new API from deprecated one 2024-07-08 13:09:08 +03:00
Filipp Vakhitov
a1d214316c Make VimStateMachine global
It is global in Vim
2024-07-08 13:09:08 +03:00
filipp
8a1e3eb066 Move toggleInsertOverwrite() to VimEditor
If we execute it from VimStateMachine directly, then mode change listeners are not notified
2024-07-08 13:09:08 +03:00
77 changed files with 486 additions and 379 deletions

View File

@ -21,7 +21,7 @@ ideaVersion=2024.1.1
ideaType=IC ideaType=IC
downloadIdeaSources=true downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-36 version=chylex-37
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.22 remoteRobotVersion=0.11.22
antlrVersion=4.10.1 antlrVersion=4.10.1

View File

@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
@ -102,7 +102,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val argument = cmd.argument ?: return false val argument = cmd.argument ?: return false
if (!editor.vimStateMachine.isDotRepeatInProgress) { if (!editor.inRepeatMode) {
argumentCaptured = argument argumentCaptured = argument
} }
val range = getMotionRange(editor, context, argument, operatorArguments) val range = getMotionRange(editor, context, argument, operatorArguments)

View File

@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
@CommandOrMotion(keys = ["."], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["."], modes = [Mode.NORMAL])
@ -25,7 +24,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_WRITABLE override val type: Command.Type = Command.Type.OTHER_WRITABLE
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val state = editor.vimStateMachine val state = injector.vimState
val lastCommand = VimRepeater.lastChangeCommand val lastCommand = VimRepeater.lastChangeCommand
if (lastCommand == null && Extension.lastExtensionHandler == null) return false if (lastCommand == null && Extension.lastExtensionHandler == null) return false

View File

@ -9,51 +9,75 @@ package com.maddyhome.idea.vim.ex
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimExOutputPanel import com.maddyhome.idea.vim.api.VimOutputPanel
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimExOutput import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel import com.maddyhome.idea.vim.ui.ExOutputPanel
import java.lang.ref.WeakReference
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing // TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel { class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanel {
private var isActiveInTestMode = false private var isActiveInTestMode = false
override val isActive: Boolean val editor get() = myEditor.get()
val isActive: Boolean
get() = if (!ApplicationManager.getApplication().isUnitTestMode) { get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.isPanelActive(myEditor) editor?.let { ExOutputPanel.getNullablePanel(it) }?.myActive ?: false
} else { } else {
isActiveInTestMode isActiveInTestMode
} }
override var text: String? = null override fun addText(text: String, isNewLine: Boolean) {
if (this.text.isNotEmpty() && isNewLine) this.text += "\n$text" else this.text += text
}
override fun show() {
if (editor == null) return
val currentPanel = injector.outputPanel.getCurrentOutputPanel()
if (currentPanel != null && currentPanel != this) currentPanel.close()
editor!!.vimExOutput = this
val exOutputPanel = ExOutputPanel.getInstance(editor!!)
if (!exOutputPanel.myActive) {
if (ApplicationManager.getApplication().isUnitTestMode) {
isActiveInTestMode = true
} else {
exOutputPanel.activate()
}
}
}
override var text: String = ""
get() = if (!ApplicationManager.getApplication().isUnitTestMode) { get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).text editor?.let { ExOutputPanel.getInstance(it).text } ?: ""
} else { } else {
// ExOutputPanel always returns a non-null string // ExOutputPanel always returns a non-null string
field ?: "" field
} }
set(value) { set(value) {
// ExOutputPanel will strip a trailing newline. We'll do it now so that tests have the same behaviour. We also // ExOutputPanel will strip a trailing newline. We'll do it now so that tests have the same behaviour. We also
// never pass null to ExOutputPanel, but we do store it for tests, so we know if we're active or not // never pass null to ExOutputPanel, but we do store it for tests, so we know if we're active or not
val newValue = value?.removeSuffix("\n") val newValue = value.removeSuffix("\n")
if (!ApplicationManager.getApplication().isUnitTestMode) { if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(newValue ?: "") editor?.let { ExOutputPanel.getInstance(it).setText(newValue) }
} else { } else {
field = newValue field = newValue
isActiveInTestMode = !newValue.isNullOrEmpty() isActiveInTestMode = newValue.isNotEmpty()
} }
} }
override fun output(text: String) { fun output(text: String) {
this.text = text this.text = text
} }
override fun clear() { fun clear() {
text = null text = ""
} }
override fun close() { override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) { if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).close() editor?.let { ExOutputPanel.getInstance(it).close() }
} }
else { else {
isActiveInTestMode = false isActiveInTestMode = false
@ -65,7 +89,7 @@ class ExOutputModel private constructor(private val myEditor: Editor) : VimExOut
fun getInstance(editor: Editor): ExOutputModel { fun getInstance(editor: Editor): ExOutputModel {
var model = editor.vimExOutput var model = editor.vimExOutput
if (model == null) { if (model == null) {
model = ExOutputModel(editor) model = ExOutputModel(WeakReference(editor))
editor.vimExOutput = model editor.vimExOutput = model
} }
return model return model

View File

@ -24,8 +24,8 @@ import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.TestInputModel import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.helper.noneOfEnum import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.MappingOwner import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@ -151,7 +151,7 @@ object VimExtensionFacade {
/** Returns a single key stroke from the user input similar to 'getchar()'. */ /** Returns a single key stroke from the user input similar to 'getchar()'. */
@JvmStatic @JvmStatic
fun inputKeyStroke(editor: Editor): KeyStroke { fun inputKeyStroke(editor: Editor): KeyStroke {
if (editor.vim.vimStateMachine.isDotRepeatInProgress) { if (editor.vim.inRepeatMode) {
val input = Extension.consumeKeystroke() val input = Extension.consumeKeystroke()
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input") LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}") return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")

View File

@ -46,7 +46,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.handler.TextObjectActionHandler import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.PsiHelper import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
@ -65,7 +64,7 @@ internal class CommentaryExtension : VimExtension {
selectionType: SelectionType, selectionType: SelectionType,
resetCaret: Boolean = true, resetCaret: Boolean = true,
): Boolean { ): Boolean {
val mode = editor.vimStateMachine.mode val mode = editor.mode
if (mode !is Mode.VISUAL) { if (mode !is Mode.VISUAL) {
editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset) editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset)
} }

View File

@ -218,45 +218,6 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE); editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE);
} }
// We add Vim bindings to all opened editors, including editors used as UI controls rather than just project file
// editors. This includes editors used as part of the UI, such as the VCS commit message, or used as read-only
// viewers for text output, such as log files in run configurations or the Git Console tab. And editors are used for
// interactive stdin/stdout for console-based run configurations.
// We want to provide an intuitive experience for working with these additional editors, so we automatically switch
// to INSERT mode if they are interactive editors. Recognising these can be a bit tricky.
// These additional interactive editors are not file-based, but must have a writable document. However, log output
// documents are also writable (the IDE is writing new content as it becomes available) just not user-editable. So
// we must also check that the editor is not in read-only "viewer" mode (this includes "rendered" mode, which is
// read-only and also hides the caret).
// Furthermore, interactive stdin/stdout console output in run configurations is hosted in a read-only editor, but
// it can still be edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's
// `isViewer` property and allows typing if the associated process (if any) is still running. We can get the
// editor's console view and check this ourselves, but we have to wait until the editor has finished initialising
// before it's available in user data.
// Finally, we have a special check for diff windows. If we compare against clipboard, we get a diff editor that is
// not file based, is writable, and not a viewer, but we don't want to treat this as an interactive editor.
// Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
Runnable switchToInsertMode = () -> {
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor));
VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
KeyHandler.getInstance().reset(new IjVimEditor(editor));
};
if (!editor.isViewer() &&
!EditorHelper.isFileEditor(editor) &&
editor.getDocument().isWritable() &&
!CommandStateHelper.inInsertMode(editor) &&
editor.getEditorKind() != EditorKind.DIFF) {
switchToInsertMode.run();
}
ApplicationManager.getApplication().invokeLater(
() -> {
if (editor.isDisposed()) return;
ConsoleViewImpl consoleView = editor.getUserData(ConsoleViewImpl.CONSOLE_VIEW_IN_EDITOR_VIEW);
if (consoleView != null && consoleView.isRunning() && !CommandStateHelper.inInsertMode(editor)) {
switchToInsertMode.run();
}
});
updateCaretsVisualAttributes(new IjVimEditor(editor)); updateCaretsVisualAttributes(new IjVimEditor(editor));
} }
@ -416,7 +377,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// Note that IDE scale is handled by LafManager.lookAndFeelChanged // Note that IDE scale is handled by LafManager.lookAndFeelChanged
VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine(); VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine();
if (activeCommandLine != null) { if (activeCommandLine != null) {
injector.getProcessGroup().cancelExEntry(new IjVimEditor(editor), false); injector.getProcessGroup().cancelExEntry(new IjVimEditor(editor), true, false);
} }
ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor); ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor);
if (exOutputModel != null) { if (exOutputModel != null) {

View File

@ -271,7 +271,7 @@ class FileGroup : VimFileBase() {
val msg = StringBuilder() val msg = StringBuilder()
val doc = editor.document val doc = editor.document
if (getInstance(IjVimEditor(editor)).mode !is VISUAL) { if (injector.vimState.mode !is VISUAL) {
val lp = editor.caretModel.logicalPosition val lp = editor.caretModel.logicalPosition
val col = editor.caretModel.offset - doc.getLineStartOffset(lp.line) val col = editor.caretModel.offset - doc.getLineStartOffset(lp.line)
var endoff = doc.getLineEndOffset(lp.line) var endoff = doc.getLineEndOffset(lp.line)

View File

@ -47,7 +47,6 @@ import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.listener.AppCodeTemplates import com.maddyhome.idea.vim.listener.AppCodeTemplates
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
@ -307,13 +306,13 @@ internal class MotionGroup : VimMotionGroupBase() {
val editor = fileEditor.editor val editor = fileEditor.editor
if (!editor.isDisposed) { if (!editor.isDisposed) {
editor.vim.let { vimEditor -> editor.vim.let { vimEditor ->
when (vimEditor.vimStateMachine.mode) { when (vimEditor.mode) {
is Mode.VISUAL -> { is Mode.VISUAL -> {
vimEditor.exitVisualMode() vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor) KeyHandler.getInstance().reset(vimEditor)
} }
is Mode.CMD_LINE -> { is Mode.CMD_LINE -> {
injector.processGroup.cancelExEntry(vimEditor, false) injector.processGroup.cancelExEntry(vimEditor, refocusOwningEditor = false, resetCaret = false)
ExOutputModel.tryGetInstance(editor)?.close() ExOutputModel.tryGetInstance(editor)?.close()
} }
else -> {} else -> {}

View File

@ -73,7 +73,7 @@ internal object GuicursorChangeListener : EffectiveOptionValueChangeListener {
} }
private fun Editor.guicursorMode(): GuiCursorMode { private fun Editor.guicursorMode(): GuiCursorMode {
return GuiCursorMode.fromMode(vim.mode, vim.vimStateMachine.isReplaceCharacter) return GuiCursorMode.fromMode(vim.mode, injector.vimState.isReplaceCharacter)
} }
/** /**
@ -146,21 +146,15 @@ internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener { class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
override fun isReplaceCharChanged(editor: VimEditor) { override fun isReplaceCharChanged(editor: VimEditor) {
updateCaretsVisual(editor) updateCaretsVisual()
} }
override fun modeChanged(editor: VimEditor, oldMode: Mode) { override fun modeChanged(editor: VimEditor, oldMode: Mode) {
updateCaretsVisual(editor) updateCaretsVisual()
} }
private fun updateCaretsVisual(editor: VimEditor) { private fun updateCaretsVisual() {
if (injector.globalOptions().ideaglobalmode) {
updateAllEditorsCaretsVisual() updateAllEditorsCaretsVisual()
} else {
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
}
} }
fun updateAllEditorsCaretsVisual() { fun updateAllEditorsCaretsVisual() {

View File

@ -34,4 +34,4 @@ val Editor.inVisualMode: Boolean
@get:JvmName("inExMode") @get:JvmName("inExMode")
internal val Editor.inExMode internal val Editor.inExMode
get() = this.vim.vimStateMachine.mode is Mode.CMD_LINE get() = this.vim.mode is Mode.CMD_LINE

View File

@ -31,7 +31,7 @@ import com.maddyhome.idea.vim.state.mode.returnTo
internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) { internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
if (!this.vim.inSelectMode) return if (!this.vim.inSelectMode) return
val returnTo = this.vim.vimStateMachine.mode.returnTo val returnTo = this.vim.mode.returnTo
when (returnTo) { when (returnTo) {
ReturnTo.INSERT -> { ReturnTo.INSERT -> {
this.vim.mode = Mode.INSERT this.vim.mode = Mode.INSERT
@ -64,7 +64,7 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) { internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
if (!this.inSelectMode) return if (!this.inSelectMode) return
val returnTo = this.vimStateMachine.mode.returnTo val returnTo = this.mode.returnTo
when (returnTo) { when (returnTo) {
ReturnTo.INSERT -> { ReturnTo.INSERT -> {
this.mode = Mode.INSERT this.mode = Mode.INSERT

View File

@ -180,7 +180,7 @@ internal object ScrollViewHelper {
} }
private fun getScrollJump(editor: VimEditor, height: Int): Int { private fun getScrollJump(editor: VimEditor, height: Int): Int {
val flags = VimStateMachine.getInstance(editor).executingCommandFlags val flags = injector.vimState.executingCommandFlags
val scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) val scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP)
// Default value is 1. Zero is a valid value, but we normalise to 1 - we always want to scroll at least one line // Default value is 1. Zero is a valid value, but we normalise to 1 - we always want to scroll at least one line
@ -203,7 +203,7 @@ internal object ScrollViewHelper {
val caretColumn = position.column val caretColumn = position.column
val halfWidth = getApproximateScreenWidth(editor) / 2 val halfWidth = getApproximateScreenWidth(editor) / 2
val scrollOffset = getNormalizedSideScrollOffset(editor) val scrollOffset = getNormalizedSideScrollOffset(editor)
val flags = VimStateMachine.getInstance(vimEditor).executingCommandFlags val flags = injector.vimState.executingCommandFlags
val allowSidescroll = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP) val allowSidescroll = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP)
val sidescroll = injector.options(vimEditor).sidescroll val sidescroll = injector.options(vimEditor).sidescroll
val offsetLeft = caretColumn - (currentVisualLeftColumn + scrollOffset) val offsetLeft = caretColumn - (currentVisualLeftColumn + scrollOffset)

View File

@ -103,7 +103,6 @@ internal var Editor.vimInitialised: Boolean by userDataOr { false }
// ------------------ Editor // ------------------ Editor
internal fun unInitializeEditor(editor: Editor) { internal fun unInitializeEditor(editor: Editor) {
editor.vimLastSelectionType = null editor.vimLastSelectionType = null
editor.vimStateMachine = null
editor.vimMorePanel = null editor.vimMorePanel = null
editor.vimExOutput = null editor.vimExOutput = null
editor.vimLastHighlighters = null editor.vimLastHighlighters = null
@ -118,7 +117,6 @@ internal var Editor.vimIncsearchCurrentMatchOffset: Int? by userData()
* @see :help visualmode() * @see :help visualmode()
*/ */
internal var Editor.vimLastSelectionType: SelectionType? by userData() internal var Editor.vimLastSelectionType: SelectionType? by userData()
internal var Editor.vimStateMachine: VimStateMachine? by userData()
internal var Editor.vimEditorGroup: Boolean by userDataOr { false } internal var Editor.vimEditorGroup: Boolean by userDataOr { false }
internal var Editor.vimHasRelativeLineNumbersInstalled: Boolean by userDataOr { false } internal var Editor.vimHasRelativeLineNumbersInstalled: Boolean by userDataOr { false }
internal var Editor.vimMorePanel: ExOutputPanel? by userData() internal var Editor.vimMorePanel: ExOutputPanel? by userData()

View File

@ -0,0 +1,72 @@
/*
* 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.listener
import com.intellij.execution.impl.ConsoleViewImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.EditorKind
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
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.common.EditorListener
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.newapi.ij
/**
* This listener is similar to the one we introduce in vim-engine to handle focus change,
* However, in IJ we would like to start editing in some editors in INSERT mode (e.g., consoles)
* It is different to we had previously. Now we go to INSERT mode not only when we focus on the console the first time, but every time.
* Going to INSERT on every focus is easier to implement and more consistent (behavior is always the same, you don't have to remember if you are focusing a console the first time or not)
*/
class IJEditorFocusListener : EditorListener {
override fun focusGained(editor: VimEditor) {
// We add Vim bindings to all opened editors, including editors used as UI controls rather than just project file
// editors. This includes editors used as part of the UI, such as the VCS commit message, or used as read-only
// viewers for text output, such as log files in run configurations or the Git Console tab. And editors are used for
// interactive stdin/stdout for console-based run configurations.
// We want to provide an intuitive experience for working with these additional editors, so we automatically switch
// to INSERT mode if they are interactive editors. Recognising these can be a bit tricky.
// These additional interactive editors are not file-based, but must have a writable document. However, log output
// documents are also writable (the IDE is writing new content as it becomes available) just not user-editable. So
// we must also check that the editor is not in read-only "viewer" mode (this includes "rendered" mode, which is
// read-only and also hides the caret).
// Furthermore, interactive stdin/stdout console output in run configurations is hosted in a read-only editor, but
// it can still be edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's
// `isViewer` property and allows typing if the associated process (if any) is still running. We can get the
// editor's console view and check this ourselves, but we have to wait until the editor has finished initialising
// before it's available in user data.
// Finally, we have a special check for diff windows. If we compare against clipboard, we get a diff editor that is
// not file based, is writable, and not a viewer, but we don't want to treat this as an interactive editor.
// Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
val switchToInsertMode = Runnable {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
VimPlugin.getChange().insertBeforeCursor(editor, context)
}
val ijEditor = editor.ij
if (!ijEditor.isViewer &&
!EditorHelper.isFileEditor(ijEditor) &&
ijEditor.document.isWritable &&
!ijEditor.inInsertMode && ijEditor.editorKind != EditorKind.DIFF
) {
switchToInsertMode.run()
}
ApplicationManager.getApplication().invokeLater {
if (ijEditor.isDisposed) return@invokeLater
val consoleView: ConsoleViewImpl? = ijEditor.getUserData(ConsoleViewImpl.CONSOLE_VIEW_IN_EDITOR_VIEW)
if (consoleView != null && consoleView.isRunning && !ijEditor.inInsertMode) {
switchToInsertMode.run()
}
}
KeyHandler.getInstance().reset(editor)
}
}

View File

@ -37,7 +37,6 @@ import com.maddyhome.idea.vim.action.VimShortcutKeyAction
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.NotificationService import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
@ -128,7 +127,6 @@ internal object IdeaSpecifics {
} }
) { ) {
editor?.let { editor?.let {
val commandState = it.vim.vimStateMachine
it.vim.mode = Mode.NORMAL() it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim) VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim) KeyHandler.getInstance().reset(it.vim)

View File

@ -56,7 +56,6 @@ import com.intellij.util.ExceptionUtil
import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rd.util.lifetime.Lifetime
import com.maddyhome.idea.vim.EventFacade import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.KeyHandlerStateResetter
import com.maddyhome.idea.vim.VimKeyListener import com.maddyhome.idea.vim.VimKeyListener
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimTypedActionHandler import com.maddyhome.idea.vim.VimTypedActionHandler
@ -178,7 +177,7 @@ internal object VimListenerManager {
injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener) injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener)
injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener) injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener)
injector.listenersNotifier.myEditorListeners.add(KeyHandlerStateResetter()) injector.listenersNotifier.myEditorListeners.add(IJEditorFocusListener())
injector.listenersNotifier.myEditorListeners.add(ShowCmdWidgetUpdater()) injector.listenersNotifier.myEditorListeners.add(ShowCmdWidgetUpdater())
} }
@ -746,7 +745,7 @@ internal object VimListenerManager {
val editor = event.editor val editor = event.editor
val commandLine = injector.commandLine.getActiveCommandLine() val commandLine = injector.commandLine.getActiveCommandLine()
if (commandLine != null) { if (commandLine != null) {
injector.processGroup.cancelExEntry(editor.vim, false) injector.processGroup.cancelExEntry(editor.vim, refocusOwningEditor = true, resetCaret = false)
} }
ExOutputModel.tryGetInstance(editor)?.close() ExOutputModel.tryGetInstance(editor)?.close()
@ -777,7 +776,7 @@ internal object VimListenerManager {
) { ) {
val commandLine = injector.commandLine.getActiveCommandLine() val commandLine = injector.commandLine.getActiveCommandLine()
if (commandLine != null) { if (commandLine != null) {
injector.processGroup.cancelExEntry(event.editor.vim, false) injector.processGroup.cancelExEntry(event.editor.vim, refocusOwningEditor = true, resetCaret = false)
} }
ExOutputModel.getInstance(event.editor).close() ExOutputModel.getInstance(event.editor).close()

View File

@ -151,7 +151,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
return editor.caretModel.allCarets.map { IjVimCaret(it) } return editor.caretModel.allCarets.map { IjVimCaret(it) }
} }
override var isFirstCaret = false override var isFirstCaret = true
override var isReversingCarets = false override var isReversingCarets = false
@Suppress("ideavimRunForEachCaret") @Suppress("ideavimRunForEachCaret")
@ -159,7 +159,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
if (editor.vim.inBlockSelection) { if (editor.vim.inBlockSelection) {
action(IjVimCaret(editor.caretModel.primaryCaret)) action(IjVimCaret(editor.caretModel.primaryCaret))
} else { } else {
isFirstCaret = true
try { try {
editor.caretModel.runForEachCaret({ editor.caretModel.runForEachCaret({
if (it.isValid) { if (it.isValid) {
@ -168,13 +167,12 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
} }
}, false) }, false)
} finally { } finally {
isFirstCaret = false isFirstCaret = true
} }
} }
} }
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) { override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
isFirstCaret = true
isReversingCarets = reverse isReversingCarets = reverse
try { try {
editor.caretModel.runForEachCaret({ editor.caretModel.runForEachCaret({
@ -182,7 +180,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
isFirstCaret = false isFirstCaret = false
}, reverse) }, reverse)
} finally { } finally {
isFirstCaret = false isFirstCaret = true
isReversingCarets = false isReversingCarets = false
} }
} }

View File

@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.components.serviceIfCreated import com.intellij.openapi.components.serviceIfCreated
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
import com.maddyhome.idea.vim.api.EngineEditorHelper import com.maddyhome.idea.vim.api.EngineEditorHelper
import com.maddyhome.idea.vim.api.ExecutionContextManager import com.maddyhome.idea.vim.api.ExecutionContextManager
@ -28,8 +27,6 @@ import com.maddyhome.idea.vim.api.VimDigraphGroup
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimEditorGroup import com.maddyhome.idea.vim.api.VimEditorGroup
import com.maddyhome.idea.vim.api.VimEnabler import com.maddyhome.idea.vim.api.VimEnabler
import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.api.VimExOutputPanelService
import com.maddyhome.idea.vim.api.VimExtensionRegistrator import com.maddyhome.idea.vim.api.VimExtensionRegistrator
import com.maddyhome.idea.vim.api.VimFile import com.maddyhome.idea.vim.api.VimFile
import com.maddyhome.idea.vim.api.VimInjector import com.maddyhome.idea.vim.api.VimInjector
@ -41,6 +38,7 @@ import com.maddyhome.idea.vim.api.VimMarkService
import com.maddyhome.idea.vim.api.VimMessages import com.maddyhome.idea.vim.api.VimMessages
import com.maddyhome.idea.vim.api.VimMotionGroup import com.maddyhome.idea.vim.api.VimMotionGroup
import com.maddyhome.idea.vim.api.VimOptionGroup import com.maddyhome.idea.vim.api.VimOptionGroup
import com.maddyhome.idea.vim.api.VimOutputPanelService
import com.maddyhome.idea.vim.api.VimProcessGroup import com.maddyhome.idea.vim.api.VimProcessGroup
import com.maddyhome.idea.vim.api.VimPsiService import com.maddyhome.idea.vim.api.VimPsiService
import com.maddyhome.idea.vim.api.VimRedrawService import com.maddyhome.idea.vim.api.VimRedrawService
@ -79,9 +77,7 @@ import com.maddyhome.idea.vim.helper.IjActionExecutor
import com.maddyhome.idea.vim.helper.IjEditorHelper import com.maddyhome.idea.vim.helper.IjEditorHelper
import com.maddyhome.idea.vim.helper.IjVimStringParser import com.maddyhome.idea.vim.helper.IjVimStringParser
import com.maddyhome.idea.vim.helper.UndoRedoHelper import com.maddyhome.idea.vim.helper.UndoRedoHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.history.VimHistory import com.maddyhome.idea.vim.history.VimHistory
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.macro.VimMacro import com.maddyhome.idea.vim.macro.VimMacro
import com.maddyhome.idea.vim.put.VimPut import com.maddyhome.idea.vim.put.VimPut
import com.maddyhome.idea.vim.register.VimRegisterGroup import com.maddyhome.idea.vim.register.VimRegisterGroup
@ -105,12 +101,6 @@ internal class IjVimInjector : VimInjectorBase() {
override val actionExecutor: VimActionExecutor override val actionExecutor: VimActionExecutor
get() = service<IjActionExecutor>() get() = service<IjActionExecutor>()
override val exOutputPanel: VimExOutputPanelService
get() = object : VimExOutputPanelService {
override fun getPanel(editor: VimEditor): VimExOutputPanel {
return ExOutputModel.getInstance(editor.ij)
}
}
override val historyGroup: VimHistory override val historyGroup: VimHistory
get() = service<HistoryGroup>() get() = service<HistoryGroup>()
override val extensionRegistrator: VimExtensionRegistrator override val extensionRegistrator: VimExtensionRegistrator
@ -193,6 +183,8 @@ internal class IjVimInjector : VimInjectorBase() {
get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
override val commandLine: VimCommandLineService override val commandLine: VimCommandLineService
get() = service() get() = service()
override val outputPanel: VimOutputPanelService
get() = service()
override val optionGroup: VimOptionGroup override val optionGroup: VimOptionGroup
get() = service() get() = service()
@ -206,21 +198,14 @@ internal class IjVimInjector : VimInjectorBase() {
override val redrawService: VimRedrawService override val redrawService: VimRedrawService
get() = service() get() = service()
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
override fun commandStateFor(editor: VimEditor): VimStateMachine { override fun commandStateFor(editor: VimEditor): VimStateMachine {
var res = editor.ij.vimStateMachine return vimState
if (res == null) {
res = VimStateMachineImpl()
editor.ij.vimStateMachine = res
}
return res
} }
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
override fun commandStateFor(editor: Any): VimStateMachine { override fun commandStateFor(editor: Any): VimStateMachine {
return when (editor) { return vimState
is VimEditor -> this.commandStateFor(editor)
is Editor -> this.commandStateFor(IjVimEditor(editor))
else -> error("Unexpected type: $editor")
}
} }
override val engineEditorHelper: EngineEditorHelper override val engineEditorHelper: EngineEditorHelper

View File

@ -58,7 +58,7 @@ public class ExOutputPanel extends JPanel {
private @Nullable LayoutManager myOldLayout = null; private @Nullable LayoutManager myOldLayout = null;
private boolean myWasOpaque = false; private boolean myWasOpaque = false;
private boolean myActive = false; public boolean myActive = false;
private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class); private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class);
@ -90,12 +90,16 @@ public class ExOutputPanel extends JPanel {
updateUI(); updateUI();
} }
public static @Nullable ExOutputPanel getNullablePanel(@NotNull Editor editor) {
return UserDataManager.getVimMorePanel(editor);
}
public static boolean isPanelActive(@NotNull Editor editor) { public static boolean isPanelActive(@NotNull Editor editor) {
return UserDataManager.getVimMorePanel(editor) != null; return getNullablePanel(editor) != null;
} }
public static @NotNull ExOutputPanel getInstance(@NotNull Editor editor) { public static @NotNull ExOutputPanel getInstance(@NotNull Editor editor) {
ExOutputPanel panel = UserDataManager.getVimMorePanel(editor); ExOutputPanel panel = getNullablePanel(editor);
if (panel == null) { if (panel == null) {
panel = new ExOutputPanel(editor); panel = new ExOutputPanel(editor);
UserDataManager.setVimMorePanel(editor, panel); UserDataManager.setVimMorePanel(editor, panel);
@ -192,7 +196,7 @@ public class ExOutputPanel extends JPanel {
/** /**
* Turns on the more window for the given editor * Turns on the more window for the given editor
*/ */
private void activate() { public void activate() {
JRootPane root = SwingUtilities.getRootPane(myEditor.getContentComponent()); JRootPane root = SwingUtilities.getRootPane(myEditor.getContentComponent());
myOldGlass = (JComponent)root.getGlassPane(); myOldGlass = (JComponent)root.getGlassPane();
if (myOldGlass != null) { if (myOldGlass != null) {

View File

@ -15,8 +15,8 @@ import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.TestInputModel import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.helper.isCloseKeyStroke import com.maddyhome.idea.vim.helper.isCloseKeyStroke
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
@ -31,7 +31,7 @@ class ExEntryPanelService : VimCommandLineService {
override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? { override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
val editor = vimEditor.ij val editor = vimEditor.ij
if (vimEditor.vimStateMachine.isDotRepeatInProgress) { if (vimEditor.inRepeatMode) {
val input = Extension.consumeString() val input = Extension.consumeString()
return input ?: error("Not enough strings saved: ${Extension.lastExtensionHandler}") return input ?: error("Not enough strings saved: ${Extension.lastExtensionHandler}")
} }

View File

@ -293,7 +293,7 @@ public class ExTextField extends JTextField {
clearCurrentAction(); clearCurrentAction();
Editor editor = ExEntryPanel.instance.getEditor(); Editor editor = ExEntryPanel.instance.getEditor();
if (editor != null) { if (editor != null) {
VimPlugin.getProcess().cancelExEntry(new IjVimEditor(editor), true); VimPlugin.getProcess().cancelExEntry(new IjVimEditor(editor), true, true);
} }
} }

View File

@ -0,0 +1,34 @@
/*
* 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.ui.ex
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimOutputPanel
import com.maddyhome.idea.vim.api.VimOutputPanelServiceBase
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.newapi.ij
import java.lang.ref.WeakReference
class IjOutputPanelService : VimOutputPanelServiceBase() {
private var activeOutputPanel: VimOutputPanel? = null
override fun getCurrentOutputPanel(): VimOutputPanel? {
return activeOutputPanel?.takeIf {
(it as ExOutputModel)
it.isActive && it.editor != null
}
}
override fun create(editor: VimEditor, context: ExecutionContext): VimOutputPanel {
val panel = ExOutputModel(WeakReference(editor.ij))
activeOutputPanel = panel
return panel
}
}

View File

@ -24,6 +24,8 @@
serviceInterface="com.maddyhome.idea.vim.api.VimProcessGroup"/> serviceInterface="com.maddyhome.idea.vim.api.VimProcessGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.ExEntryPanelService" <applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.ExEntryPanelService"
serviceInterface="com.maddyhome.idea.vim.api.VimCommandLineService"/> serviceInterface="com.maddyhome.idea.vim.api.VimCommandLineService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.IjOutputPanelService"
serviceInterface="com.maddyhome.idea.vim.api.VimOutputPanelService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.DigraphGroup" <applicationService serviceImplementation="com.maddyhome.idea.vim.group.DigraphGroup"
serviceInterface="com.maddyhome.idea.vim.api.VimDigraphGroup"/> serviceInterface="com.maddyhome.idea.vim.api.VimDigraphGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.HistoryGroup"/> <applicationService serviceImplementation="com.maddyhome.idea.vim.group.HistoryGroup"/>

View File

@ -10,7 +10,6 @@ package org.jetbrains.plugins.ideavim.action
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
@ -73,8 +72,6 @@ class MotionActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
doTest(listOf("12", "<Esc>"), content, content, Mode.NORMAL()) doTest(listOf("12", "<Esc>"), content, content, Mode.NORMAL())
assertPluginError(false) assertPluginError(false)
val vimCommandState = fixture.editor.vimStateMachine
kotlin.test.assertNotNull(vimCommandState)
assertEmpty(KeyHandler.getInstance().keyHandlerState.commandBuilder.keys.toList()) assertEmpty(KeyHandler.getInstance().keyHandlerState.commandBuilder.keys.toList())
} }

View File

@ -154,6 +154,8 @@ class SetCommandTest : VimTestCase() {
// 'fileencoding' defaults to "", but is automatically detected as UTF-8 // 'fileencoding' defaults to "", but is automatically detected as UTF-8
enterCommand("set number relativenumber scrolloff nrformats") enterCommand("set number relativenumber scrolloff nrformats")
assertExOutput(" nrformats=hex scrolloff=0")
injector.outputPanel.getCurrentOutputPanel()?.close()
assertCommandOutput("set", assertCommandOutput("set",
""" """
|--- Options --- |--- Options ---
@ -169,21 +171,20 @@ class SetCommandTest : VimTestCase() {
assertCommandOutput("set all", assertCommandOutput("set all",
""" """
|--- Options --- |--- Options ---
|noargtextobj ideamarks scrolljump=1 notextobj-indent |noargtextobj ideamarks scroll=0 nosurround
|nobomb ideawrite=all scrolloff=0 textwidth=0 |nobomb ideawrite=all scrolljump=1 notextobj-entire
|nobreakindent noignorecase selectmode= timeout |nobreakindent noignorecase scrolloff=0 notextobj-indent
| colorcolumn= noincsearch shellcmdflag=-x timeoutlen=1000 | colorcolumn= noincsearch selectmode= textwidth=0
|nocommentary nolist shellxescape=@ notrackactionids |nocommentary nolist shellcmdflag=-x timeout
|nocursorline nomatchit shellxquote={ undolevels=1000 |nocursorline nomatchit shellxescape=@ timeoutlen=1000
|nodigraph maxmapdepth=20 showcmd virtualedit= |nodigraph maxmapdepth=20 shellxquote={ notrackactionids
|noexchange more showmode novisualbell |noexchange more showcmd undolevels=1000
| fileformat=unix nomultiple-cursors sidescroll=0 visualdelay=100 | fileformat=unix nomultiple-cursors showmode virtualedit=
|nogdefault noNERDTree sidescrolloff=0 whichwrap=b,s |nogdefault noNERDTree sidescroll=0 novisualbell
|nohighlightedyank nrformats=hex nosmartcase wrap |nohighlightedyank nrformats=hex sidescrolloff=0 visualdelay=100
| history=50 nonumber nosneak wrapscan | history=50 nonumber nosmartcase whichwrap=b,s
|nohlsearch operatorfunc= startofline |nohlsearch operatorfunc= nosneak wrap
|noideaglobalmode norelativenumber nosurround |noideajoin norelativenumber startofline wrapscan
|noideajoin scroll=0 notextobj-entire
| clipboard=ideaput,autoselect,exclude:cons\|linux | clipboard=ideaput,autoselect,exclude:cons\|linux
| fileencoding=utf-8 | fileencoding=utf-8
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
@ -220,6 +221,8 @@ class SetCommandTest : VimTestCase() {
// 'fileencoding' defaults to "", but is automatically detected as UTF-8 // 'fileencoding' defaults to "", but is automatically detected as UTF-8
enterCommand("set number relativenumber scrolloff nrformats") enterCommand("set number relativenumber scrolloff nrformats")
assertExOutput(" nrformats=hex scrolloff=0")
injector.outputPanel.getCurrentOutputPanel()?.close()
assertCommandOutput("set!", assertCommandOutput("set!",
""" """
|--- Options --- |--- Options ---
@ -254,7 +257,6 @@ class SetCommandTest : VimTestCase() {
|nohlsearch |nohlsearch
| ide=IntelliJ IDEA Community Edition | ide=IntelliJ IDEA Community Edition
|noideacopypreprocess |noideacopypreprocess
|noideaglobalmode
|noideajoin |noideajoin
| ideamarks | ideamarks
| idearefactormode=select | idearefactormode=select

View File

@ -435,21 +435,21 @@ class SetglobalCommandTest : VimTestCase() {
setOsSpecificOptionsToSafeValues() setOsSpecificOptionsToSafeValues()
assertCommandOutput("setglobal all", """ assertCommandOutput("setglobal all", """
|--- Global option values --- |--- Global option values ---
|noargtextobj noideajoin scroll=0 notextobj-entire |noargtextobj ideamarks scrolljump=1 notextobj-indent
|nobomb ideamarks scrolljump=1 notextobj-indent |nobomb ideawrite=all scrolloff=0 textwidth=0
|nobreakindent ideawrite=all scrolloff=0 textwidth=0 |nobreakindent noignorecase selectmode= timeout
| colorcolumn= noignorecase selectmode= timeout | colorcolumn= noincsearch shellcmdflag=-x timeoutlen=1000
|nocommentary noincsearch shellcmdflag=-x timeoutlen=1000 |nocommentary nolist shellxescape=@ notrackactionids
|nocursorline nolist shellxescape=@ notrackactionids |nocursorline nomatchit shellxquote={ undolevels=1000
|nodigraph nomatchit shellxquote={ undolevels=1000 |nodigraph maxmapdepth=20 showcmd virtualedit=
|noexchange maxmapdepth=20 showcmd virtualedit= |noexchange more showmode novisualbell
| fileencoding= more showmode novisualbell | fileencoding= nomultiple-cursors sidescroll=0 visualdelay=100
| fileformat=unix nomultiple-cursors sidescroll=0 visualdelay=100 | fileformat=unix noNERDTree sidescrolloff=0 whichwrap=b,s
|nogdefault noNERDTree sidescrolloff=0 whichwrap=b,s |nogdefault nrformats=hex nosmartcase wrap
|nohighlightedyank nrformats=hex nosmartcase wrap |nohighlightedyank nonumber nosneak wrapscan
| history=50 nonumber nosneak wrapscan | history=50 operatorfunc= startofline
|nohlsearch operatorfunc= startofline |nohlsearch norelativenumber nosurround
|noideaglobalmode norelativenumber nosurround |noideajoin scroll=0 notextobj-entire
| clipboard=ideaput,autoselect,exclude:cons\|linux | clipboard=ideaput,autoselect,exclude:cons\|linux
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
| ide=IntelliJ IDEA Community Edition | ide=IntelliJ IDEA Community Edition
@ -523,7 +523,6 @@ class SetglobalCommandTest : VimTestCase() {
|nohlsearch |nohlsearch
| ide=IntelliJ IDEA Community Edition | ide=IntelliJ IDEA Community Edition
|noideacopypreprocess |noideacopypreprocess
|noideaglobalmode
|noideajoin |noideajoin
| ideamarks | ideamarks
| idearefactormode=select | idearefactormode=select

View File

@ -489,21 +489,20 @@ class SetlocalCommandTest : VimTestCase() {
setOsSpecificOptionsToSafeValues() setOsSpecificOptionsToSafeValues()
assertCommandOutput("setlocal all", """ assertCommandOutput("setlocal all", """
|--- Local option values --- |--- Local option values ---
|noargtextobj ideamarks scroll=0 notextobj-entire |noargtextobj ideamarks norelativenumber startofline
|nobomb idearefactormode= scrolljump=1 notextobj-indent |nobomb idearefactormode= scroll=0 nosurround
|nobreakindent ideawrite=all scrolloff=-1 textwidth=0 |nobreakindent ideawrite=all scrolljump=1 notextobj-entire
| colorcolumn= noignorecase selectmode= timeout | colorcolumn= noignorecase scrolloff=-1 notextobj-indent
|nocommentary noincsearch shellcmdflag=-x timeoutlen=1000 |nocommentary noincsearch selectmode= textwidth=0
|nocursorline nolist shellxescape=@ notrackactionids |nocursorline nolist shellcmdflag=-x timeout
|nodigraph nomatchit shellxquote={ virtualedit= |nodigraph nomatchit shellxescape=@ timeoutlen=1000
|noexchange maxmapdepth=20 showcmd novisualbell |noexchange maxmapdepth=20 shellxquote={ notrackactionids
| fileformat=unix more showmode visualdelay=100 | fileformat=unix more showcmd virtualedit=
|nogdefault nomultiple-cursors sidescroll=0 whichwrap=b,s |nogdefault nomultiple-cursors showmode novisualbell
|nohighlightedyank noNERDTree sidescrolloff=-1 wrap |nohighlightedyank noNERDTree sidescroll=0 visualdelay=100
| history=50 nrformats=hex nosmartcase wrapscan | history=50 nrformats=hex sidescrolloff=-1 whichwrap=b,s
|nohlsearch nonumber nosneak |nohlsearch nonumber nosmartcase wrap
|noideaglobalmode operatorfunc= startofline |--ideajoin operatorfunc= nosneak wrapscan
|--ideajoin norelativenumber nosurround
| clipboard=ideaput,autoselect,exclude:cons\|linux | clipboard=ideaput,autoselect,exclude:cons\|linux
| fileencoding=utf-8 | fileencoding=utf-8
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
@ -577,7 +576,6 @@ class SetlocalCommandTest : VimTestCase() {
|nohlsearch |nohlsearch
| ide=IntelliJ IDEA Community Edition | ide=IntelliJ IDEA Community Edition
|--ideacopypreprocess |--ideacopypreprocess
|noideaglobalmode
|--ideajoin |--ideajoin
| ideamarks | ideamarks
| idearefactormode= | idearefactormode=

View File

@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.ex.implementation.statements package org.jetbrains.plugins.ideavim.ex.implementation.statements
import com.maddyhome.idea.vim.api.injector
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -55,6 +56,7 @@ class FunctionDeclarationTest : VimTestCase() {
) )
typeText(commandToKeys("echo F1()")) typeText(commandToKeys("echo F1()"))
assertExOutput("5550") assertExOutput("5550")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()")) typeText(commandToKeys("echo F2()"))
assertExOutput("555") assertExOutput("555")
@ -203,10 +205,13 @@ class FunctionDeclarationTest : VimTestCase() {
), ),
) )
typeText(commandToKeys("echo F1()")) typeText(commandToKeys("echo F1()"))
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()")) typeText(commandToKeys("echo F2()"))
assertExOutput("1") assertExOutput("1")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()")) typeText(commandToKeys("echo F2()"))
assertExOutput("2") assertExOutput("2")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()")) typeText(commandToKeys("echo F2()"))
assertExOutput("3") assertExOutput("3")
@ -230,9 +235,11 @@ class FunctionDeclarationTest : VimTestCase() {
), ),
) )
typeText(commandToKeys("echo F1()")) typeText(commandToKeys("echo F1()"))
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()")) typeText(commandToKeys("echo F2()"))
assertExOutput("1") assertExOutput("1")
typeText(commandToKeys("delf! F1")) typeText(commandToKeys("delf! F1"))
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()")) typeText(commandToKeys("echo F2()"))
assertExOutput("2") assertExOutput("2")
@ -260,10 +267,12 @@ class FunctionDeclarationTest : VimTestCase() {
assertPluginError(true) assertPluginError(true)
assertPluginErrorMessageContains("E121: Undefined variable: x") assertPluginErrorMessageContains("E121: Undefined variable: x")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()")) typeText(commandToKeys("echo F2()"))
assertExOutput("10") assertExOutput("10")
assertPluginError(false) assertPluginError(false)
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F1()")) typeText(commandToKeys("echo F1()"))
assertPluginError(true) assertPluginError(true)
assertPluginErrorMessageContains("E121: Undefined variable: x") assertPluginErrorMessageContains("E121: Undefined variable: x")
@ -289,6 +298,7 @@ class FunctionDeclarationTest : VimTestCase() {
assertPluginError(true) assertPluginError(true)
assertPluginErrorMessageContains("E121: Undefined variable: unknownVar") assertPluginErrorMessageContains("E121: Undefined variable: unknownVar")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo x")) typeText(commandToKeys("echo x"))
assertExOutput("10") assertExOutput("10")
assertPluginError(false) assertPluginError(false)
@ -474,7 +484,12 @@ class FunctionDeclarationTest : VimTestCase() {
) )
typeText(commandToKeys("1,4call F1()")) typeText(commandToKeys("1,4call F1()"))
assertPluginError(false) assertPluginError(false)
assertExOutput("1:4") assertExOutput("""
1:4
1:4
1:4
1:4
""".trimIndent())
assertState( assertState(
""" """
----- -----

View File

@ -16,7 +16,6 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.common.CharacterPosition import com.maddyhome.idea.vim.common.CharacterPosition
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.register.RegisterConstants.ALTERNATE_BUFFER_REGISTER import com.maddyhome.idea.vim.register.RegisterConstants.ALTERNATE_BUFFER_REGISTER
import com.maddyhome.idea.vim.register.RegisterConstants.BLACK_HOLE_REGISTER import com.maddyhome.idea.vim.register.RegisterConstants.BLACK_HOLE_REGISTER
@ -168,7 +167,7 @@ object NeovimTesting {
fun vimMode() = neovimApi.mode.get().mode fun vimMode() = neovimApi.mode.get().mode
private fun assertMode(editor: Editor) { private fun assertMode(editor: Editor) {
val ideavimState = editor.vim.vimStateMachine.mode.toVimNotation() val ideavimState = editor.vim.mode.toVimNotation()
val neovimState = vimMode() val neovimState = vimMode()
assertEquals(neovimState, ideavimState) assertEquals(neovimState, ideavimState)
} }

View File

@ -33,7 +33,6 @@ class TestOptionConstants {
const val whichwrap = "whichwrap" const val whichwrap = "whichwrap"
// IdeaVim specific // IdeaVim specific
const val ideaglobalmode = "ideaglobalmode"
const val ideatracetime = "ideatracetime" const val ideatracetime = "ideatracetime"
} }
} }

View File

@ -128,6 +128,7 @@ abstract class VimTestCase {
KeyHandler.getInstance().fullReset(editor.vim) KeyHandler.getInstance().fullReset(editor.vim)
} }
KeyHandler.getInstance().keyHandlerState.reset(Mode.NORMAL()) KeyHandler.getInstance().keyHandlerState.reset(Mode.NORMAL())
injector.vimState.reset()
resetAllOptions() resetAllOptions()
VimPlugin.getKey().resetKeyMappings() VimPlugin.getKey().resetKeyMappings()
VimPlugin.getSearch().resetState() VimPlugin.getSearch().resetState()
@ -221,6 +222,7 @@ abstract class VimTestCase {
VimPlugin.getChange().resetRepeat() VimPlugin.getChange().resetRepeat()
VimPlugin.getKey().savedShortcutConflicts.clear() VimPlugin.getKey().savedShortcutConflicts.clear()
assertTrue(KeyHandler.getInstance().keyStack.isEmpty()) assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
injector.outputPanel.getCurrentOutputPanel()?.close()
// Tear down neovim // Tear down neovim
NeovimTesting.tearDown(testInfo) NeovimTesting.tearDown(testInfo)

View File

@ -156,7 +156,6 @@ private class OptionsVerificator : BeforeTestExecutionCallback, AfterTestExecuti
val LOG by lazy { vimLogger<OptionsVerificator>() } val LOG by lazy { vimLogger<OptionsVerificator>() }
private val ignored = setOf( private val ignored = setOf(
TestOptionConstants.guicursor, TestOptionConstants.guicursor,
TestOptionConstants.ideaglobalmode,
TestOptionConstants.ideatracetime, TestOptionConstants.ideatracetime,
TestIjOptionConstants.ideavimsupport, TestIjOptionConstants.ideavimsupport,
TestOptionConstants.maxmapdepth, TestOptionConstants.maxmapdepth,

View File

@ -27,6 +27,9 @@ class TestInjector(val injector: VimInjector) : VimInjector by injector {
tracers[key] = collector tracers[key] = collector
} }
override val vimState
get() = injector.vimState
override val optionGroup: VimOptionGroup override val optionGroup: VimOptionGroup
get() { get() {
val tracer = tracers[OptionsTracer] as? OptionsTraceCollector val tracer = tracers[OptionsTracer] as? OptionsTraceCollector

View File

@ -18,11 +18,9 @@ import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.MappingProcessor import com.maddyhome.idea.vim.command.MappingProcessor
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.CurrentCommandState import com.maddyhome.idea.vim.common.CurrentCommandState
import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.diagnostic.VimLogger import com.maddyhome.idea.vim.diagnostic.VimLogger
import com.maddyhome.idea.vim.diagnostic.trace import com.maddyhome.idea.vim.diagnostic.trace
import com.maddyhome.idea.vim.diagnostic.vimLogger 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.impl.state.toMappingMode
import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyConsumer
@ -167,7 +165,7 @@ class KeyHandler {
if (commandBuilder.isReady) { if (commandBuilder.isReady) {
logger.trace("Ready command builder. Execute command.") logger.trace("Ready command builder. Execute command.")
executeCommand(editor, context, editor.vimStateMachine, keyState) executeCommand(editor, context, injector.vimState, keyState)
} }
// Don't record the keystroke that stops the recording (unmapped this is `q`) // Don't record the keystroke that stops the recording (unmapped this is `q`)
@ -186,7 +184,7 @@ class KeyHandler {
logger.trace("Command builder is set to BAD") logger.trace("Command builder is set to BAD")
keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
editor.resetOpPending() editor.resetOpPending()
editor.vimStateMachine.resetRegisterPending() injector.vimState.resetRegisterPending()
editor.isReplaceCharacter = false editor.isReplaceCharacter = false
reset(keyState, editor.mode) reset(keyState, editor.mode)
} }
@ -267,6 +265,7 @@ class KeyHandler {
*/ */
fun reset(editor: VimEditor) { fun reset(editor: VimEditor) {
logger.trace { "Reset is executed" } logger.trace { "Reset is executed" }
editor.resetOpPending()
keyHandlerState.partialReset(editor.mode) keyHandlerState.partialReset(editor.mode)
keyHandlerState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode())) keyHandlerState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode()))
} }
@ -298,7 +297,7 @@ class KeyHandler {
injector.messages.clearError() injector.messages.clearError()
editor.mode = Mode.NORMAL() editor.mode = Mode.NORMAL()
editor.vimStateMachine.executingCommand = null injector.vimState.executingCommand = null
keyHandlerState.digraphSequence.reset() keyHandlerState.digraphSequence.reset()
reset(keyHandlerState, editor.mode) reset(keyHandlerState, editor.mode)
@ -322,7 +321,7 @@ class KeyHandler {
val operatorArguments: OperatorArguments, val operatorArguments: OperatorArguments,
) : Runnable { ) : Runnable {
override fun run() { override fun run() {
val editorState = VimStateMachine.getInstance(editor) val editorState = injector.vimState
keyState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND keyState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
val register = cmd.register val register = cmd.register
if (register != null) { if (register != null) {
@ -492,15 +491,4 @@ sealed interface KeyProcessResult {
} }
} }
class KeyHandlerStateResetter : EditorListener {
override fun focusGained(editor: VimEditor) {
KeyHandler.getInstance().reset(editor)
}
override fun focusLost(editor: VimEditor) {
// We do not reset the KeyHandler state here, because a command or a search prompt is not considered to be part of the
// editor, and resetting state would break the search functionality.
}
}
typealias KeyProcessing = (KeyHandlerState, VimEditor, ExecutionContext) -> Unit typealias KeyProcessing = (KeyHandlerState, VimEditor, ExecutionContext) -> Unit

View File

@ -16,7 +16,6 @@ import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.vimStateMachine
import java.util.* import java.util.*
@CommandOrMotion(keys = ["<Insert>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["<Insert>"], modes = [Mode.INSERT])
@ -38,5 +37,5 @@ class InsertInsertAction : VimActionHandler.SingleExecution() {
private fun processInsert(editor: VimEditor) { private fun processInsert(editor: VimEditor) {
editor.insertMode = !editor.insertMode editor.insertMode = !editor.insertMode
editor.vimStateMachine.toggleInsertOverwrite() editor.toggleInsertOverwrite()
} }

View File

@ -18,7 +18,6 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.pushVisualMode import com.maddyhome.idea.vim.helper.pushVisualMode
import com.maddyhome.idea.vim.helper.setSelectMode import com.maddyhome.idea.vim.helper.setSelectMode
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
/** /**
@ -42,8 +41,7 @@ class SelectToggleVisualMode : VimActionHandler.SingleExecution() {
companion object { companion object {
fun toggleMode(editor: VimEditor) { fun toggleMode(editor: VimEditor) {
val commandState = editor.vimStateMachine val myMode = editor.mode
val myMode = commandState.mode
if (myMode is com.maddyhome.idea.vim.state.mode.Mode.VISUAL) { if (myMode is com.maddyhome.idea.vim.state.mode.Mode.VISUAL) {
editor.setSelectMode(myMode.selectionType) editor.setSelectMode(myMode.selectionType)
if (myMode.selectionType != SelectionType.LINE_WISE) { if (myMode.selectionType != SelectionType.LINE_WISE) {

View File

@ -51,10 +51,6 @@ open class GlobalOptions(scope: OptionAccessScope): OptionsPropertiesBase(scope)
// IdeaVim specific options. Put any editor or IDE specific options in IjOptionProperties // IdeaVim specific options. Put any editor or IDE specific options in IjOptionProperties
// This is an experimental option that enables global mode for the editor. However,
// for the moment it has issues and there is no quality garantee if this option is enabled
var ideaglobalmode: Boolean by optionProperty(Options.ideaglobalmode)
// Temporary flags for work-in-progress behaviour. Hidden from the output of `:set all` // Temporary flags for work-in-progress behaviour. Hidden from the output of `:set all`
var ideastrictmode: Boolean by optionProperty(Options.ideastrictmode) var ideastrictmode: Boolean by optionProperty(Options.ideastrictmode)
var ideatracetime: Boolean by optionProperty(Options.ideatracetime) var ideatracetime: Boolean by optionProperty(Options.ideatracetime)

View File

@ -320,7 +320,6 @@ object Options {
) )
// IdeaVim specific options. Put any editor or IDE specific options in IjOptionProperties // IdeaVim specific options. Put any editor or IDE specific options in IjOptionProperties
val ideaglobalmode: ToggleOption = addOption(ToggleOption("ideaglobalmode", GLOBAL, "ideaglobalmode", false))
// Temporary feature flags for work-in-progress behaviour, diagnostic switches, etc. Hidden from the output of `:set all` // Temporary feature flags for work-in-progress behaviour, diagnostic switches, etc. Hidden from the output of `:set all`
val ideastrictmode: ToggleOption = addOption(ToggleOption("ideastrictmode", GLOBAL, "ideastrictmode", false, isHidden = true)) val ideastrictmode: ToggleOption = addOption(ToggleOption("ideastrictmode", GLOBAL, "ideastrictmode", false, isHidden = true))

View File

@ -26,13 +26,11 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.CharacterHelper.charType import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.helper.StrictMode import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.usesVirtualSpace import com.maddyhome.idea.vim.helper.usesVirtualSpace
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_END import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_END
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_START import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_START
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.toReturnTo import com.maddyhome.idea.vim.state.mode.toReturnTo
@ -430,7 +428,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
* @param mode The mode - indicate insert or replace * @param mode The mode - indicate insert or replace
*/ */
override fun initInsert(editor: VimEditor, context: ExecutionContext, mode: Mode) { override fun initInsert(editor: VimEditor, context: ExecutionContext, mode: Mode) {
val state = getInstance(editor) val state = injector.vimState
for (caret in editor.nativeCarets()) { for (caret in editor.nativeCarets()) {
caret.vimInsertStart = editor.createLiveMarker(caret.offset, caret.offset) caret.vimInsertStart = editor.createLiveMarker(caret.offset, caret.offset)
injector.markService.setMark(caret, MARK_CHANGE_START, caret.offset) injector.markService.setMark(caret, MARK_CHANGE_START, caret.offset)
@ -442,7 +440,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
editor.insertMode = false editor.insertMode = false
} }
if (cmd.flags.contains(CommandFlags.FLAG_NO_REPEAT_INSERT)) { if (cmd.flags.contains(CommandFlags.FLAG_NO_REPEAT_INSERT)) {
val commandState = getInstance(editor) val commandState = injector.vimState
repeatInsert( repeatInsert(
editor, editor,
context, context,
@ -451,7 +449,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
OperatorArguments(false, 1, commandState.mode), OperatorArguments(false, 1, commandState.mode),
) )
} else { } else {
val commandState = getInstance(editor) val commandState = injector.vimState
repeatInsert( repeatInsert(
editor, editor,
context, context,
@ -484,7 +482,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
} }
override fun runEnterAction(editor: VimEditor, context: ExecutionContext) { override fun runEnterAction(editor: VimEditor, context: ExecutionContext) {
val state = getInstance(editor) val state = injector.vimState
if (!state.isDotRepeatInProgress) { if (!state.isDotRepeatInProgress) {
// While repeating the enter action has been already executed because `initInsert` repeats the input // While repeating the enter action has been already executed because `initInsert` repeats the input
val action = injector.nativeActionManager.enterAction val action = injector.nativeActionManager.enterAction
@ -502,7 +500,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
} }
override fun runEnterAboveAction(editor: VimEditor, context: ExecutionContext) { override fun runEnterAboveAction(editor: VimEditor, context: ExecutionContext) {
val state = getInstance(editor) val state = injector.vimState
if (!state.isDotRepeatInProgress) { if (!state.isDotRepeatInProgress) {
// While repeating the enter action has been already executed because `initInsert` repeats the input // While repeating the enter action has been already executed because `initInsert` repeats the input
val action = injector.nativeActionManager.createLineAboveCaret val action = injector.nativeActionManager.createLineAboveCaret
@ -543,7 +541,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
val markGroup = injector.markService val markGroup = injector.markService
markGroup.setMark(editor, VimMarkService.INSERT_EXIT_MARK) markGroup.setMark(editor, VimMarkService.INSERT_EXIT_MARK)
markGroup.setMark(editor, MARK_CHANGE_END) markGroup.setMark(editor, MARK_CHANGE_END)
if (getInstance(editor).mode is Mode.REPLACE) { if (editor.mode is Mode.REPLACE) {
editor.insertMode = true editor.insertMode = true
} }
var cnt = if (lastInsert != null) lastInsert!!.count else 0 var cnt = if (lastInsert != null) lastInsert!!.count else 0
@ -558,7 +556,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
if (context != null) { if (context != null) {
injector.changeGroup.repeatInsert(editor, context, if (cnt == 0) 0 else cnt - 1, true, operatorArguments) injector.changeGroup.repeatInsert(editor, context, if (cnt == 0) 0 else cnt - 1, true, operatorArguments)
} }
if (getInstance(editor).mode is Mode.INSERT) { if (editor.mode is Mode.INSERT) {
updateLastInsertedTextRegister() updateLastInsertedTextRegister()
} }
@ -589,7 +587,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
* @param context The data context * @param context The data context
*/ */
override fun processEnter(editor: VimEditor, context: ExecutionContext) { override fun processEnter(editor: VimEditor, context: ExecutionContext) {
if (editor.vimStateMachine.mode is Mode.REPLACE) { if (editor.mode is Mode.REPLACE) {
editor.insertMode = true editor.insertMode = true
} }
val enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0) val enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)
@ -599,7 +597,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
break break
} }
} }
if (editor.vimStateMachine.mode is Mode.REPLACE) { if (editor.mode is Mode.REPLACE) {
editor.insertMode = false editor.insertMode = false
} }
} }

View File

@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.api
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.LiveRange import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.common.TextRange 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.VimStateMachineImpl
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo import com.maddyhome.idea.vim.state.mode.ReturnTo
@ -126,20 +125,22 @@ import com.maddyhome.idea.vim.state.mode.returnTo
*/ */
interface VimEditor { interface VimEditor {
var mode: Mode var mode: Mode
get() = vimStateMachine.mode get() = injector.vimState.mode
set(value) { set(value) {
if (vimStateMachine.mode == value) return val vimState = injector.vimState
if (vimState.mode == value) return
val oldValue = vimStateMachine.mode val oldValue = vimState.mode
(vimStateMachine as VimStateMachineImpl).mode = value (vimState as VimStateMachineImpl).mode = value
injector.listenersNotifier.notifyModeChanged(this, oldValue) injector.listenersNotifier.notifyModeChanged(this, oldValue)
} }
var isReplaceCharacter: Boolean var isReplaceCharacter: Boolean
get() = vimStateMachine.isReplaceCharacter get() = injector.vimState.isReplaceCharacter
set(value) { set(value) {
if (value != vimStateMachine.isReplaceCharacter) { val vimState = injector.vimState
(vimStateMachine as VimStateMachineImpl).isReplaceCharacter = value if (value != vimState.isReplaceCharacter) {
(vimState as VimStateMachineImpl).isReplaceCharacter = value
injector.listenersNotifier.notifyIsReplaceCharChanged(this) injector.listenersNotifier.notifyIsReplaceCharChanged(this)
} }
} }
@ -162,7 +163,7 @@ interface VimEditor {
* which indicated that the buffer is empty. However, the line count is still 1. * which indicated that the buffer is empty. However, the line count is still 1.
* *
* The variable for line count is named `ml_line_count` in `memline` structure. There is a single spot where * The variable for line count is named `ml_line_count` in `memline` structure. There is a single spot where
* `0` is assigned to this variable (at the end of `buf_freeall` public function), however I'm not sure that this affects * `0` is assigned to this variable (at the end of `buf_freeall` function), however I'm not sure that this affects
* the opened buffer. * the opened buffer.
* Another thing that I don't understand is that I don't see where this variable is updated. There is a small chance * Another thing that I don't understand is that I don't see where this variable is updated. There is a small chance
* that this variable doesn't present the line count, so I may be wrong and line count can return zero. * that this variable doesn't present the line count, so I may be wrong and line count can return zero.
@ -208,7 +209,7 @@ interface VimEditor {
fun isOneLineMode(): Boolean fun isOneLineMode(): Boolean
/** /**
* public function for refactoring, get rid of it * function for refactoring, get rid of it
*/ */
fun search( fun search(
pair: Pair<Int, Int>, pair: Pair<Int, Int>,
@ -304,6 +305,23 @@ interface VimEditor {
} }
} }
} }
/**
* Toggles the insert/overwrite state. If currently insert, goto replace mode. If currently replace, goto insert
* mode.
*/
fun toggleInsertOverwrite() {
val oldMode = this.mode
var newMode = oldMode
if (oldMode == Mode.INSERT) {
newMode = Mode.REPLACE
} else if (oldMode == Mode.REPLACE) {
newMode = Mode.INSERT
}
if (oldMode != newMode) {
mode = newMode
}
}
} }
interface MutableVimEditor : VimEditor { interface MutableVimEditor : VimEditor {

View File

@ -1,23 +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.api
interface VimExOutputPanelService {
fun getPanel(editor: VimEditor): VimExOutputPanel
}
interface VimExOutputPanel {
val isActive: Boolean
val text: String?
fun output(text: String)
fun clear()
fun close()
}

View File

@ -22,6 +22,8 @@ import com.maddyhome.idea.vim.vimscript.services.VariableService
import com.maddyhome.idea.vim.yank.VimYankGroup import com.maddyhome.idea.vim.yank.VimYankGroup
interface VimInjector { interface VimInjector {
val vimState: VimStateMachine
/** /**
* The window used when we need a window but there are no editor windows available. * The window used when we need a window but there are no editor windows available.
* *
@ -85,12 +87,14 @@ interface VimInjector {
val visualMotionGroup: VimVisualMotionGroup val visualMotionGroup: VimVisualMotionGroup
// [FINISHED] Class moved to vim-engine, but it's attached to Editor using IJ things // [FINISHED] Class moved to vim-engine, but it's attached to Editor using IJ things
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
fun commandStateFor(editor: VimEditor): VimStateMachine fun commandStateFor(editor: VimEditor): VimStateMachine
// [FINISHED] Class moved to vim-engine, but it's attached to Editor using IJ things // [FINISHED] Class moved to vim-engine, but it's attached to Editor using IJ things
/** /**
* COMPATIBILITY-LAYER: Added new method with Any * COMPATIBILITY-LAYER: Added new method with Any
* Please see: https://jb.gg/zo8n0r * Please see: https://jb.gg/zo8n0r
*/ */
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
fun commandStateFor(editor: Any): VimStateMachine fun commandStateFor(editor: Any): VimStateMachine
// !! in progress // !! in progress
@ -108,9 +112,6 @@ interface VimInjector {
// Can't be fully moved to vim-engine. // Can't be fully moved to vim-engine.
val actionExecutor: VimActionExecutor val actionExecutor: VimActionExecutor
// Can't be fully moved to vim-engine.
val exOutputPanel: VimExOutputPanelService
// Can't be fully moved to vim-engine. // Can't be fully moved to vim-engine.
val clipboardManager: VimClipboardManager val clipboardManager: VimClipboardManager
@ -175,6 +176,7 @@ interface VimInjector {
val variableService: VariableService val variableService: VariableService
val commandLine: VimCommandLineService val commandLine: VimCommandLineService
val outputPanel: VimOutputPanelService
// !! in progress // !! in progress
val functionService: VimscriptFunctionService val functionService: VimscriptFunctionService

View File

@ -16,8 +16,10 @@ import com.maddyhome.idea.vim.api.stubs.VimProcessGroupStub
import com.maddyhome.idea.vim.common.VimListenersNotifier import com.maddyhome.idea.vim.common.VimListenersNotifier
import com.maddyhome.idea.vim.diagnostic.VimLogger import com.maddyhome.idea.vim.diagnostic.VimLogger
import com.maddyhome.idea.vim.diagnostic.vimLogger import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.register.VimRegisterGroup import com.maddyhome.idea.vim.register.VimRegisterGroup
import com.maddyhome.idea.vim.register.VimRegisterGroupBase import com.maddyhome.idea.vim.register.VimRegisterGroupBase
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.vimscript.services.VariableService import com.maddyhome.idea.vim.vimscript.services.VariableService
import com.maddyhome.idea.vim.vimscript.services.VimVariableServiceBase import com.maddyhome.idea.vim.vimscript.services.VimVariableServiceBase
import com.maddyhome.idea.vim.yank.VimYankGroup import com.maddyhome.idea.vim.yank.VimYankGroup
@ -29,6 +31,8 @@ abstract class VimInjectorBase : VimInjector {
val registerGroupStub: VimRegisterGroupBase by lazy { object : VimRegisterGroupBase() {} } val registerGroupStub: VimRegisterGroupBase by lazy { object : VimRegisterGroupBase() {} }
} }
override val vimState: VimStateMachine = VimStateMachineImpl()
override val parser: VimStringParser = object : VimStringParserBase() {} override val parser: VimStringParser = object : VimStringParserBase() {}
override val optionGroup: VimOptionGroup by lazy { object : VimOptionGroupBase() {} } override val optionGroup: VimOptionGroup by lazy { object : VimOptionGroupBase() {} }

View File

@ -27,7 +27,6 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.diagnostic.debug import com.maddyhome.idea.vim.diagnostic.debug
import com.maddyhome.idea.vim.diagnostic.vimLogger import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.mark.Jump import com.maddyhome.idea.vim.mark.Jump
import com.maddyhome.idea.vim.mark.Mark import com.maddyhome.idea.vim.mark.Mark
import com.maddyhome.idea.vim.mark.VimMark import com.maddyhome.idea.vim.mark.VimMark
@ -368,7 +367,7 @@ abstract class VimMarkServiceBase : VimMarkService {
val markLineStartOffset = editor.getLineStartOffset(mark.line) val markLineStartOffset = editor.getLineStartOffset(mark.line)
val markLineEndOffset = editor.getLineEndOffset(mark.line, true) val markLineEndOffset = editor.getLineEndOffset(mark.line, true)
val command = editor.vimStateMachine.executingCommand val command = injector.vimState.executingCommand
// If text is being changed from the start of the mark line (a special case for mark deletion) // If text is being changed from the start of the mark line (a special case for mark deletion)
val changeFromMarkLineStart = val changeFromMarkLineStart =
(command != null && command.type === Command.Type.CHANGE && delStartOffset == markLineStartOffset) (command != null && command.type === Command.Type.CHANGE && delStartOffset == markLineStartOffset)

View File

@ -0,0 +1,39 @@
/*
* 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.api
interface VimOutputPanel {
/**
* The current text displayed in the output panel.
* The actual text may be different (if we called the [addText] method and did not call [show] afterward)
*/
val text: String
/**
* Appends the specified text to the existing content of the output panel.
* If 'isNewLine' is true, the text will begin on a new line.
*
* Note: The full text content is not updated in the display until [show] is invoked.
*
* @param text The text to append.
* @param isNewLine Whether to start the appended text on a new line. Defaults to true.
*/
fun addText(text: String, isNewLine: Boolean = true)
/**
* This method shows the text output or updates the output text if the panel was already shown
*/
fun show()
/**
* Disposes or hides the output panel, depending on its implementation.
* This may free any associated resources.
*/
fun close()
}

View File

@ -0,0 +1,33 @@
/*
* 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.api
interface VimOutputPanelService {
/**
* Creates a new VimOutputPanel instance for building output without affecting the current panel until displayed.
*/
// TODO make it possible to pass null instead of editor
fun create(editor: VimEditor, context: ExecutionContext): VimOutputPanel
/**
* Retrieves the current VimOutputPanel or creates a new one if none exists.
*/
fun getOrCreate(editor: VimEditor, context: ExecutionContext): VimOutputPanel
/**
* Returns the currently active VimOutputPanel, if available.
*/
fun getCurrentOutputPanel(): VimOutputPanel?
/**
* Appends text to the existing output panel or creates a new one with the given text.
* Basic method that should be sufficient in most cases.
*/
fun output(editor: VimEditor, context: ExecutionContext, text: String)
}

View File

@ -0,0 +1,21 @@
/*
* 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.api
abstract class VimOutputPanelServiceBase : VimOutputPanelService {
override fun getOrCreate(editor: VimEditor, context: ExecutionContext): VimOutputPanel {
return getCurrentOutputPanel() ?: create(editor, context)
}
override fun output(editor: VimEditor, context: ExecutionContext, text: String) {
val panel = getOrCreate(editor, context)
panel.addText(text)
panel.show()
}
}

View File

@ -21,7 +21,7 @@ interface VimProcessGroup {
// TODO remove me // TODO remove me
// TODO: Why ^^ ? Should that also include startExEntry? // TODO: Why ^^ ? Should that also include startExEntry?
fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) fun cancelExEntry(editor: VimEditor, refocusOwningEditor: Boolean, resetCaret: Boolean)
@kotlin.jvm.Throws(java.lang.Exception::class) @kotlin.jvm.Throws(java.lang.Exception::class)
fun executeCommand(editor: VimEditor, command: String, input: CharSequence?, currentDirectoryPath: String?): String? fun executeCommand(editor: VimEditor, command: String, input: CharSequence?, currentDirectoryPath: String?): String?

View File

@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.api
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
import com.maddyhome.idea.vim.KeyProcessResult import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.inVisualMode import com.maddyhome.idea.vim.state.mode.inVisualMode
@ -27,7 +26,7 @@ abstract class VimProcessGroupBase : VimProcessGroup {
// Don't allow ex commands in one line editors // Don't allow ex commands in one line editors
if (editor.isOneLineMode()) return if (editor.isOneLineMode()) return
val currentMode = editor.vimStateMachine.mode val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) { check(currentMode is ReturnableFromCmd) {
"Cannot enable cmd mode from current mode $currentMode" "Cannot enable cmd mode from current mode $currentMode"
} }
@ -76,12 +75,12 @@ abstract class VimProcessGroupBase : VimProcessGroup {
} }
} }
override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) { override fun cancelExEntry(editor: VimEditor, refocusOwningEditor: Boolean, resetCaret: Boolean) {
// If 'cpoptions' contains 'x', then Escape should execute the command line. This is the default for Vi but not Vim. // If 'cpoptions' contains 'x', then Escape should execute the command line. This is the default for Vi but not Vim.
// IdeaVim does not (currently?) support 'cpoptions', so sticks with Vim's default behaviour. Escape cancels. // IdeaVim does not (currently?) support 'cpoptions', so sticks with Vim's default behaviour. Escape cancels.
editor.mode = editor.mode.returnTo() editor.mode = editor.mode.returnTo()
getInstance().reset(editor) getInstance().reset(editor)
injector.commandLine.getActiveCommandLine()?.deactivate(refocusOwningEditor = true, resetCaret) injector.commandLine.getActiveCommandLine()?.deactivate(refocusOwningEditor, resetCaret)
} }
private fun getRange(editor: VimEditor, cmd: Command) = when { private fun getRange(editor: VimEditor, cmd: Command) = when {

View File

@ -20,7 +20,6 @@ import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.VimRegexException import com.maddyhome.idea.vim.regexp.VimRegexException
import com.maddyhome.idea.vim.regexp.VimRegexOptions import com.maddyhome.idea.vim.regexp.VimRegexOptions
import com.maddyhome.idea.vim.regexp.match.VimMatchResult import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.annotations.Contract import org.jetbrains.annotations.Contract
import org.jetbrains.annotations.Range import org.jetbrains.annotations.Range
@ -1454,7 +1453,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
while (selectionEndWithoutNewline < sequence.length && sequence[selectionEndWithoutNewline] == '\n') { while (selectionEndWithoutNewline < sequence.length && sequence[selectionEndWithoutNewline] == '\n') {
selectionEndWithoutNewline++ selectionEndWithoutNewline++
} }
val mode = VimStateMachine.getInstance(editor).mode val mode = editor.mode
if (mode is Mode.VISUAL) { if (mode is Mode.VISUAL) {
if (closingTagTextRange.startOffset == selectionEndWithoutNewline && if (closingTagTextRange.startOffset == selectionEndWithoutNewline &&
openingTag.endOffset == selectionStart openingTag.endOffset == selectionStart

View File

@ -16,7 +16,6 @@ import com.maddyhome.idea.vim.group.visual.vimUpdateEditorSelection
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.pushVisualMode import com.maddyhome.idea.vim.helper.pushVisualMode
import com.maddyhome.idea.vim.helper.setSelectMode import com.maddyhome.idea.vim.helper.setSelectMode
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
@ -72,7 +71,7 @@ abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
} else { } else {
editor.mode = Mode.VISUAL( editor.mode = Mode.VISUAL(
selectionType, selectionType,
returnTo ?: editor.vimStateMachine.mode.returnTo returnTo ?: editor.mode.returnTo
) )
editor.forEachCaret { it.vimSetSelection(it.offset) } editor.forEachCaret { it.vimSetSelection(it.offset) }
} }

View File

@ -40,7 +40,7 @@ class VimProcessGroupStub : VimProcessGroupBase() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) { override fun cancelExEntry(editor: VimEditor, refocusOwningEditor: Boolean, resetCaret: Boolean) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.diagnostic.debug import com.maddyhome.idea.vim.diagnostic.debug
import com.maddyhome.idea.vim.diagnostic.trace import com.maddyhome.idea.vim.diagnostic.trace
import com.maddyhome.idea.vim.diagnostic.vimLogger 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.impl.state.toMappingMode
import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyConsumer
import com.maddyhome.idea.vim.key.KeyMappingLayer import com.maddyhome.idea.vim.key.KeyMappingLayer
@ -42,13 +41,12 @@ object MappingProcessor: KeyConsumer {
log.debug("Start processing key mappings.") log.debug("Start processing key mappings.")
val keyState = keyProcessResultBuilder.state val keyState = keyProcessResultBuilder.state
val commandState = editor.vimStateMachine
val mappingState = keyState.mappingState val mappingState = keyState.mappingState
val commandBuilder = keyState.commandBuilder val commandBuilder = keyState.commandBuilder
if (commandBuilder.isAwaitingCharOrDigraphArgument() || if (commandBuilder.isAwaitingCharOrDigraphArgument() ||
commandBuilder.isBuildingMultiKeyCommand() || commandBuilder.isBuildingMultiKeyCommand() ||
isMappingDisabledForKey(key, keyState) || isMappingDisabledForKey(key, keyState) ||
commandState.isRegisterPending injector.vimState.isRegisterPending
) { ) {
log.debug("Finish key processing, returning false") log.debug("Finish key processing, returning false")
return false return false

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.common package com.maddyhome.idea.vim.common
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.ApiStatus.Internal
import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.ConcurrentLinkedDeque
@ -22,30 +23,37 @@ class VimListenersNotifier {
val isReplaceCharListeners: MutableCollection<IsReplaceCharListener> = ConcurrentLinkedDeque() val isReplaceCharListeners: MutableCollection<IsReplaceCharListener> = ConcurrentLinkedDeque()
fun notifyModeChanged(editor: VimEditor, oldMode: Mode) { fun notifyModeChanged(editor: VimEditor, oldMode: Mode) {
if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
modeChangeListeners.forEach { it.modeChanged(editor, oldMode) } modeChangeListeners.forEach { it.modeChanged(editor, oldMode) }
} }
fun notifyEditorCreated(editor: VimEditor) { fun notifyEditorCreated(editor: VimEditor) {
if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
myEditorListeners.forEach { it.created(editor) } myEditorListeners.forEach { it.created(editor) }
} }
fun notifyEditorReleased(editor: VimEditor) { fun notifyEditorReleased(editor: VimEditor) {
if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
myEditorListeners.forEach { it.released(editor) } myEditorListeners.forEach { it.released(editor) }
} }
fun notifyEditorFocusGained(editor: VimEditor) { fun notifyEditorFocusGained(editor: VimEditor) {
if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
myEditorListeners.forEach { it.focusGained(editor) } myEditorListeners.forEach { it.focusGained(editor) }
} }
fun notifyEditorFocusLost(editor: VimEditor) { fun notifyEditorFocusLost(editor: VimEditor) {
if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
myEditorListeners.forEach { it.focusLost(editor) } myEditorListeners.forEach { it.focusLost(editor) }
} }
fun notifyMacroRecordingStarted() { fun notifyMacroRecordingStarted() {
if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
macroRecordingListeners.forEach { it.recordingStarted() } macroRecordingListeners.forEach { it.recordingStarted() }
} }
fun notifyMacroRecordingFinished() { fun notifyMacroRecordingFinished() {
if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
macroRecordingListeners.forEach { it.recordingFinished() } macroRecordingListeners.forEach { it.recordingFinished() }
} }
@ -58,6 +66,7 @@ class VimListenersNotifier {
} }
fun notifyIsReplaceCharChanged(editor: VimEditor) { fun notifyIsReplaceCharChanged(editor: VimEditor) {
if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
isReplaceCharListeners.forEach { it.isReplaceCharChanged(editor) } isReplaceCharListeners.forEach { it.isReplaceCharChanged(editor) }
} }

View File

@ -18,7 +18,6 @@ import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.diagnostic.vimLogger import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.helper.noneOfEnum import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.state.VimStateMachine
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.util.* import java.util.*
import javax.swing.KeyStroke import javax.swing.KeyStroke
@ -114,7 +113,7 @@ abstract class EditorActionHandlerBase(private val myRunForEachCaret: Boolean) {
} }
if (currentCaret == primaryCaret) { if (currentCaret == primaryCaret) {
val cmd = VimStateMachine.getInstance(editor).executingCommand ?: run { val cmd = injector.vimState.executingCommand ?: run {
injector.messages.indicateError() injector.messages.indicateError()
return return
} }
@ -127,7 +126,7 @@ abstract class EditorActionHandlerBase(private val myRunForEachCaret: Boolean) {
logger.debug("Execute command with handler: " + this.javaClass.name) logger.debug("Execute command with handler: " + this.javaClass.name)
val cmd = VimStateMachine.getInstance(editor).executingCommand ?: run { val cmd = injector.vimState.executingCommand ?: run {
injector.messages.indicateError() injector.messages.indicateError()
return return
} }

View File

@ -24,8 +24,6 @@ import com.maddyhome.idea.vim.group.visual.VimSimpleSelection
import com.maddyhome.idea.vim.group.visual.VisualChange import com.maddyhome.idea.vim.group.visual.VisualChange
import com.maddyhome.idea.vim.group.visual.VisualOperation import com.maddyhome.idea.vim.group.visual.VisualOperation
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE 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.inBlockSelection
@ -176,7 +174,7 @@ sealed class VisualOperatorActionHandler : EditorActionHandlerBase(false) {
private fun VimEditor.collectSelections(): Map<VimCaret, VimSelection>? { private fun VimEditor.collectSelections(): Map<VimCaret, VimSelection>? {
return when { return when {
!this.inVisualMode && this.inRepeatMode -> { !this.inVisualMode && injector.vimState.isDotRepeatInProgress -> {
if (this.vimLastSelectionType == SelectionType.BLOCK_WISE) { if (this.vimLastSelectionType == SelectionType.BLOCK_WISE) {
val primaryCaret = primaryCaret() val primaryCaret = primaryCaret()
val range = primaryCaret.vimLastVisualOperatorRange ?: return null val range = primaryCaret.vimLastVisualOperatorRange ?: return null
@ -211,7 +209,7 @@ sealed class VisualOperatorActionHandler : EditorActionHandlerBase(false) {
) )
} }
else -> this.nativeCarets().associateWith { caret -> else -> this.nativeCarets().associateWith { caret ->
val mode = this.vimStateMachine.mode val mode = this.mode
VimSimpleSelection.createWithNative( VimSimpleSelection.createWithNative(
caret.vimSelectionStart, caret.vimSelectionStart,
caret.offset, caret.offset,
@ -235,7 +233,7 @@ sealed class VisualOperatorActionHandler : EditorActionHandlerBase(false) {
editor.forEachCaret { editor.forEachCaret {
val change = val change =
if (editor.inVisualMode && !editor.inRepeatMode) { if (editor.inVisualMode && !injector.vimState.isDotRepeatInProgress) {
VisualOperation.getRange(editor, it, cmd.flags) VisualOperation.getRange(editor, it, cmd.flags)
} else { } else {
null null

View File

@ -26,10 +26,7 @@ val TextRange.endOffsetInclusive: Int
get() = if (this.endOffset > 0 && this.endOffset > this.startOffset) this.endOffset - 1 else this.endOffset get() = if (this.endOffset > 0 && this.endOffset > this.startOffset) this.endOffset - 1 else this.endOffset
val VimEditor.inRepeatMode: Boolean val VimEditor.inRepeatMode: Boolean
get() = this.vimStateMachine.isDotRepeatInProgress get() = injector.vimState.isDotRepeatInProgress
val VimEditor.vimStateMachine: VimStateMachine
get() = VimStateMachine.getInstance(this)
val VimEditor.usesVirtualSpace: Boolean val VimEditor.usesVirtualSpace: Boolean
get() = injector.options(this).virtualedit.contains(OptionConstants.virtualedit_onemore) get() = injector.options(this).virtualedit.contains(OptionConstants.virtualedit_onemore)

View File

@ -33,7 +33,7 @@ fun VimEditor.exitVisualMode() {
injector.markService.setVisualSelectionMarks(this) injector.markService.setVisualSelectionMarks(this)
this.nativeCarets().forEach { it.vimSelectionStartClear() } this.nativeCarets().forEach { it.vimSelectionStartClear() }
val returnTo = this.vimStateMachine.mode.returnTo val returnTo = this.mode.returnTo
when (returnTo) { when (returnTo) {
ReturnTo.INSERT -> { ReturnTo.INSERT -> {
this.mode = Mode.INSERT this.mode = Mode.INSERT

View File

@ -47,21 +47,12 @@ class VimStateMachineImpl : VimStateMachine {
} }
} }
/** override fun reset() {
* Toggles the insert/overwrite state. If currently insert, goto replace mode. If currently replace, goto insert mode = Mode.NORMAL()
* mode. isDotRepeatInProgress = false
*/ isRegisterPending = false
override fun toggleInsertOverwrite() { isReplaceCharacter = false
val oldMode = this.mode executingCommand = null
var newMode = oldMode
if (oldMode == Mode.INSERT) {
newMode = Mode.REPLACE
} else if (oldMode == Mode.REPLACE) {
newMode = Mode.INSERT
}
if (oldMode != newMode) {
mode = newMode
}
} }
companion object { companion object {

View File

@ -22,7 +22,6 @@ import com.maddyhome.idea.vim.common.argumentCaptured
import com.maddyhome.idea.vim.diagnostic.trace import com.maddyhome.idea.vim.diagnostic.trace
import com.maddyhome.idea.vim.diagnostic.vimLogger import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase 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.CommandNode
import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyConsumer
@ -126,9 +125,8 @@ class CommandConsumer : KeyConsumer {
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
logger.trace("Set waiting for the argument") logger.trace("Set waiting for the argument")
val argumentType = action.argumentType val argumentType = action.argumentType
val editorState = lambdaEditor.vimStateMachine startWaitingForArgument(lambdaEditor, lambdaContext, action, argumentType!!, lambdaKeyState, injector.vimState)
startWaitingForArgument(lambdaEditor, lambdaContext, action, argumentType!!, lambdaKeyState, editorState) lambdaKeyState.partialReset(lambdaEditor.mode)
lambdaKeyState.partialReset(editorState.mode)
} }
} }

View File

@ -11,9 +11,9 @@ package com.maddyhome.idea.vim.key.consumers
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.KeyProcessResult import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.diagnostic.trace import com.maddyhome.idea.vim.diagnostic.trace
import com.maddyhome.idea.vim.diagnostic.vimLogger 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.key.KeyConsumer
import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.KeyHandlerState
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
@ -43,7 +43,7 @@ class CommandCountConsumer : KeyConsumer {
private fun isCommandCountKey(chKey: Char, keyState: KeyHandlerState, editor: VimEditor): Boolean { private fun isCommandCountKey(chKey: Char, keyState: KeyHandlerState, editor: VimEditor): Boolean {
// Make sure to avoid handling '0' as the start of a count. // Make sure to avoid handling '0' as the start of a count.
val editorState = editor.vimStateMachine val editorState = injector.vimState
val commandBuilder = keyState.commandBuilder val commandBuilder = keyState.commandBuilder
val notRegisterPendingCommand = editorState.mode is Mode.NORMAL && !editorState.isRegisterPending val notRegisterPendingCommand = editorState.mode is Mode.NORMAL && !editorState.isRegisterPending
val visualMode = editorState.mode is Mode.VISUAL && !editorState.isRegisterPending val visualMode = editorState.mode is Mode.VISUAL && !editorState.isRegisterPending

View File

@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.diagnostic.trace import com.maddyhome.idea.vim.diagnostic.trace
import com.maddyhome.idea.vim.diagnostic.vimLogger 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.key.KeyConsumer
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
@ -33,7 +32,7 @@ class RegisterConsumer : KeyConsumer {
shouldRecord: KeyHandler.MutableBoolean, shouldRecord: KeyHandler.MutableBoolean,
): Boolean { ): Boolean {
logger.trace { "Entered RegisterConsumer" } logger.trace { "Entered RegisterConsumer" }
if (!editor.vimStateMachine.isRegisterPending) return false if (!injector.vimState.isRegisterPending) return false
logger.trace("Pending mode.") logger.trace("Pending mode.")
keyProcessResultBuilder.state.commandBuilder.addKey(key) keyProcessResultBuilder.state.commandBuilder.addKey(key)
@ -45,7 +44,7 @@ class RegisterConsumer : KeyConsumer {
private fun handleSelectRegister(editor: VimEditor, chKey: Char, processBuilder: KeyProcessResult.KeyProcessResultBuilder) { private fun handleSelectRegister(editor: VimEditor, chKey: Char, processBuilder: KeyProcessResult.KeyProcessResultBuilder) {
logger.trace("Handle select register") logger.trace("Handle select register")
editor.vimStateMachine.resetRegisterPending() injector.vimState.resetRegisterPending()
if (injector.registerGroup.isValid(chKey)) { if (injector.registerGroup.isValid(chKey)) {
logger.trace("Valid register") logger.trace("Valid register")
processBuilder.state.commandBuilder.pushCommandPart(chKey) processBuilder.state.commandBuilder.pushCommandPart(chKey)

View File

@ -11,9 +11,9 @@ package com.maddyhome.idea.vim.key.consumers
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.KeyProcessResult import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.diagnostic.trace import com.maddyhome.idea.vim.diagnostic.trace
import com.maddyhome.idea.vim.diagnostic.vimLogger 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.key.KeyConsumer
import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.KeyHandlerState
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
@ -35,24 +35,25 @@ class SelectRegisterConsumer : KeyConsumer {
): Boolean { ): Boolean {
logger.trace { "Entered SelectRegisterConsumer" } logger.trace { "Entered SelectRegisterConsumer" }
val state = keyProcessResultBuilder.state val state = keyProcessResultBuilder.state
if (!isSelectRegister(key, state, editor.vimStateMachine)) return false if (!isSelectRegister(key, state)) return false
logger.trace("Select register") logger.trace("Select register")
state.commandBuilder.addKey(key) state.commandBuilder.addKey(key)
keyProcessResultBuilder.addExecutionStep { _, lambdaEditor, _ -> keyProcessResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
lambdaEditor.vimStateMachine.isRegisterPending = true injector.vimState.isRegisterPending = true
} }
return true return true
} }
private fun isSelectRegister(key: KeyStroke, keyState: KeyHandlerState, editorState: VimStateMachine): Boolean { private fun isSelectRegister(key: KeyStroke, keyState: KeyHandlerState): Boolean {
if (editorState.mode !is Mode.NORMAL && editorState.mode !is Mode.VISUAL) { val vimState = injector.vimState
if (vimState.mode !is Mode.NORMAL && vimState.mode !is Mode.VISUAL) {
return false return false
} }
return if (editorState.isRegisterPending) { return if (vimState.isRegisterPending) {
true true
} else { } else {
key.keyChar == '"' && !KeyHandler.getInstance().isOperatorPending(editorState.mode, keyState) && keyState.commandBuilder.expectedArgumentType == null key.keyChar == '"' && !KeyHandler.getInstance().isOperatorPending(vimState.mode, keyState) && keyState.commandBuilder.expectedArgumentType == null
} }
} }
} }

View File

@ -146,8 +146,8 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
myRegisters.clear() myRegisters.clear()
} }
private fun isSmallDeletionSpecialCase(editor: VimEditor): Boolean { private fun isSmallDeletionSpecialCase(): Boolean {
val currentCommand = VimStateMachine.getInstance(editor).executingCommand val currentCommand = injector.vimState.executingCommand
if (currentCommand != null) { if (currentCommand != null) {
val argument = currentCommand.argument val argument = currentCommand.argument
if (argument != null) { if (argument != null) {
@ -264,7 +264,7 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
) )
// Deletes go into numbered registers only if text is smaller than a line, register is used or it's a special case // Deletes go into numbered registers only if text is smaller than a line, register is used or it's a special case
if (!smallInlineDeletion && register == defaultRegister || isSmallDeletionSpecialCase(editor)) { if (!smallInlineDeletion && register == defaultRegister || isSmallDeletionSpecialCase()) {
// Old 1 goes to 2, etc. Old 8 to 9, old 9 is lost // Old 1 goes to 2, etc. Old 8 to 9, old 9 is lost
var d = '8' var d = '8'
while (d >= '1') { while (d >= '1') {

View File

@ -8,11 +8,9 @@
package com.maddyhome.idea.vim.state package com.maddyhome.idea.vim.state
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import java.util.* import java.util.*
@ -40,22 +38,12 @@ interface VimStateMachine {
fun resetRegisterPending() fun resetRegisterPending()
/** fun reset()
* Toggles the insert/overwrite state. If currently insert, goto replace mode. If currently replace, goto insert
* mode.
*/
fun toggleInsertOverwrite()
companion object { companion object {
private val globalState = VimStateMachineImpl() @Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("injector.vimState", imports = ["com.maddyhome.idea.vim.api.injector"]))
// TODO do we really need this method? Can't we use editor.vimStateMachine?
fun getInstance(editor: Any?): VimStateMachine { fun getInstance(editor: Any?): VimStateMachine {
return if (editor == null || injector.globalOptions().ideaglobalmode) { return injector.vimState
globalState
} else {
injector.commandStateFor(editor)
}
} }
} }
} }

View File

@ -9,13 +9,10 @@
package com.maddyhome.idea.vim.state.mode package com.maddyhome.idea.vim.state.mode
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.api.injector
val VimEditor.mode: Mode
get() = this.vimStateMachine.mode
val VimEditor.inVisualMode: Boolean val VimEditor.inVisualMode: Boolean
get() = this.vimStateMachine.mode is Mode.VISUAL get() = injector.vimState.mode is Mode.VISUAL
val VimEditor.inBlockSelection: Boolean val VimEditor.inBlockSelection: Boolean
get() = this.mode.selectionType == SelectionType.BLOCK_WISE get() = this.mode.selectionType == SelectionType.BLOCK_WISE

View File

@ -52,14 +52,14 @@ data class CmdCommand(val range: Range, val argument: String) : Command.SingleEx
} }
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult { override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
val result: Boolean = if (argument.trim().isEmpty()) { val result: Boolean = if (argument.trim().isEmpty()) {
this.listAlias(editor, "") this.listAlias(editor, context, "")
} else { } else {
this.addAlias(editor) this.addAlias(editor, context)
} }
return if (result) ExecutionResult.Success else ExecutionResult.Error return if (result) ExecutionResult.Success else ExecutionResult.Error
} }
private fun listAlias(editor: VimEditor, filter: String): Boolean { private fun listAlias(editor: VimEditor, context: ExecutionContext, filter: String): Boolean {
val lineSeparator = "\n" val lineSeparator = "\n"
val allAliases = injector.commandGroup.listAliases() val allAliases = injector.commandGroup.listAliases()
val aliases = allAliases.filter { val aliases = allAliases.filter {
@ -67,11 +67,11 @@ data class CmdCommand(val range: Range, val argument: String) : Command.SingleEx
}.map { }.map {
"${it.key.padEnd(12)}${it.value.numberOfArguments.padEnd(11)}${it.value.printValue()}" "${it.key.padEnd(12)}${it.value.numberOfArguments.padEnd(11)}${it.value.printValue()}"
}.sortedWith(String.CASE_INSENSITIVE_ORDER).joinToString(lineSeparator) }.sortedWith(String.CASE_INSENSITIVE_ORDER).joinToString(lineSeparator)
injector.exOutputPanel.getPanel(editor).output("Name Args Definition$lineSeparator$aliases") injector.outputPanel.output(editor, context, "Name Args Definition$lineSeparator$aliases")
return true return true
} }
private fun addAlias(editor: VimEditor): Boolean { private fun addAlias(editor: VimEditor, context: ExecutionContext): Boolean {
var argument = argument.trim() var argument = argument.trim()
// Handle overwriting of aliases // Handle overwriting of aliases
@ -165,7 +165,7 @@ data class CmdCommand(val range: Range, val argument: String) : Command.SingleEx
// No message should be shown either, since there is no editor. // No message should be shown either, since there is no editor.
return false return false
} }
return this.listAlias(editor, alias) return this.listAlias(editor, context, alias)
} }
// If we are not over-writing existing aliases, and an alias with the same command // If we are not over-writing existing aliases, and an alias with the same command

View File

@ -29,7 +29,7 @@ data class EchoCommand(val range: Range, val args: List<Expression>) : Command.S
val text = args.joinToString(separator = " ", postfix = "\n") { val text = args.joinToString(separator = " ", postfix = "\n") {
it.evaluate(editor, context, this).toString() it.evaluate(editor, context, this).toString()
} }
injector.exOutputPanel.getPanel(editor).output(text) injector.outputPanel.output(editor, context, text)
return ExecutionResult.Success return ExecutionResult.Success
} }
} }

View File

@ -131,9 +131,7 @@ data class GlobalCommand(val range: Range, val argument: String, val invert: Boo
globalBusy = true globalBusy = true
try { try {
if (cmd.isEmpty() || (cmd.length == 1 && cmd[0] == '\n')) { if (cmd.isEmpty() || (cmd.length == 1 && cmd[0] == '\n')) {
val exOutputModel = injector.exOutputPanel.getPanel(editor) injector.outputPanel.output(editor, context, originalCommandString + '\n' + PrintCommand.getText(editor, lines))
exOutputModel.clear()
exOutputModel.output(originalCommandString + '\n' + PrintCommand.getText(editor, lines))
} else { } else {
for (mark in marks) { for (mark in marks) {
if (gotInt) break if (gotInt) break

View File

@ -105,8 +105,7 @@ data class HistoryCommand(val range: Range, val argument: String) : Command.Sing
else -> "" else -> ""
} }
injector.exOutputPanel.getPanel(editor).output(res) injector.outputPanel.output(editor, context, res)
return ExecutionResult.Success return ExecutionResult.Success
} }

View File

@ -58,8 +58,7 @@ data class JumpsCommand(val range: Range, val argument: String) : Command.Single
text.append(">\n") text.append(">\n")
} }
injector.exOutputPanel.getPanel(editor).output(text.toString()) injector.outputPanel.output(editor, context, text.toString())
return ExecutionResult.Success return ExecutionResult.Success
} }
} }

View File

@ -49,7 +49,7 @@ data class MarksCommand(val range: Range, val argument: String) : Command.Single
" ${mark.key} $line $column $text" " ${mark.key} $line $column $text"
} }
injector.exOutputPanel.getPanel(editor).output(res) injector.outputPanel.output(editor, context, res)
return ExecutionResult.Success return ExecutionResult.Success
} }

View File

@ -19,7 +19,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ranges.Range import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
// todo make it for each caret // todo make it for each caret
@ -40,7 +39,6 @@ data class NormalCommand(val range: Range, val argument: String) : Command.Singl
argument = argument.substring(1) argument = argument.substring(1)
} }
val commandState = editor.vimStateMachine
val rangeUsed = range.size() != 0 val rangeUsed = range.size() != 0
when (editor.mode) { when (editor.mode) {
is Mode.VISUAL -> { is Mode.VISUAL -> {
@ -50,8 +48,8 @@ data class NormalCommand(val range: Range, val argument: String) : Command.Singl
editor.currentCaret().moveToBufferPosition(BufferPosition(selectionStart.line, selectionStart.col)) editor.currentCaret().moveToBufferPosition(BufferPosition(selectionStart.line, selectionStart.col))
} }
} }
is Mode.CMD_LINE -> injector.processGroup.cancelExEntry(editor, false) is Mode.CMD_LINE -> injector.processGroup.cancelExEntry(editor, refocusOwningEditor = true, resetCaret = false)
Mode.INSERT, Mode.REPLACE -> editor.exitInsertMode(context, OperatorArguments(false, 1, commandState.mode)) Mode.INSERT, Mode.REPLACE -> editor.exitInsertMode(context, OperatorArguments(false, 1, editor.mode))
is Mode.SELECT -> editor.exitSelectModeNative(false) is Mode.SELECT -> editor.exitSelectModeNative(false)
is Mode.OP_PENDING, is Mode.NORMAL -> Unit is Mode.OP_PENDING, is Mode.NORMAL -> Unit
} }
@ -76,12 +74,12 @@ data class NormalCommand(val range: Range, val argument: String) : Command.Singl
} }
// Exit if state leaves as insert or cmd_line // Exit if state leaves as insert or cmd_line
val mode = commandState.mode val mode = editor.mode
if (mode is Mode.CMD_LINE) { if (mode is Mode.CMD_LINE) {
injector.processGroup.cancelExEntry(editor, false) injector.processGroup.cancelExEntry(editor, refocusOwningEditor = true, resetCaret = false)
} }
if (mode is Mode.INSERT || mode is Mode.REPLACE) { if (mode is Mode.INSERT || mode is Mode.REPLACE) {
editor.exitInsertMode(context, OperatorArguments(false, 1, commandState.mode)) editor.exitInsertMode(context, OperatorArguments(false, 1, mode))
} }
} }

View File

@ -29,22 +29,12 @@ data class PrintCommand(val range: Range, val argument: String) : Command.Single
editor.removeSecondaryCarets() editor.removeSecondaryCarets()
val caret = editor.currentCaret() val caret = editor.currentCaret()
val lineRange = getLineRangeWithCount(editor, caret) val lineRange = getLineRangeWithCount(editor, caret)
val text = getText(editor, (lineRange.startLine .. lineRange.endLine).toList())
// Move the caret to the start of the last line of the range // Move the caret to the start of the last line of the range
val offset = injector.motion.moveCaretToLineStartSkipLeading(editor, lineRange.endLine) val offset = injector.motion.moveCaretToLineStartSkipLeading(editor, lineRange.endLine)
caret.moveToOffset(offset) caret.moveToOffset(offset)
// Note that we append to the existing text because we can be called multiple times by the :global command injector.outputPanel.output(editor, context, getText(editor, (lineRange.startLine .. lineRange.endLine).toList()))
// TODO: We need a better way to handle output. This is not very efficient, especially if we have a lot of output
val exOutputModel = injector.exOutputPanel.getPanel(editor)
if (!exOutputModel.isActive) {
// When we add text, we make the panel active. So if we're appending, it should already be active. If it's not,
// make sure it's clear
exOutputModel.clear()
}
val existingText = exOutputModel.text?.let { if (it.isNotEmpty()) it.plus("\n") else it } ?: ""
exOutputModel.output(existingText + text)
return ExecutionResult.Success return ExecutionResult.Success
} }

View File

@ -38,7 +38,7 @@ data class RegistersCommand(val range: Range, val argument: String) : Command.Si
" $type \"${reg.name} ${EngineStringHelper.toPrintableCharacters(text).take(200)}" " $type \"${reg.name} ${EngineStringHelper.toPrintableCharacters(text).take(200)}"
} }
injector.exOutputPanel.getPanel(editor).output(regs) injector.outputPanel.output(editor, context, regs)
return ExecutionResult.Success return ExecutionResult.Success
} }

View File

@ -61,7 +61,7 @@ abstract class SetCommandBase(range: Range, argument: String) : Command.SingleEx
context: ExecutionContext, context: ExecutionContext,
operatorArguments: OperatorArguments operatorArguments: OperatorArguments
): ExecutionResult { ): ExecutionResult {
parseOptionLine(editor, commandArgument, getScope(editor)) parseOptionLine(editor, context, commandArgument, getScope(editor))
return ExecutionResult.Success return ExecutionResult.Success
} }
@ -93,7 +93,7 @@ abstract class SetCommandBase(range: Range, argument: String) : Command.SingleEx
* @param args The raw text passed to the `:set` command * @param args The raw text passed to the `:set` command
* @throws ExException Thrown if any option names or operations are incorrect * @throws ExException Thrown if any option names or operations are incorrect
*/ */
fun parseOptionLine(editor: VimEditor, args: String, scope: OptionAccessScope) { fun parseOptionLine(editor: VimEditor, context: ExecutionContext, args: String, scope: OptionAccessScope) {
val optionGroup = injector.optionGroup val optionGroup = injector.optionGroup
val columnFormat = args.startsWith("!") val columnFormat = args.startsWith("!")
@ -105,14 +105,14 @@ fun parseOptionLine(editor: VimEditor, args: String, scope: OptionAccessScope) {
val changedOptions = optionGroup.getAllOptions() val changedOptions = optionGroup.getAllOptions()
.filter { !optionGroup.isDefaultValue(it, scope) && (!it.isHidden || (injector.application.isInternal() && !injector.application.isUnitTest())) } .filter { !optionGroup.isDefaultValue(it, scope) && (!it.isHidden || (injector.application.isInternal() && !injector.application.isUnitTest())) }
.map { Pair(it.name, it.name) } .map { Pair(it.name, it.name) }
showOptions(editor, changedOptions, scope, true, columnFormat) showOptions(editor, context, changedOptions, scope, true, columnFormat)
return return
} }
argument == "all" -> { argument == "all" -> {
val options = optionGroup.getAllOptions() val options = optionGroup.getAllOptions()
.filter { !it.isHidden || (injector.application.isInternal() && !injector.application.isUnitTest()) } .filter { !it.isHidden || (injector.application.isInternal() && !injector.application.isUnitTest()) }
.map { Pair(it.name, it.name) } .map { Pair(it.name, it.name) }
showOptions(editor, options, scope, true, columnFormat) showOptions(editor, context, options, scope, true, columnFormat)
return return
} }
argument == "all&" -> { argument == "all&" -> {
@ -196,7 +196,7 @@ fun parseOptionLine(editor: VimEditor, args: String, scope: OptionAccessScope) {
// Now show all options that were individually requested // Now show all options that were individually requested
if (toShow.size > 0) { if (toShow.size > 0) {
showOptions(editor, toShow, scope, false, columnFormat) showOptions(editor, context, toShow, scope, false, columnFormat)
} }
if (error != null) { if (error != null) {
@ -212,6 +212,7 @@ private fun getValidToggleOption(optionName: String, token: String) =
private fun showOptions( private fun showOptions(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext,
nameAndToken: Collection<Pair<String, String>>, nameAndToken: Collection<Pair<String, String>>,
scope: OptionAccessScope, scope: OptionAccessScope,
showIntro: Boolean, showIntro: Boolean,
@ -275,7 +276,7 @@ private fun showOptions(
appendLine(option) appendLine(option)
} }
} }
injector.exOutputPanel.getPanel(editor).output(output) injector.outputPanel.output(editor, context, output)
if (unknownOption != null) { if (unknownOption != null) {
throw exExceptionMessage("E518", unknownOption.second) throw exExceptionMessage("E518", unknownOption.second)