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

Compare commits

...

61 Commits

Author SHA1 Message Date
822dad52f3
Set plugin version to chylex-41 2024-09-05 07:40:10 +02:00
bf12c98880
Exit insert mode after refactoring 2024-09-05 07:40:10 +02:00
c02bd15e4f
Add action to run last macro in all opened files 2024-09-05 07:27:53 +02:00
5957d5d681
Stop macro execution after a failed search 2024-09-05 07:27:53 +02:00
9445afb555
Revert per-caret registers 2024-09-05 07:27:53 +02:00
6e115bdf64
Revert "Factor disposable objects on editor opening"
This reverts commit 1fa78935
2024-09-05 07:27:53 +02:00
6305c412b5
Fix(VIM-3364): Exception with mapped Generate action 2024-09-05 07:27:53 +02:00
be2eabe3b9
Apply scrolloff after executing native IDEA actions 2024-09-05 07:27:53 +02:00
de13e4348a
Stay on same line after reindenting 2024-09-05 07:12:09 +02:00
1c154fdc8a
Update search register when using f/t 2024-09-05 07:12:09 +02:00
9b75931736
Automatically add unambiguous imports after running a macro 2024-09-05 07:12:09 +02:00
3fe97aa767
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-09-05 07:12:09 +02:00
8eb2dbebcf
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-09-05 07:12:09 +02:00
cb781f9a0d
Add support for count for visual and line motion surround 2024-09-05 07:12:09 +02:00
6693755823
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-09-05 07:12:09 +02:00
9a9bd335d3
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-09-05 07:12:09 +02:00
91e22482c6
Respect count with <Action> mappings 2024-09-05 07:08:52 +02:00
06dc2ed75c
Change matchit plugin to use HTML patterns in unrecognized files 2024-09-05 07:02:47 +02:00
12da1ac969
Reset insert mode when switching active editor 2024-09-05 07:02:47 +02:00
5747ac0ebf
Disable switching to insert mode for some editors 2024-09-05 07:02:44 +02:00
8a47bc1840
Remove update checker 2024-09-05 07:02:36 +02:00
da4deaf698
Set custom plugin version 2024-09-05 07:02:36 +02:00
IdeaVim Bot
4c09ab4766 Add Felix Wiemuth to contributors list 2024-08-31 09:01:59 +00:00
Alex Plate
ee447bce07
Add a note to replace the raw string after the changes will be available
https://github.com/JetBrains/intellij-community/pull/2825
2024-08-30 18:57:00 +03:00
Alex Plate
5fb4c10f88
Add 2024.2 IJ for testing on TC 2024-08-30 18:42:02 +03:00
Alex Plate
ed2fcb08b0
[VIM-3620] Add a link to the usage survey 2024-08-30 18:34:10 +03:00
dedd90ce13 Fix(VIM-3615): Escape closes dialog while waiting for more keys 2024-08-30 16:46:53 +03:00
Alex Plate
73326e623e
Use the behavior form in docs 2024-08-30 16:42:03 +03:00
Alex Plate
a2bc34d6ec
[VIM-3620] Show the uninstall survey only when uninstalling IdeaVim 2024-08-30 16:41:36 +03:00
Felix Wiemuth
b652c7726a Fix typo 2024-08-30 16:41:05 +03:00
Matt Ellis
fb7a2de07b Encapsulate the command builder's state flag
This also gets rid of BAD_COMMAND, which was set but never checked - the function that set the flag would immediately reset the command builder
2024-08-30 16:36:24 +03:00
Matt Ellis
def9ca479b Ensure builder resets to a root command trie node
Also refactors command nodes a bit for better debug/trace output
2024-08-30 16:36:24 +03:00
Matt Ellis
0936e0761f Reorder CommandBuilder methods
Try to keep related functions together: awaiting arguments, count, registers, adding action/argument, processing keystrokes, build, reset.
2024-08-30 16:36:24 +03:00
Matt Ellis
09a335bcfe Start to encapsulate setting command builder state
Also rename `pushCommandPart` and `completeCommandPart`
2024-08-30 16:36:24 +03:00
Matt Ellis
37b8d69bac Remove unused EMPTY_COMMAND 2024-08-30 16:36:24 +03:00
Matt Ellis
13308050a8 Remove unnecessary OperatorArguments parameters 2024-08-30 16:36:24 +03:00
Matt Ellis
a1a553ebc9 Deprecate OperatorArguments.mode 2024-08-30 16:36:24 +03:00
Matt Ellis
5bb0c4f7cb Use editor.mode instead of OperatorArguments.mode
`OperatorArguments.mode` is the mode *before* the command was completed, rather than the current mode, which is non-obvious. E.g. for a command in Insert mode, it will still be Insert, and for a (simple) command in Normal, it will still be Normal. The only difference is that a command such as `dw` would be in Operator-pending before the command is completed. That logic is not required for this method, so it's safe to use the current mode.

This allows us to start to deprecate `OperatorArguments.mode`.
2024-08-30 16:36:24 +03:00
Matt Ellis
da6736f24a Simplify the logic when yanking during delete 2024-08-30 16:36:24 +03:00
Matt Ellis
4df1ce2ae8 Remove OperatorArguments.mode usage in block insert
`OperatorArguments.mode` is the mode *before* the command is completed, so might be Visual, Operator-pending, Insert, etc. It's not immediately obvious this is the case, so we're going to deprecate `OperatorArguments.mode` to avoid confusion with `editor.mode`.

It's not required for this method because it's only called for Visual-block mode.
2024-08-30 16:36:24 +03:00
Matt Ellis
00fd4cd491 Handle op-pending for space and backspace 2024-08-30 16:36:24 +03:00
Matt Ellis
d185672e2f Deprecate OperatorArguments.isOperatorPending
Register specific handlers for Operator-pending mode instead of relying on a runtime flag for behaviour. Also refactors and renames some arrow motion handlers.
2024-08-30 16:36:24 +03:00
Matt Ellis
55fef03a4a Add 'whichwrap' to dictionary 2024-08-30 16:36:24 +03:00
Matt Ellis
69b3e4f782 Start to refactor OperatorArguments 2024-08-30 16:36:24 +03:00
Matt Ellis
6be29b0378 Remove KeyHandler.isOperatorPending
It's easier to just look at mode. We don't need the additional check on command builder, because we can't be in OP_PENDING without pushing an operator action to the command builder
2024-08-30 16:36:24 +03:00
Matt Ellis
9965c863a6 Encapsulate command node state in builder 2024-08-30 16:36:24 +03:00
Matt Ellis
3f11ae512c Move register pending state to command builder 2024-08-30 16:36:24 +03:00
Matt Ellis
1c842eb3d8 Avoid exposing misleading count on command builder 2024-08-30 16:36:24 +03:00
Matt Ellis
c7fbce675b Update comment 2024-08-30 16:36:24 +03:00
Matt Ellis
d7e68488c8 Make Command.rawCount immutable 2024-08-30 16:36:24 +03:00
Matt Ellis
69d13a74e6 Remove unnecessary copy method 2024-08-30 16:36:24 +03:00
Matt Ellis
5a83df34a7 Replace var properties with read only 2024-08-30 16:36:24 +03:00
Matt Ellis
0a18c388e0 Simplify CommandBuilder
Fixes selecting last register if multiple registers are used in a command
2024-08-30 16:36:24 +03:00
Matt Ellis
1a3409e7df Remove count from motion argument
Only Command has a count. The motion argument is now a sealed class hierarchy, and consists only of the motion action and optional argument. This is to reduce confusion over which count to use, and potential incorrect calculation of the count
2024-08-30 16:36:24 +03:00
Matt Ellis
e93db961a0 Wrap offsets argument as an external action 2024-08-30 16:36:24 +03:00
Matt Ellis
8fd76bd08f Refactor properties for sealed Argument classes 2024-08-30 16:36:24 +03:00
Matt Ellis
0eea4a5b2c Introduce sealed classes to represent an argument 2024-08-30 16:36:24 +03:00
Matt Ellis
18a0c533e2 Remove unused OperatedRange type 2024-08-30 16:36:24 +03:00
Matt Ellis
0bd8d8f4d2 Remove unused digraph command flag 2024-08-30 16:36:24 +03:00
Alex Plate
64a89c8863
[VIM-3620] Add the uninstall survey
We're actively working on understanding the users and what we can improve in the plugin
2024-08-28 18:57:29 +03:00
Filipp Vakhitov
5b17d7740e Update generated files after merging PRs 2024-08-25 21:51:14 +03:00
162 changed files with 2069 additions and 1611 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -27,6 +27,7 @@ object Project : Project({
// Active tests // Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2024.1.1", "<default>")) buildType(TestingBuildType("2024.1.1", "<default>"))
buildType(TestingBuildType("2024.2", "<default>"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased) buildType(PropertyBased)

View File

@ -535,6 +535,10 @@ Contributors:
[![icon][github]](https://github.com/igorbabko) [![icon][github]](https://github.com/igorbabko)
&nbsp; &nbsp;
Igor Babko Igor Babko
* [![icon][mail]](mailto:533601+felixwiemuth@users.noreply.github.com)
[![icon][github]](https://github.com/felixwiemuth)
&nbsp;
Felix Wiemuth
Previous contributors: Previous contributors:

View File

@ -125,6 +125,7 @@ dependencies {
// AceJump is an optional dependency. We use their SessionManager class to check if it's active // AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19") plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "242.20224.159")
} }
moduleSources(project(":vim-engine", "sourcesJarArtifacts")) moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
@ -210,6 +211,8 @@ tasks {
} }
compileTestKotlin { compileTestKotlin {
enabled = false
kotlinOptions { kotlinOptions {
jvmTarget = javaVersion jvmTarget = javaVersion
apiVersion = "1.9" apiVersion = "1.9"

View File

@ -8,7 +8,7 @@ Every effort is made to make these options compatible with Vim behaviour.
However, some differences are inevitable. However, some differences are inevitable.
``` ```
'clipboard' 'cb' Defines clipboard behavioue 'clipboard' 'cb' Defines clipboard behavior
A comma-separated list of words to control clipboard behaviour: A comma-separated list of words to control clipboard behaviour:
unnamed The clipboard register '*' is used instead of the unnamed The clipboard register '*' is used instead of the
unnamed register unnamed register

View File

@ -16,11 +16,11 @@
# https://data.services.jetbrains.com/products?code=IC # https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases # Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots # And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
ideaVersion=2024.1.1 ideaVersion=2024.2
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC ideaType=IC
instrumentPluginCode=true instrumentPluginCode=true
version=SNAPSHOT version=chylex-41
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.23 remoteRobotVersion=0.11.23
antlrVersion=4.10.1 antlrVersion=4.10.1
@ -47,7 +47,6 @@ youtrackToken=
# Gradle settings # Gradle settings
org.gradle.jvmargs='-Dfile.encoding=UTF-8' org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.configuration-cache=true
org.gradle.caching=true org.gradle.caching=true
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary # Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary

View File

@ -8,6 +8,10 @@
package com.maddyhome.idea.vim package com.maddyhome.idea.vim
import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.ide.plugins.PluginStateListener
import com.intellij.ide.plugins.PluginStateManager
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
@ -40,6 +44,18 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
// This code should be executed once // This code should be executed once
VimPlugin.getInstance().initialize() VimPlugin.getInstance().initialize()
// Uninstall survey. Should be registered once for all projects
PluginStateManager.addStateListener(object : PluginStateListener {
override fun install(p0: IdeaPluginDescriptor) {/*Nothing*/
}
override fun uninstall(descriptor: IdeaPluginDescriptor) {
if (descriptor.pluginId == VimPlugin.getPluginId()) {
BrowserUtil.open("https://surveys.jetbrains.com/s3/ideavim-uninstall-feedback")
}
}
})
} }
} }

View File

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

View File

@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
// todo make it multicaret // todo make it multicaret
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean { private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, motionType: SelectionType): Boolean {
val func = injector.globalOptions().operatorfunc val func = injector.globalOptions().operatorfunc
if (func.isEmpty()) { if (func.isEmpty()) {
VimPlugin.showMessage(MessageHelper.message("E774")) VimPlugin.showMessage(MessageHelper.message("E774"))
@ -57,9 +57,9 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
if (value is VimFuncref) { if (value is VimFuncref) {
handler = value.handler handler = value.handler
} }
} catch (ex: ExException) { } catch (_: ExException) {
// Get the argument for function('...') or funcref('...') for the error message // Get the argument for function('...') or funcref('...') for the error message
val functionName = if (expression is FunctionCallExpression && expression.arguments.size > 0) { val functionName = if (expression is FunctionCallExpression && expression.arguments.isNotEmpty()) {
expression.arguments[0].evaluate(editor, context, scriptContext).toString() expression.arguments[0].evaluate(editor, context, scriptContext).toString()
} }
else { else {
@ -77,7 +77,7 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
return false return false
} }
val arg = when (selectionType) { val arg = when (motionType) {
SelectionType.LINE_WISE -> "line" SelectionType.LINE_WISE -> "line"
SelectionType.CHARACTER_WISE -> "char" SelectionType.CHARACTER_WISE -> "char"
SelectionType.BLOCK_WISE -> "block" SelectionType.BLOCK_WISE -> "block"
@ -101,19 +101,13 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
override val argumentType: Argument.Type = Argument.Type.MOTION override val argumentType: Argument.Type = Argument.Type.MOTION
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 as? Argument.Motion ?: return false
if (!editor.inRepeatMode) { if (!editor.inRepeatMode) {
argumentCaptured = argument argumentCaptured = argument
} }
val range = getMotionRange(editor, context, argument, operatorArguments) val range = getMotionRange(editor, context, argument, operatorArguments)
if (range != null) { if (range != null) {
val selectionType = if (argument.motion.isLinewiseMotion()) { return doOperatorAction(editor, context, range, argument.getMotionType())
SelectionType.LINE_WISE
} else {
SelectionType.CHARACTER_WISE
}
return doOperatorAction(editor, context, range, selectionType)
} }
return false return false
} }
@ -121,7 +115,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
private fun getMotionRange( private fun getMotionRange(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
argument: Argument, argument: Argument.Motion,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): TextRange? { ): TextRange? {
// Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start // Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start
@ -136,7 +130,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
operatorArguments, operatorArguments,
)?.normalize()?.let { )?.normalize()?.let {
// If we're linewise, make sure the end offset isn't just the EOL char // If we're linewise, make sure the end offset isn't just the EOL char
if (argument.motion.isLinewiseMotion() && it.endOffset < editor.fileSize()) { if (argument.getMotionType() == SelectionType.LINE_WISE && it.endOffset < editor.fileSize()) {
TextRange(it.startOffset, it.endOffset + 1) TextRange(it.startOffset, it.endOffset + 1)
} else { } else {
it it

View File

@ -25,7 +25,7 @@ internal class RepeatChangeAction : 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 state = injector.vimState val state = injector.vimState
val lastCommand = VimRepeater.lastChangeCommand var lastCommand = VimRepeater.lastChangeCommand
if (lastCommand == null && Extension.lastExtensionHandler == null) return false if (lastCommand == null && Extension.lastExtensionHandler == null) return false
@ -57,12 +57,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
) )
} else if (!repeatHandler && lastCommand != null) { } else if (!repeatHandler && lastCommand != null) {
if (cmd.rawCount > 0) { if (cmd.rawCount > 0) {
lastCommand.rawCount = cmd.count lastCommand = lastCommand.copy(rawCount = cmd.rawCount)
val arg = lastCommand.argument
if (arg != null) {
val mot = arg.motion
mot.rawCount = 0
}
} }
state.executingCommand = lastCommand state.executingCommand = lastCommand

View File

@ -40,7 +40,7 @@ class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecuti
): Boolean { ): Boolean {
injector.editorGroup.notifyIdeaJoin(editor) injector.editorGroup.notifyIdeaJoin(editor)
return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false, operatorArguments) return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false)
} }
override fun execute( override fun execute(

View File

@ -35,7 +35,7 @@ class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution()
injector.editorGroup.notifyIdeaJoin(editor) injector.editorGroup.notifyIdeaJoin(editor)
var res = true var res = true
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret -> editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) { if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true)) {
res = false res = false
} }
} }

View File

@ -69,7 +69,8 @@ object VimExtensionFacade {
@JvmStatic @JvmStatic
@Deprecated("Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)", @Deprecated(
"Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
ReplaceWith( ReplaceWith(
"VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)", "VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
"com.maddyhome.idea.vim.VimPlugin" "com.maddyhome.idea.vim.VimPlugin"
@ -195,7 +196,7 @@ object VimExtensionFacade {
@JvmStatic @JvmStatic
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? { fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
val reg = caret.registerStorage.getRegister(register) ?: return null val reg = injector.registerGroup.getRegister(register) ?: return null
return reg.keys return reg.keys
} }
@ -208,7 +209,7 @@ object VimExtensionFacade {
/** Set the current contents of the given register */ /** Set the current contents of the given register */
@JvmStatic @JvmStatic
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) { fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList()) injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList())
} }
/** Set the current contents of the given register */ /** Set the current contents of the given register */
@ -277,4 +278,4 @@ fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFu
fun interface ScriptFunction { fun interface ScriptFunction {
fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
} }

View File

@ -33,7 +33,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.EnumSet;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
@ -64,8 +63,8 @@ public class VimArgTextObjExtension implements VimExtension {
*/ */
private static class BracketPairs { private static class BracketPairs {
// NOTE: brackets must match by the position, and ordered by rank (highest to lowest). // NOTE: brackets must match by the position, and ordered by rank (highest to lowest).
@NotNull private final String openBrackets; private final @NotNull String openBrackets;
@NotNull private final String closeBrackets; private final @NotNull String closeBrackets;
static class ParseException extends Exception { static class ParseException extends Exception {
public ParseException(@NotNull String message) { public ParseException(@NotNull String message) {
@ -87,8 +86,7 @@ public class VimArgTextObjExtension implements VimExtension {
* @param bracketPairs comma-separated list of colon-separated bracket pairs. * @param bracketPairs comma-separated list of colon-separated bracket pairs.
* @throws ParseException if a syntax error is detected. * @throws ParseException if a syntax error is detected.
*/ */
@NotNull static @NotNull BracketPairs fromBracketPairList(final @NotNull String bracketPairs) throws ParseException {
static BracketPairs fromBracketPairList(@NotNull final String bracketPairs) throws ParseException {
StringBuilder openBrackets = new StringBuilder(); StringBuilder openBrackets = new StringBuilder();
StringBuilder closeBrackets = new StringBuilder(); StringBuilder closeBrackets = new StringBuilder();
ParseState state = ParseState.OPEN; ParseState state = ParseState.OPEN;
@ -128,7 +126,7 @@ public class VimArgTextObjExtension implements VimExtension {
return new BracketPairs(openBrackets.toString(), closeBrackets.toString()); return new BracketPairs(openBrackets.toString(), closeBrackets.toString());
} }
BracketPairs(@NotNull final String openBrackets, @NotNull final String closeBrackets) { BracketPairs(final @NotNull String openBrackets, final @NotNull String closeBrackets) {
assert openBrackets.length() == closeBrackets.length(); assert openBrackets.length() == closeBrackets.length();
this.openBrackets = openBrackets; this.openBrackets = openBrackets;
this.closeBrackets = closeBrackets; this.closeBrackets = closeBrackets;
@ -158,10 +156,9 @@ public class VimArgTextObjExtension implements VimExtension {
} }
} }
public static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")"); private static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")");
@Nullable private static @Nullable String bracketPairsVariable() {
private static String bracketPairsVariable() {
final Object value = VimPlugin.getVariableService().getGlobalVariableValue("argtextobj_pairs"); final Object value = VimPlugin.getVariableService().getGlobalVariableValue("argtextobj_pairs");
if (value instanceof VimString vimValue) { if (value instanceof VimString vimValue) {
return vimValue.getValue(); return vimValue.getValue();
@ -192,13 +189,12 @@ public class VimArgTextObjExtension implements VimExtension {
this.isInner = isInner; this.isInner = isInner;
} }
@Nullable
@Override @Override
public TextRange getRange(@NotNull VimEditor editor, public @Nullable TextRange getRange(@NotNull VimEditor editor,
@NotNull ImmutableVimCaret caret, @NotNull ImmutableVimCaret caret,
@NotNull ExecutionContext context, @NotNull ExecutionContext context,
int count, int count,
int rawCount) { int rawCount) {
BracketPairs bracketPairs = DEFAULT_BRACKET_PAIRS; BracketPairs bracketPairs = DEFAULT_BRACKET_PAIRS;
final String bracketPairsVar = bracketPairsVariable(); final String bracketPairsVar = bracketPairsVariable();
if (bracketPairsVar != null) { if (bracketPairsVar != null) {
@ -236,24 +232,22 @@ public class VimArgTextObjExtension implements VimExtension {
return new TextRange(finder.getLeftBound(), finder.getRightBound()); return new TextRange(finder.getLeftBound(), finder.getRightBound());
} }
@NotNull
@Override @Override
public TextObjectVisualType getVisualType() { public @NotNull TextObjectVisualType getVisualType() {
return TextObjectVisualType.CHARACTER_WISE; return TextObjectVisualType.CHARACTER_WISE;
} }
} }
@Override @Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) { public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner); final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
//noinspection DuplicatedCode //noinspection DuplicatedCode
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
int count0 = operatorArguments.getCount0();
editor.nativeCarets().forEach((VimCaret caret) -> { editor.nativeCarets().forEach((VimCaret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0); final TextRange range = textObjectHandler.getRange(editor, caret, context, Math.max(1, count0), count0);
if (range != null) { if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) { try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) { if (editor.getMode() instanceof Mode.VISUAL) {
@ -265,8 +259,7 @@ public class VimArgTextObjExtension implements VimExtension {
} }
}); });
} else { } else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class))));
} }
} }
} }
@ -276,9 +269,9 @@ public class VimArgTextObjExtension implements VimExtension {
* position * position
*/ */
private static class ArgBoundsFinder { private static class ArgBoundsFinder {
@NotNull private final CharSequence text; private final @NotNull CharSequence text;
@NotNull private final Document document; private final @NotNull Document document;
@NotNull private final BracketPairs brackets; private final @NotNull BracketPairs brackets;
private int leftBound = Integer.MAX_VALUE; private int leftBound = Integer.MAX_VALUE;
private int rightBound = Integer.MIN_VALUE; private int rightBound = Integer.MIN_VALUE;
private int leftBracket; private int leftBracket;
@ -305,7 +298,7 @@ public class VimArgTextObjExtension implements VimExtension {
* @param position starting position. * @param position starting position.
*/ */
boolean findBoundsAt(int position) throws IllegalStateException { boolean findBoundsAt(int position) throws IllegalStateException {
if (text.length() == 0) { if (text.isEmpty()) {
error = "empty document"; error = "empty document";
return false; return false;
} }

View File

@ -25,9 +25,6 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions 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.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.command.TextObjectVisualType import com.maddyhome.idea.vim.command.TextObjectVisualType
@ -52,7 +49,6 @@ 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.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 java.util.*
internal class CommentaryExtension : VimExtension { internal class CommentaryExtension : VimExtension {
@ -184,10 +180,8 @@ internal class CommentaryExtension : VimExtension {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
val keyState = KeyHandler.getInstance().keyHandlerState val keyState = KeyHandler.getInstance().keyHandlerState
keyState.commandBuilder.completeCommandPart(Argument(command)) keyState.commandBuilder.addAction(CommentaryTextObjectMotionHandler)
} }
} }

View File

@ -21,10 +21,7 @@ import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.util.Alarm import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse import com.intellij.util.Alarm.ThreadToUse
import com.jetbrains.rd.util.first
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret
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.common.ModeChangeListener import com.maddyhome.idea.vim.common.ModeChangeListener
@ -117,9 +114,9 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
initialised = false initialised = false
} }
override fun yankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) { override fun yankPerformed(editor: VimEditor, range: TextRange) {
ensureInitialised() ensureInitialised()
highlightHandler.highlightYankRange(caretToRange) highlightHandler.highlightYankRange(editor.ij, range)
} }
override fun modeChanged(editor: VimEditor, oldMode: Mode) { override fun modeChanged(editor: VimEditor, oldMode: Mode) {
@ -140,25 +137,22 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
private var lastEditor: Editor? = null private var lastEditor: Editor? = null
private val highlighters = mutableSetOf<RangeHighlighter>() private val highlighters = mutableSetOf<RangeHighlighter>()
fun highlightYankRange(caretToRange: Map<ImmutableVimCaret, TextRange>) { fun highlightYankRange(editor: Editor, range: TextRange) {
// from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted // from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted
clearYankHighlighters() clearYankHighlighters()
val editor = caretToRange.first().key.editor.ij
lastEditor = editor lastEditor = editor
val attributes = getHighlightTextAttributes(editor) val attributes = getHighlightTextAttributes(editor)
for (range in caretToRange.values) { for (i in 0 until range.size()) {
for (i in 0 until range.size()) { val highlighter = editor.markupModel.addRangeHighlighter(
val highlighter = editor.markupModel.addRangeHighlighter( range.startOffsets[i],
range.startOffsets[i], range.endOffsets[i],
range.endOffsets[i], HighlighterLayer.SELECTION,
HighlighterLayer.SELECTION, attributes,
attributes, HighlighterTargetArea.EXACT_RANGE,
HighlighterTargetArea.EXACT_RANGE, )
) highlighters.add(highlighter)
highlighters.add(highlighter)
}
} }
// from vim-highlightedyank docs: A negative number makes the highlight persistent. // from vim-highlightedyank docs: A negative number makes the highlight persistent.

View File

@ -44,6 +44,7 @@ import com.maddyhome.idea.vim.helper.enumSetOf
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
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@ -93,34 +94,29 @@ internal class Matchit : VimExtension {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
val keyState = keyHandler.keyHandlerState val keyState = keyHandler.keyHandlerState
val count = keyState.commandBuilder.count
// Reset the command count so it doesn't transfer onto subsequent commands. // Reset the command count so it doesn't transfer onto subsequent commands.
keyState.commandBuilder.resetCount() keyState.commandBuilder.resetCount()
// Normally we want to jump to the start of the matching pair. But when moving forward in operator // Normally we want to jump to the start of the matching pair. But when moving forward in operator
// pending mode, we want to include the entire match. isInOpPending makes that distinction. // pending mode, we want to include the entire match. isInOpPending makes that distinction.
val isInOpPending = keyHandler.isOperatorPending(editor.mode, keyState) if (editor.mode is Mode.OP_PENDING) {
if (isInOpPending) {
val matchitAction = MatchitAction() val matchitAction = MatchitAction()
matchitAction.reverse = reverse matchitAction.reverse = reverse
matchitAction.isInOpPending = true matchitAction.isInOpPending = true
keyState.commandBuilder.completeCommandPart( keyState.commandBuilder.addAction(matchitAction)
Argument(
Command(
count,
matchitAction,
Command.Type.MOTION,
EnumSet.noneOf(CommandFlags::class.java),
),
),
)
} else { } else {
editor.sortedCarets().forEach { caret -> editor.sortedCarets().forEach { caret ->
injector.jumpService.saveJumpLocation(editor) injector.jumpService.saveJumpLocation(editor)
caret.moveToOffset(getMatchitOffset(editor.ij, caret.ij, count, isInOpPending, reverse)) caret.moveToOffset(
getMatchitOffset(
editor.ij,
caret.ij,
operatorArguments.count0,
isInOpPending = false,
reverse
))
} }
} }
} }
@ -234,7 +230,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") { } else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns this.cMakePatterns
} else { } else {
return null this.htmlPatterns
} }
} }
@ -354,7 +350,7 @@ private object FileTypePatterns {
private val DEFAULT_PAIRS = setOf('(', ')', '[', ']', '{', '}') private val DEFAULT_PAIRS = setOf('(', ')', '[', ']', '{', '}')
private fun getMatchitOffset(editor: Editor, caret: Caret, count: Int, isInOpPending: Boolean, reverse: Boolean): Int { private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPending: Boolean, reverse: Boolean): Int {
val virtualFile = EditorHelper.getVirtualFile(editor) val virtualFile = EditorHelper.getVirtualFile(editor)
var caretOffset = caret.offset var caretOffset = caret.offset
@ -367,9 +363,9 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count: Int, isInOpPen
val currentChar = editor.document.charsSequence[caretOffset] val currentChar = editor.document.charsSequence[caretOffset]
var motionOffset: Int? = null var motionOffset: Int? = null
if (count > 0) { if (count0 > 0) {
// Matchit doesn't affect the percent motion, so we fall back to the default behavior. // Matchit doesn't affect the percent motion, so we fall back to the default behavior.
motionOffset = VimPlugin.getMotion().moveCaretToLinePercent(editor.vim, caret.vim, count) motionOffset = VimPlugin.getMotion().moveCaretToLinePercent(editor.vim, caret.vim, count0)
} else { } else {
// Check the simplest case first. // Check the simplest case first.
if (DEFAULT_PAIRS.contains(currentChar)) { if (DEFAULT_PAIRS.contains(currentChar)) {
@ -400,8 +396,7 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count: Int, isInOpPen
private fun getMotionOffset(motion: Motion): Int? { private fun getMotionOffset(motion: Motion): Int? {
return when (motion) { return when (motion) {
is Motion.AbsoluteOffset -> motion.offset is Motion.AdjustedOffset, is Motion.AbsoluteOffset -> motion.offset
is Motion.AdjustedOffset -> motion.offset
is Motion.Error, is Motion.NoMotion -> null is Motion.Error, is Motion.NoMotion -> null
} }
} }

View File

@ -555,12 +555,13 @@ private fun registerCommand(default: String, action: NerdAction) {
} }
private val actionsRoot: RootNode<NerdAction> = RootNode() private val actionsRoot: RootNode<NerdAction> = RootNode("NERDTree")
private var currentNode: CommandPartNode<NerdAction> = actionsRoot private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> { private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
return if (node is CommandPartNode<NerdAction>) { return if (node is CommandPartNode<NerdAction>) {
val res = node.keys.toMutableSet() val res = node.children.keys.toMutableSet()
res += node.values.map { collectShortcuts(it) }.flatten() res += node.children.values.map { collectShortcuts(it) }.flatten()
res res
} else { } else {
emptySet() emptySet()

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.extension.replacewithregister
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
@ -144,7 +143,7 @@ internal class ReplaceWithRegister : VimExtension {
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) { private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
val registerGroup = injector.registerGroup val registerGroup = injector.registerGroup
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret() val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return val savedRegister = registerGroup.getRegister(lastRegisterChar) ?: return
var usedType = savedRegister.type var usedType = savedRegister.type
var usedText = savedRegister.text var usedText = savedRegister.text
@ -166,18 +165,12 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
putToLine = -1, putToLine = -1,
) )
val vimEditor = editor.vim val vimEditor = editor.vim
val keyHandler = KeyHandler.getInstance()
ClipboardOptionHelper.IdeaputDisabler().use { ClipboardOptionHelper.IdeaputDisabler().use {
VimPlugin.getPut().putText( VimPlugin.getPut().putText(
vimEditor, vimEditor,
context.vim, context.vim,
putData, putData,
operatorArguments = OperatorArguments(
keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState),
0,
editor.vim.mode,
),
saveToRegister = false saveToRegister = false
) )
} }
} }

View File

@ -0,0 +1,30 @@
package com.maddyhome.idea.vim.extension.surround
import com.intellij.util.text.CharSequenceSubSequence
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
override val length = text.length * count
override fun get(index: Int): Char {
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
return text[index % text.length]
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return CharSequenceSubSequence(this, startIndex, endIndex)
}
override fun toString(): String {
return text.repeat(count)
}
companion object {
fun of(text: CharSequence, count: Int): CharSequence {
return when (count) {
0 -> ""
1 -> text
else -> RepeatedCharSequence(text, count)
}
}
}
}

View File

@ -14,6 +14,7 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
@ -36,7 +37,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
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.options.helpers.ClipboardOptionHelper import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
@ -80,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true) putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
} }
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator()) VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
} }
private class YSurroundHandler : ExtensionHandler { private class YSurroundHandler : ExtensionHandler {
@ -108,7 +112,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset) val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) { if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1) val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it) performSurround(pair, range, it, count = operatorArguments.count1)
} }
// it.moveToOffset(lineStartOffset) // it.moveToOffset(lineStartOffset)
} }
@ -131,15 +135,13 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler { private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway // NB: Operator ignores SelectionType anyway
if (!Operator().apply(editor, context, editor.mode.selectionType)) { if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
return return
} }
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
editor.exitVisualMode() editor.exitVisualMode()
editor.ij.caretModel.moveToOffset(selectionStart)
} }
} }
} }
@ -160,6 +162,10 @@ internal class VimSurroundExtension : VimExtension {
companion object { companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) { fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
}
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
// Save old register values for carets // Save old register values for carets
val surroundings = editor.sortedCarets() val surroundings = editor.sortedCarets()
.map { .map {
@ -267,20 +273,41 @@ internal class VimSurroundExtension : VimExtension {
} }
} }
private class Operator : OperatorFunction { private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij val ijEditor = vimEditor.ij
val c = getChar(ijEditor) val c = getChar(ijEditor)
if (c.code == 0) return true if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor.currentCaret()) ?: return false runWriteAction {
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE) val change = VimPlugin.getChange()
// Jump back to start if (supportsMultipleCursors) {
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) ijEditor.runWithEveryCaretAndRestore {
applyOnce(ijEditor, change, pair, count)
}
}
else {
applyOnce(ijEditor, change, pair, count)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
}
}
return true return true
} }
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
// XXX: Will it work with line-wise or block-wise selections?
val primaryCaret = editor.caretModel.primaryCaret
val range = getSurroundRange(primaryCaret.vim)
if (range != null) {
val start = RepeatedCharSequence.of(pair.first, count)
val end = RepeatedCharSequence.of(pair.second, count)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
}
}
private fun getSurroundRange(caret: VimCaret): TextRange? { private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor val editor = caret.editor
@ -375,15 +402,15 @@ private fun getChar(editor: Editor): Char {
return res return res
} }
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) { private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction { runWriteAction {
val editor = caret.editor val editor = caret.editor
val change = VimPlugin.getChange() val change = VimPlugin.getChange()
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else "" val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
val isEOF = range.endOffset == editor.text().length val isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine() val hasNewLine = editor.endsWithNewLine()
val rightSurround = if (tagsOnNewLines) { val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) { if (isEOF && !hasNewLine) {
"\n" + pair.second "\n" + pair.second
} else { } else {
@ -391,7 +418,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
} }
} else { } else {
pair.second pair.second
} }).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround) change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround) change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)

View File

@ -29,14 +29,12 @@ import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
/** /**
* Port of vim-entire: * Port of vim-entire:
* https://github.com/kana/vim-textobj-entire * <a href="https://github.com/kana/vim-textobj-entire">vim-textobj-entire</a>
* *
* <p> * <p>
* vim-textobj-entire provides two text objects: * vim-textobj-entire provides two text objects:
@ -51,7 +49,7 @@ import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingI
* </ul> * </ul>
* *
* See also the reference manual for more details at: * See also the reference manual for more details at:
* https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt * <a href="https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt">text-obj-entire.txt</a>
* *
* @author Alexandre Grison (@agrison) * @author Alexandre Grison (@agrison)
*/ */
@ -94,13 +92,12 @@ public class VimTextObjEntireExtension implements VimExtension {
this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing; this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing;
} }
@Nullable
@Override @Override
public TextRange getRange(@NotNull VimEditor editor, public @Nullable TextRange getRange(@NotNull VimEditor editor,
@NotNull ImmutableVimCaret caret, @NotNull ImmutableVimCaret caret,
@NotNull ExecutionContext context, @NotNull ExecutionContext context,
int count, int count,
int rawCount) { int rawCount) {
int start = 0, end = ((IjVimEditor)editor).getEditor().getDocument().getTextLength(); int start = 0, end = ((IjVimEditor)editor).getEditor().getDocument().getTextLength();
// for the `ie` text object we don't want leading an trailing spaces // for the `ie` text object we don't want leading an trailing spaces
@ -125,24 +122,22 @@ public class VimTextObjEntireExtension implements VimExtension {
return new TextRange(start, end); return new TextRange(start, end);
} }
@NotNull
@Override @Override
public TextObjectVisualType getVisualType() { public @NotNull TextObjectVisualType getVisualType() {
return TextObjectVisualType.CHARACTER_WISE; return TextObjectVisualType.CHARACTER_WISE;
} }
} }
@Override @Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) { public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing); final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
//noinspection DuplicatedCode //noinspection DuplicatedCode
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
int count0 = operatorArguments.getCount0();
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> { ((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0); final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, Math.max(1, count0), count0);
if (range != null) { if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) { try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) { if (editor.getMode() instanceof Mode.VISUAL) {
@ -155,9 +150,7 @@ public class VimTextObjEntireExtension implements VimExtension {
}); });
} else { } else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
textObjectHandler, Command.Type.MOTION,
EnumSet.noneOf(CommandFlags.class))));
} }
} }
} }

View File

@ -30,14 +30,12 @@ import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
/** /**
* Port of vim-indent-object: * Port of vim-indent-object:
* https://github.com/michaeljsmith/vim-indent-object * <a href="https://github.com/michaeljsmith/vim-indent-object">vim-indent-object</a>
* *
* <p> * <p>
* vim-indent-object provides these text objects based on the cursor line's indentation: * vim-indent-object provides these text objects based on the cursor line's indentation:
@ -49,7 +47,7 @@ import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
* </ul> * </ul>
* *
* See also the reference manual for more details at: * See also the reference manual for more details at:
* https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt * <a href="https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt">indent-object.txt</a>
* *
* @author Shrikant Kandula (@sharat87) * @author Shrikant Kandula (@sharat87)
*/ */
@ -98,13 +96,12 @@ public class VimIndentObject implements VimExtension {
this.includeBelow = includeBelow; this.includeBelow = includeBelow;
} }
@Nullable
@Override @Override
public TextRange getRange(@NotNull VimEditor editor, public @Nullable TextRange getRange(@NotNull VimEditor editor,
@NotNull ImmutableVimCaret caret, @NotNull ImmutableVimCaret caret,
@NotNull ExecutionContext context, @NotNull ExecutionContext context,
int count, int count,
int rawCount) { int rawCount) {
final CharSequence charSequence = ((IjVimEditor)editor).getEditor().getDocument().getCharsSequence(); final CharSequence charSequence = ((IjVimEditor)editor).getEditor().getDocument().getCharsSequence();
final int caretOffset = ((IjVimCaret)caret).getCaret().getOffset(); final int caretOffset = ((IjVimCaret)caret).getCaret().getOffset();
@ -249,9 +246,8 @@ public class VimIndentObject implements VimExtension {
return new TextRange(upperBoundaryOffset, lowerBoundaryOffset); return new TextRange(upperBoundaryOffset, lowerBoundaryOffset);
} }
@NotNull
@Override @Override
public TextObjectVisualType getVisualType() { public @NotNull TextObjectVisualType getVisualType() {
return TextObjectVisualType.LINE_WISE; return TextObjectVisualType.LINE_WISE;
} }
@ -264,15 +260,14 @@ public class VimIndentObject implements VimExtension {
@Override @Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) { public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
IjVimEditor vimEditor = (IjVimEditor)editor; IjVimEditor vimEditor = (IjVimEditor)editor;
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow); final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
int count0 = operatorArguments.getCount0();
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> { ((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0); final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, Math.max(1, count0), count0);
if (range != null) { if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) { try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) { if (editor.getMode() instanceof Mode.VISUAL) {
@ -285,9 +280,7 @@ public class VimIndentObject implements VimExtension {
}); });
} else { } else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
textObjectHandler, Command.Type.MOTION,
EnumSet.noneOf(CommandFlags.class))));
} }
} }
} }

View File

@ -39,7 +39,6 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret
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
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import kotlin.math.min
/** /**
* Provides all the insert/replace related functionality * Provides all the insert/replace related functionality
@ -130,6 +129,7 @@ class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext, context: ExecutionContext,
range: TextRange, range: TextRange,
) { ) {
val startPos = editor.offsetToBufferPosition(caret.offset)
val startOffset = editor.getLineStartForOffset(range.startOffset) val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset) val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor val ijEditor = (editor as IjVimEditor).editor
@ -154,11 +154,7 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
val afterAction = { val afterAction = {
val firstLine = editor.offsetToBufferPosition( caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
).line
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line) restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
} }
if (project != null) { if (project != null) {

View File

@ -139,7 +139,7 @@ object IjOptions {
// Temporary feature flags during development, not really intended for external use // Temporary feature flags during development, not really intended for external use
val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true)) val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true)) val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isHidden = true)) val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true)) val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true)) val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))

View File

@ -0,0 +1,68 @@
package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import java.util.function.BooleanSupplier
internal object MacroAutoImport {
fun run(editor: Editor, dataContext: DataContext) {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
return
}
val importers = ReferenceImporter.EP_NAME.extensionList
if (importers.isEmpty()) {
return
}
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
override fun run(indicator: ProgressIndicator) {
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
val fixes = mutableListOf<BooleanSupplier>()
file.accept(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
for (reference in element.references) {
if (reference.resolve() != null) {
continue
}
for (importer in importers) {
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
?.let(fixes::add)
}
}
super.visitElement(element)
}
})
return@nonBlocking fixes
}.executeSynchronously()
ApplicationManager.getApplication().invokeAndWait {
WriteCommandAction.writeCommandAction(project)
.withName("Auto Import")
.withGroupId("IdeaVimAutoImportAfterMacro")
.shouldRecordActionForActiveDocument(true)
.run<RuntimeException> {
fixes.forEach { it.asBoolean }
}
}
}
})
}
}

View File

@ -22,6 +22,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.MessageHelper.message import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.macro.VimMacroBase import com.maddyhome.idea.vim.macro.VimMacroBase
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
/** /**
* Used to handle playback of macros * Used to handle playback of macros
@ -93,6 +94,9 @@ internal class MacroGroup : VimMacroBase() {
} finally { } finally {
keyStack.removeFirst() keyStack.removeFirst()
} }
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
} }
if (isInternalMacro) { if (isInternalMacro) {

View File

@ -35,7 +35,7 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.command.MotionType
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExOutputModel import com.maddyhome.idea.vim.handler.ExternalActionHandler
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.handler.MotionActionHandler import com.maddyhome.idea.vim.handler.MotionActionHandler
@ -193,21 +193,16 @@ internal class MotionGroup : VimMotionGroupBase() {
argument: Argument, argument: Argument,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): TextRange? { ): TextRange? {
if (argument !is Argument.Motion) {
throw RuntimeException("Unexpected argument passed to getMotionRange2: $argument")
}
var start: Int var start: Int
var end: Int var end: Int
if (argument.type === Argument.Type.OFFSETS) {
val offsets = argument.offsets[caret.vim] ?: return null
val (first, second) = offsets.getNativeStartAndEnd()
start = first
end = second
} else {
val cmd = argument.motion
// Normalize the counts between the command and the motion argument
val cnt = cmd.count * operatorArguments.count1
val raw = if (operatorArguments.count0 == 0 && cmd.rawCount == 0) 0 else cnt
if (cmd.action is MotionActionHandler) {
val action = cmd.action as MotionActionHandler
val action = argument.motion
when (action) {
is MotionActionHandler -> {
// This is where we are now // This is where we are now
start = caret.offset start = caret.offset
@ -216,8 +211,8 @@ internal class MotionGroup : VimMotionGroupBase() {
editor.vim, editor.vim,
caret.vim, caret.vim,
IjEditorExecutionContext(context!!), IjEditorExecutionContext(context!!),
cmd.argument, argument.argument,
operatorArguments.withCount0(raw), operatorArguments
) )
// Invalid motion // Invalid motion
@ -233,22 +228,32 @@ internal class MotionGroup : VimMotionGroupBase() {
end++ end++
} }
} }
} else if (cmd.action is TextObjectActionHandler) { }
val action = cmd.action as TextObjectActionHandler
val range = is TextObjectActionHandler -> {
action.getRange(editor.vim, caret.vim, IjEditorExecutionContext(context!!), cnt, raw) ?: return null val range = action.getRange(
editor.vim,
caret.vim,
IjEditorExecutionContext(context!!),
operatorArguments.count1,
operatorArguments.count0
) ?: return null
start = range.startOffset start = range.startOffset
end = range.endOffset end = range.endOffset
if (cmd.isLinewiseMotion()) end-- if (argument.isLinewiseMotion()) end--
} else {
throw RuntimeException(
"Commands doesn't take " + cmd.action.javaClass.simpleName + " as an operator",
)
} }
is ExternalActionHandler -> {
val range = action.getRange(caret.vim) ?: return null
start = range.startOffset
end = range.endOffset
}
else -> throw RuntimeException("Commands doesn't take " + action.javaClass.simpleName + " as an operator")
} }
// This is a kludge for dw, dW, and d[w. Without this kludge, an extra newline is operated when it shouldn't be. // This is a kludge for dw, dW, and d[w. Without this kludge, an extra newline is operated when it shouldn't be.
val id = argument.motion.action.id val id = argument.motion.id
if (id == VimChangeGroupBase.VIM_MOTION_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_BIG_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_CAMEL_RIGHT) { if (id == VimChangeGroupBase.VIM_MOTION_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_BIG_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_CAMEL_RIGHT) {
val text = editor.document.charsSequence.subSequence(start, end).toString() val text = editor.document.charsSequence.subSequence(start, end).toString()
val lastNewLine = text.lastIndexOf('\n') val lastNewLine = text.lastIndexOf('\n')
@ -258,6 +263,7 @@ internal class MotionGroup : VimMotionGroupBase() {
} }
} }
} }
return TextRange(start, end) return TextRange(start, end)
} }

View File

@ -218,13 +218,17 @@ internal class VimEnterHandler(nextHandler: EditorActionHandler?) : VimKeyHandle
internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) { internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
override val key: String = "<Esc>" override val key: String = "<Esc>"
private val ideaVimSupportDialog
get() = injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean { override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
val ideaVimSupportDialog =
injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
return editor.isPrimaryEditor() || return editor.isPrimaryEditor() ||
EditorHelper.isFileEditor(editor) && !editor.vim.mode.inNormalMode || EditorHelper.isFileEditor(editor) && vimStateNeedsToHandleEscape(editor) ||
ideaVimSupportDialog && !editor.vim.mode.inNormalMode ideaVimSupportDialog && vimStateNeedsToHandleEscape(editor)
}
private fun vimStateNeedsToHandleEscape(editor: Editor): Boolean {
return !editor.vim.mode.inNormalMode || KeyHandler.getInstance().keyHandlerState.mappingState.hasKeys
} }
} }

View File

@ -326,7 +326,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight); final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@NotNull final VimEditor editor1 = new IjVimEditor(editor); @NotNull final VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1; final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine); final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen. // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.

View File

@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeWithMe.ClientId import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
@ -20,6 +21,8 @@ import com.maddyhome.idea.vim.api.StringListOptionValue
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
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.state.mode.inBlockSelection
import java.awt.Component import java.awt.Component
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JTable import javax.swing.JTable
@ -96,3 +99,41 @@ internal val Caret.vimLine: Int
*/ */
internal val Editor.vimLine: Int internal val Editor.vimLine: Int
get() = this.caretModel.currentCaret.vimLine get() = this.caretModel.currentCaret.vimLine
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
val caretModel = this.caretModel
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
if (carets == null || carets.size == 1) {
action()
}
else {
var initialDocumentSize = this.document.textLength
var documentSizeDifference = 0
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
val restoredCarets = mutableListOf<CaretState>()
caretModel.removeSecondaryCarets()
for ((selectionStart, selectionEnd) in caretOffsets) {
if (selectionStart == selectionEnd) {
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
}
else {
caretModel.primaryCaret.setSelection(
selectionStart + documentSizeDifference,
selectionEnd + documentSizeDifference
)
}
action()
restoredCarets.add(caretModel.caretsAndSelections.single())
val documentLength = this.document.textLength
documentSizeDifference += documentLength - initialDocumentSize
initialDocumentSize = documentLength
}
caretModel.caretsAndSelections = restoredCarets
}
}

View File

@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.application.ex.ApplicationManagerEx import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
@ -61,6 +62,7 @@ internal class IjActionExecutor : VimActionExecutor {
override val ACTION_EXPAND_REGION_RECURSIVELY: String override val ACTION_EXPAND_REGION_RECURSIVELY: String
get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY
override val ACTION_EXPAND_COLLAPSE_TOGGLE: String override val ACTION_EXPAND_COLLAPSE_TOGGLE: String
// [VERSION UPDATE] 2024.3+ Replace raw "ExpandCollapseToggleAction" with IdeActions.ACTION_EXPAND_COLLAPSE_TOGGLE_REGION from the platform.
get() = "ExpandCollapseToggleAction" get() = "ExpandCollapseToggleAction"
/** /**
@ -94,6 +96,7 @@ internal class IjActionExecutor : VimActionExecutor {
ActionManager.getInstance(), ActionManager.getInstance(),
0, 0,
) )
Utils.initUpdateSession(event)
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems. // beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
// because rider use async update method. See VIM-1819. // because rider use async update method. See VIM-1819.
// This method executes inside of lastUpdateAndCheckDumb // This method executes inside of lastUpdateAndCheckDumb

View File

@ -16,7 +16,6 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndForOffset import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
@ -94,6 +93,6 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
} }
} }
internal fun Editor.exitInsertMode(context: DataContext, operatorArguments: OperatorArguments) { internal fun Editor.exitInsertMode(context: DataContext) {
VimPlugin.getChange().processEscape(IjVimEditor(this), IjEditorExecutionContext(context), operatorArguments) VimPlugin.getChange().processEscape(IjVimEditor(this), IjEditorExecutionContext(context))
} }

View File

@ -60,7 +60,7 @@ internal object ScrollViewHelper {
// that this needs to be replaced as a more or less dumb line for line rewrite. // that this needs to be replaced as a more or less dumb line for line rewrite.
val topLine = getVisualLineAtTopOfScreen(editor) val topLine = getVisualLineAtTopOfScreen(editor)
val bottomLine = getVisualLineAtBottomOfScreen(editor) val bottomLine = getVisualLineAtBottomOfScreen(editor)
val lastLine = vimEditor.getVisualLineCount() - 1 val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
val scrollOffset = injector.options(vimEditor).scrolloff val scrollOffset = injector.options(vimEditor).scrolloff

View File

@ -28,6 +28,8 @@ import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
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.inVisualMode
import com.maddyhome.idea.vim.undo.UndoRedoBase import com.maddyhome.idea.vim.undo.UndoRedoBase
/** /**
@ -66,15 +68,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking { editor.runWithChangeTracking {
undoManager.undo(fileEditor) undoManager.undo(fileEditor)
restoreVisualMode(editor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor)
}
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
} }
} else { } else {
notifyAboutNewUndo(editor.ij.project) notifyAboutNewUndo(editor.ij.project)
@ -108,7 +102,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
private fun hasSelection(editor: VimEditor): Boolean { private fun hasSelection(editor: VimEditor): Boolean {
return editor.primaryCaret().ij.hasSelection() return editor.primaryCaret().ij.hasSelection()
} }
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean { override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
val ijContext = context.context as DataContext val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
@ -229,4 +223,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
val hasChanges: Boolean val hasChanges: Boolean
get() = changeListener.hasChanged || initialPath != editor.getPath() get() = changeListener.hasChanged || initialPath != editor.getPath()
} }
private fun restoreVisualMode(editor: VimEditor) {
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
// identified as visual block mode, leading to false positives.
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
// visual block mode.
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
SelectionType.CHARACTER_WISE
else
detectedMode
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
}
}
} }

View File

@ -18,13 +18,12 @@ import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
import com.maddyhome.idea.vim.api.LocalMarkStorage import com.maddyhome.idea.vim.api.LocalMarkStorage
import com.maddyhome.idea.vim.api.SelectionInfo import com.maddyhome.idea.vim.api.SelectionInfo
import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.ex.ExOutputModel import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.group.visual.VisualChange import com.maddyhome.idea.vim.group.visual.VisualChange
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.common.VimEditorReplaceMask import com.maddyhome.idea.vim.common.VimEditorReplaceMask
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
@ -97,7 +96,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr {
} }
// TODO: Data could be lost during visual block motion // TODO: Data could be lost during visual block motion
internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretToEditor()
internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor() internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor() internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()

View File

@ -1,32 +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.helper
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.icons.VimIcons
@Service(Service.Level.APP)
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
VimPlugin.getPluginId(),
updateTimestampProperty = PROPERTY_NAME,
NotificationService.IDEAVIM_STICKY_GROUP,
VimIcons.IDEAVIM,
) {
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
companion object {
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
fun getInstance(): VimStandalonePluginUpdateChecker = service()
}
}

View File

@ -10,16 +10,12 @@ package com.maddyhome.idea.vim.listener
import com.intellij.execution.impl.ConsoleViewImpl import com.intellij.execution.impl.ConsoleViewImpl
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorKind
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.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
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.command.OperatorArguments
import com.maddyhome.idea.vim.common.EditorListener 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.helper.inInsertMode
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
@ -32,8 +28,8 @@ import com.maddyhome.idea.vim.state.mode.Mode
*/ */
class IJEditorFocusListener : EditorListener { class IJEditorFocusListener : EditorListener {
override fun focusGained(editor: VimEditor) { override fun focusGained(editor: VimEditor) {
val oldEditor = KeyHandler.getInstance().editorInFocus val editorInFocus = KeyHandler.getInstance().editorInFocus
if (oldEditor != null && oldEditor.ij == editor.ij) return if (editorInFocus != null && editorInFocus.ij == editor.ij) return
KeyHandler.getInstance().editorInFocus = editor KeyHandler.getInstance().editorInFocus = editor
@ -64,13 +60,11 @@ class IJEditorFocusListener : EditorListener {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor) val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
VimPlugin.getChange().insertBeforeCursor(editor, context) VimPlugin.getChange().insertBeforeCursor(editor, context)
} }
if (isTerminal(ijEditor) && !ijEditor.inInsertMode) { if (!ijEditor.document.isWritable) {
switchToInsertMode.run()
} else if (ijEditor.isInsertMode && ((oldEditor != null && isTerminal(oldEditor.ij)) || !ijEditor.document.isWritable)) {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor) val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
val mode = injector.vimState.mode val mode = injector.vimState.mode
when (mode) { when (mode) {
is Mode.INSERT -> editor.exitInsertMode(context, OperatorArguments(false, 0, mode)) is Mode.INSERT -> editor.exitInsertMode(context)
else -> {} else -> {}
} }
} }
@ -83,12 +77,5 @@ class IJEditorFocusListener : EditorListener {
} }
KeyHandler.getInstance().reset(editor) KeyHandler.getInstance().reset(editor)
} }
}
// By "terminal" we refer to some editor that should switch to INSERT mode on focus
private fun isTerminal(ijEditor: Editor): Boolean {
return !ijEditor.isViewer &&
!EditorHelper.isFileEditor(ijEditor) &&
ijEditor.document.isWritable &&
ijEditor.editorKind != EditorKind.DIFF
}
}

View File

@ -16,7 +16,9 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
import com.intellij.codeInsight.template.Template import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateEditingAdapter import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.codeInsight.template.TemplateManagerListener import com.intellij.codeInsight.template.TemplateManagerListener
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.impl.TemplateState import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.codeInsight.template.impl.actions.NextVariableAction
import com.intellij.find.FindModelListener import com.intellij.find.FindModelListener
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.ActionUpdateThread
@ -28,6 +30,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.ex.AnActionListener import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.ScrollingModelImpl
import com.intellij.openapi.keymap.KeymapManager import com.intellij.openapi.keymap.KeymapManager
import com.intellij.openapi.project.DumbAwareToggleAction import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
@ -58,6 +61,7 @@ internal object IdeaSpecifics {
private val surrounderAction = private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction" "com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null private var editor: Editor? = null
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null private var completionPrevDocumentOffset: Int? = null
@ -67,6 +71,7 @@ internal object IdeaSpecifics {
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR) val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) { if (hostEditor != null) {
editor = hostEditor editor = hostEditor
caretOffset = hostEditor.caretModel.offset
} }
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
@ -115,42 +120,61 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return if (VimPlugin.isNotEnabled()) return
val editor = editor val editor = editor
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) { if (editor != null) {
val prevDocumentLength = completionPrevDocumentLength if (action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentOffset = completionPrevDocumentOffset val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (prevDocumentLength != null && prevDocumentOffset != null) { if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister() val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset) val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0) val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength))) register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
repeat(caretShift.coerceAtLeast(0)) { repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow) register.recordKeyStroke(leftArrow)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
}
) {
editor?.let {
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
} }
} }
else if (action is NextVariableAction && TemplateManagerImpl.getTemplateState(editor) == null) {
this.completionPrevDocumentLength = null editor.vim.exitInsertMode(event.dataContext.vim)
this.completionPrevDocumentOffset = null KeyHandler.getInstance().reset(editor.vim)
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
} }
) { //endregion
editor?.let {
it.vim.mode = Mode.NORMAL() if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim) val scrollModel = editor.scrollingModel as ScrollingModelImpl
KeyHandler.getInstance().reset(it.vim) if (scrollModel.isScrollingNow) {
val v = scrollModel.verticalScrollOffset
val h = scrollModel.horizontalScrollOffset
scrollModel.finishAnimation()
scrollModel.scroll(h, v)
scrollModel.finishAnimation()
}
injector.scroll.scrollCaretIntoView(editor.vim)
} }
} }
//endregion
this.editor = null this.editor = null
this.caretOffset = -1
} }
} }

View File

@ -35,6 +35,7 @@ import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
import com.intellij.openapi.editor.ex.FocusChangeListener import com.intellij.openapi.editor.ex.FocusChangeListener
import com.intellij.openapi.editor.impl.EditorComponentImpl import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerEvent import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.fileEditor.FileEditorManagerListener
@ -45,11 +46,14 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
import com.intellij.openapi.fileEditor.impl.EditorComposite import com.intellij.openapi.fileEditor.impl.EditorComposite
import com.intellij.openapi.fileEditor.impl.EditorWindow import com.intellij.openapi.fileEditor.impl.EditorWindow
import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.rd.createLifetime
import com.intellij.openapi.rd.createNestedDisposable
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.removeUserData import com.intellij.openapi.util.removeUserData
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ExceptionUtil import com.intellij.util.ExceptionUtil
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.VimKeyListener import com.maddyhome.idea.vim.VimKeyListener
@ -62,7 +66,6 @@ import com.maddyhome.idea.vim.api.coerceOffset
import com.maddyhome.idea.vim.api.getLineEndForOffset import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.group.EditorGroup import com.maddyhome.idea.vim.group.EditorGroup
import com.maddyhome.idea.vim.group.FileGroup import com.maddyhome.idea.vim.group.FileGroup
import com.maddyhome.idea.vim.group.IjOptions import com.maddyhome.idea.vim.group.IjOptions
@ -79,7 +82,6 @@ import com.maddyhome.idea.vim.handler.keyCheckRequests
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
import com.maddyhome.idea.vim.helper.GuicursorChangeListener import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
import com.maddyhome.idea.vim.helper.exitSelectMode import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.forceBarCursor import com.maddyhome.idea.vim.helper.forceBarCursor
@ -95,6 +97,7 @@ import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
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.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inSelectMode import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.selectionType import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
@ -103,7 +106,6 @@ import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
import com.maddyhome.idea.vim.vimDisposable
import java.awt.event.MouseAdapter import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent import java.awt.event.MouseEvent
import javax.swing.SwingUtilities import javax.swing.SwingUtilities
@ -285,12 +287,10 @@ internal object VimListenerManager {
// TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised // TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
if (vimDisabled(editor)) return if (vimDisabled(editor)) return
// As I understand, there is no need to pass a disposable that also disposes on editor close val pluginLifetime = VimPlugin.getInstance().createLifetime()
// because all editor resources will be garbage collected anyway on editor close val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
// Note that this uses the plugin's main disposable, rather than VimPlugin.onOffDisposable, because we don't need val disposable =
// to - we explicitly call VimListenerManager.removeAll from VimPlugin.turnOffPlugin, and this disposes each Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
// editor's disposable individually.
val disposable = editor.project?.vimDisposable ?: return
// Protect against double initialisation // Protect against double initialisation
if (editor.getUserData(editorListenersDisposableKey) != null) { if (editor.getUserData(editorListenersDisposableKey) != null) {
@ -386,7 +386,18 @@ internal object VimListenerManager {
override fun selectionChanged(event: FileEditorManagerEvent) { override fun selectionChanged(event: FileEditorManagerEvent) {
// We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly // We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly
if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return
val newEditor = event.newEditor
if (newEditor is TextEditor) {
val editor = newEditor.editor
if (editor.isInsertMode) {
editor.vim.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
// Breaks relativenumber for some reason
// injector.scroll.scrollCaretIntoView(editor.vim)
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event) MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event) FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event) VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
@ -458,8 +469,6 @@ internal object VimListenerManager {
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused)) event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
} }
VimStandalonePluginUpdateChecker.getInstance().pluginUsed()
} }
override fun editorReleased(event: EditorFactoryEvent) { override fun editorReleased(event: EditorFactoryEvent) {

View File

@ -12,8 +12,6 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.VisualPosition
import com.maddyhome.idea.vim.api.BufferPosition import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.CaretRegisterStorage
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.LocalMarkStorage import com.maddyhome.idea.vim.api.LocalMarkStorage
import com.maddyhome.idea.vim.api.SelectionInfo import com.maddyhome.idea.vim.api.SelectionInfo
@ -29,7 +27,6 @@ import com.maddyhome.idea.vim.helper.insertHistory
import com.maddyhome.idea.vim.helper.lastSelectionInfo import com.maddyhome.idea.vim.helper.lastSelectionInfo
import com.maddyhome.idea.vim.helper.markStorage import com.maddyhome.idea.vim.helper.markStorage
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.helper.registerStorage
import com.maddyhome.idea.vim.helper.resetVimLastColumn import com.maddyhome.idea.vim.helper.resetVimLastColumn
import com.maddyhome.idea.vim.helper.vimInsertStart import com.maddyhome.idea.vim.helper.vimInsertStart
import com.maddyhome.idea.vim.helper.vimLastColumn import com.maddyhome.idea.vim.helper.vimLastColumn
@ -41,18 +38,6 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
internal class IjVimCaret(val caret: Caret) : VimCaretBase() { internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
override val registerStorage: CaretRegisterStorage
get() {
var storage = this.caret.registerStorage
if (storage == null) {
initInjector() // To initialize injector used in CaretRegisterStorageBase
storage = CaretRegisterStorageBase(this)
this.caret.registerStorage = storage
} else if (storage.caret != this) {
storage.caret = this
}
return storage
}
override val markStorage: LocalMarkStorage override val markStorage: LocalMarkStorage
get() { get() {
var storage = this.caret.markStorage var storage = this.caret.markStorage

View File

@ -38,9 +38,7 @@ import com.maddyhome.idea.vim.api.VimSelectionModel
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.VirtualFile import com.maddyhome.idea.vim.api.VirtualFile
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.IndentConfig import com.maddyhome.idea.vim.common.IndentConfig
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
import com.maddyhome.idea.vim.common.LiveRange import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.common.ModeChangeListener import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
@ -99,7 +97,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
editor.vimChangeActionSwitchMode = value editor.vimChangeActionSwitchMode = value
} }
override val indentConfig: VimIndentConfig override val indentConfig: VimIndentConfig
get() = create(editor) get() = IndentConfig.create(editor)
override fun fileSize(): Long = editor.fileSize.toLong() override fun fileSize(): Long = editor.fileSize.toLong()
@ -173,21 +171,38 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
return editor.caretModel.allCarets.map { IjVimCaret(it) } return editor.caretModel.allCarets.map { IjVimCaret(it) }
} }
override var isFirstCaret = true
override var isReversingCarets = false
@Suppress("ideavimRunForEachCaret") @Suppress("ideavimRunForEachCaret")
override fun forEachCaret(action: (VimCaret) -> Unit) { override fun forEachCaret(action: (VimCaret) -> Unit) {
if (editor.vim.inBlockSelection) { if (editor.vim.inBlockSelection) {
action(IjVimCaret(editor.caretModel.primaryCaret)) action(IjVimCaret(editor.caretModel.primaryCaret))
} else { } else {
editor.caretModel.runForEachCaret({ try {
if (it.isValid) { editor.caretModel.runForEachCaret({
action(IjVimCaret(it)) if (it.isValid) {
} action(IjVimCaret(it))
}, false) isFirstCaret = false
}
}, false)
} finally {
isFirstCaret = true
}
} }
} }
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) { override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse) isReversingCarets = reverse
try {
editor.caretModel.runForEachCaret({
action(IjVimCaret(it))
isFirstCaret = false
}, reverse)
} finally {
isFirstCaret = true
isReversingCarets = false
}
} }
override fun isInForEachCaretScope(): Boolean { override fun isInForEachCaretScope(): Boolean {
@ -389,8 +404,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight)) return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight))
} }
override fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) { override fun exitInsertMode(context: ExecutionContext) {
editor.exitInsertMode(context.ij, operatorArguments) editor.exitInsertMode(context.ij)
} }
override fun exitSelectModeNative(adjustCaret: Boolean) { override fun exitSelectModeNative(adjustCaret: Boolean) {
@ -539,4 +554,4 @@ internal class InsertTimeRecorder: ModeChangeListener {
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) } editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
} }
} }
} }

View File

@ -192,6 +192,13 @@ private object VimActionsPopup {
null, null,
), ),
) )
actionGroup.add(
HelpLink(
"Take Survey ↗",
"https://surveys.jetbrains.com/s3/ideavim-usage-survey",
AllIcons.Actions.IntentionBulb,
),
)
actionGroup.addSeparator(MessageHelper.message("action.eap.choice.active.text")) actionGroup.addSeparator(MessageHelper.message("action.eap.choice.active.text"))
actionGroup.add(JoinEap) actionGroup.add(JoinEap)

View File

@ -23,7 +23,10 @@ import com.maddyhome.idea.vim.EventFacade;
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.action.VimShortcutKeyAction; import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.VimCommandLine;
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
import com.maddyhome.idea.vim.ex.ranges.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.SearchHighlightsHelper; import com.maddyhome.idea.vim.helper.SearchHighlightsHelper;
import com.maddyhome.idea.vim.helper.UiHelper; import com.maddyhome.idea.vim.helper.UiHelper;
@ -341,14 +344,14 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
} }
} }
// Get the current count from the command builder. This value is coerced to at least 1, so will always be valid. // Get a snapshot of the count for the in progress command, and coerce it to 1. This value will include all
// The aggregated value includes any counts for operator and register selections, and the uncommitted count for // count components - selecting register(s), operator and motions. E.g. `2"a3"b4"c5d6/` will return 720.
// the search command (`/` or `?`). E.g., `2"a3"b4"c5d6/` would return 720. // If we're showing highlights for an Ex command like `:s`, the command builder will be empty, but we'll still
// If we're showing highlights for an ex command like `:s`, there won't be a command, but the value is already // get a valid value.
// coerced to at least 1. int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder()
int count1 = KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getAggregatedUncommittedCount(); .calculateCount0Snapshot());
if (labelText.equals("/") || labelText.equals("?") || searchCommand) { if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
int pattenEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0); int pattenEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
final String pattern = searchText.substring(0, pattenEnd); final String pattern = searchText.substring(0, pattenEnd);
@ -528,9 +531,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName()); private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
@NotNull
@Override @Override
public VimCommandLineCaret getCaret() { public @NotNull VimCommandLineCaret getCaret() {
return (VimCommandLineCaret) entry.getCaret(); return (VimCommandLineCaret) entry.getCaret();
} }
@ -548,9 +550,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
entry.clearCurrentAction(); entry.clearCurrentAction();
} }
@Nullable
@Override @Override
public Integer getPromptCharacterOffset() { public @Nullable Integer getPromptCharacterOffset() {
int offset = entry.currentActionPromptCharacterOffset; int offset = entry.currentActionPromptCharacterOffset;
return offset == -1 ? null : offset; return offset == -1 ? null : offset;
} }
@ -570,8 +571,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
IdeFocusManager.findInstance().requestFocus(entry, true); IdeFocusManager.findInstance().requestFocus(entry, true);
} }
@Nullable public @Nullable VimInputInterceptor<?> getInputInterceptor() {
public VimInputInterceptor<?> getInputInterceptor() {
return myInputInterceptor; return myInputInterceptor;
} }
@ -584,15 +584,13 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
myInputInterceptor = vimInputInterceptor; myInputInterceptor = vimInputInterceptor;
} }
@Nullable
@Override @Override
public Function1<String, Unit> getInputProcessing() { public @Nullable Function1<String, Unit> getInputProcessing() {
return inputProcessing; return inputProcessing;
} }
@Nullable
@Override @Override
public Character getFinishOn() { public @Nullable Character getFinishOn() {
return finishOn; return finishOn;
} }

View File

@ -1,12 +1,4 @@
<!-- <idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
~ 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.
-->
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IdeaVim</name> <name>IdeaVim</name>
<id>IdeaVIM</id> <id>IdeaVIM</id>
<description><![CDATA[ <description><![CDATA[
@ -21,7 +13,7 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li> <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul> </ul>
]]></description> ]]></description>
<version>SNAPSHOT</version> <version>chylex</version>
<vendor>JetBrains</vendor> <vendor>JetBrains</vendor>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) --> <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
@ -133,10 +125,12 @@
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/> <xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions resource-bundle="messages.IdeaVimBundle"> <actions resource-bundle="messages.IdeaVimBundle">
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"> <group id="com.chylex.intellij.vim" text="Vim" popup="true">
<add-to-group group-id="ToolsMenu" anchor="last"/> <add-to-group group-id="ToolsMenu" anchor="last"/>
</action> <action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/>
<action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/>
</group>
<!-- Internal --> <!-- Internal -->
<!--suppress PluginXmlI18n --> <!--suppress PluginXmlI18n -->
<action id="VimInternalAddBlockInlays" class="com.maddyhome.idea.vim.action.internal.AddBlockInlaysAction" text="Add Test Block Inlays | IdeaVim Internal" internal="true"/> <action id="VimInternalAddBlockInlays" class="com.maddyhome.idea.vim.action.internal.AddBlockInlaysAction" text="Add Test Block Inlays | IdeaVim Internal" internal="true"/>
@ -154,5 +148,6 @@
</group> </group>
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/> <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
</actions> </actions>
</idea-plugin> </idea-plugin>

View File

@ -44,6 +44,7 @@ viminfo
virtualedit virtualedit
visualbell visualbell
visualdelay visualdelay
whichwrap
wrapscan wrapscan
nobomb nobomb

View File

@ -4,16 +4,6 @@
"class": "com.maddyhome.idea.vim.action.change.RepeatChangeAction", "class": "com.maddyhome.idea.vim.action.change.RepeatChangeAction",
"modes": "N" "modes": "N"
}, },
{
"keys": "<BS>",
"class": "com.maddyhome.idea.vim.action.editor.VimEditorBackSpace",
"modes": "I"
},
{
"keys": "<C-H>",
"class": "com.maddyhome.idea.vim.action.editor.VimEditorBackSpace",
"modes": "I"
},
{ {
"keys": "<C-I>", "keys": "<C-I>",
"class": "com.maddyhome.idea.vim.action.editor.VimEditorTab", "class": "com.maddyhome.idea.vim.action.editor.VimEditorTab",

View File

@ -109,7 +109,7 @@ class CopyActionTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
fun testYankRegisterUsesLastEnteredRegister() { fun testYankRegisterUsesLastEnteredRegister() {
typeTextInFile("\"a\"byl" + "\"ap", "hel<caret>lo world\n") typeTextInFile("\"a\"byl" + "\"bp", "hel<caret>lo world\n")
assertState("helllo world\n") assertState("helllo world\n")
} }

View File

@ -13,7 +13,6 @@ import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -2173,7 +2172,7 @@ rtyfg${c}hzxc"""
val editor = configureByText(before) val editor = configureByText(before)
injector.registerGroup.storeText('*', "fgh") injector.registerGroup.storeText('*', "fgh")
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(IjVimEditor(editor), editor.vim.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false) .storeText(IjVimEditor(editor), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("\"*P")) typeText(injector.parser.parseKeys("\"*P"))
val after = "fg${c}hqfg${c}hwe asd zxc rty fg${c}hfgh vbn" val after = "fg${c}hqfg${c}hwe asd zxc rty fg${c}hfgh vbn"
assertState(after) assertState(after)

View File

@ -33,7 +33,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
appReadySetup(false) appReadySetup(false)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "legendary", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, before rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("p")) typeText(injector.parser.parseKeys("p"))
val notification = notifications().last() val notification = notifications().last()
@ -53,7 +53,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
appReadySetup(false) appReadySetup(false)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "legendary", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, before rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("p")) typeText(injector.parser.parseKeys("p"))
val notifications = notifications() val notifications = notifications()
@ -71,7 +71,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
appReadySetup(true) appReadySetup(true)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "legendary", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, before rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("p")) typeText(injector.parser.parseKeys("p"))
val notifications = EventLog.getLogModel(fixture.project).notifications val notifications = EventLog.getLogModel(fixture.project).notifications

View File

@ -88,7 +88,7 @@ class PutTestAfterCursorActionTest : VimTestCase() {
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) .storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("p")) typeText(injector.parser.parseKeys("p"))
val after = """ val after = """
A Discovery A Discovery
@ -127,7 +127,6 @@ class PutTestAfterCursorActionTest : VimTestCase() {
val vimEditor = editor.vim val vimEditor = editor.vim
injector.registerGroup.storeText( injector.registerGroup.storeText(
vimEditor, vimEditor,
vimEditor.primaryCaret(),
before rangeOf "I found it in a legendary land\n", before rangeOf "I found it in a legendary land\n",
SelectionType.LINE_WISE, SelectionType.LINE_WISE,
false, false,
@ -157,7 +156,7 @@ class PutTestAfterCursorActionTest : VimTestCase() {
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("vep")) typeText(injector.parser.parseKeys("vep"))
val after = """ val after = """
A Discovery A Discovery

View File

@ -31,7 +31,7 @@ class PutTextBeforeCursorActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
injector.registerGroup.storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) injector.registerGroup.storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("V" + "P")) typeText(injector.parser.parseKeys("V" + "P"))
typeText(injector.parser.parseKeys("V" + "P")) typeText(injector.parser.parseKeys("V" + "P"))
val after = """ val after = """

View File

@ -54,7 +54,7 @@ class PutViaIdeaTest : VimTestCase() {
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "legendary", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, before rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText("ppp") typeText("ppp")
val after = "Ilegendarylegendarylegendar${c}y found it in a legendary land" val after = "Ilegendarylegendarylegendar${c}y found it in a legendary land"
@ -74,7 +74,6 @@ class PutViaIdeaTest : VimTestCase() {
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText( .storeText(
vimEditor, vimEditor,
vimEditor.primaryCaret(),
before rangeOf "legendary$randomUUID", before rangeOf "legendary$randomUUID",
SelectionType.CHARACTER_WISE, SelectionType.CHARACTER_WISE,
false, false,
@ -100,7 +99,6 @@ class PutViaIdeaTest : VimTestCase() {
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister().storeText( VimPlugin.getRegister().storeText(
vimEditor, vimEditor,
vimEditor.primaryCaret(),
before rangeOf "\nLorem ipsum dolor sit amet,\n", before rangeOf "\nLorem ipsum dolor sit amet,\n",
SelectionType.CHARACTER_WISE, SelectionType.CHARACTER_WISE,
false, false,

View File

@ -75,7 +75,7 @@ class PutVisualTextActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "legendary", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("ve" + "p")) typeText(injector.parser.parseKeys("ve" + "p"))
val after = "legendar${c}y it in a legendary land" val after = "legendar${c}y it in a legendary land"
assertState(after) assertState(after)
@ -87,7 +87,7 @@ class PutVisualTextActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "legendary", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("v2e" + "2p")) typeText(injector.parser.parseKeys("v2e" + "2p"))
val after = "legendarylegendar${c}y in a legendary land" val after = "legendarylegendar${c}y in a legendary land"
assertState(after) assertState(after)
@ -99,7 +99,7 @@ class PutVisualTextActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "legendary", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("v$" + "2p")) typeText(injector.parser.parseKeys("v$" + "2p"))
val after = "legendarylegendar${c}y" val after = "legendarylegendar${c}y"
assertState(after) assertState(after)
@ -133,7 +133,7 @@ class PutVisualTextActionTest : VimTestCase() {
val before = "I foun${c}d it in a legendary land" val before = "I foun${c}d it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "legendary", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("vb" + "p")) typeText(injector.parser.parseKeys("vb" + "p"))
val after = "I legendar${c}y it in a legendary land" val after = "I legendar${c}y it in a legendary land"
assertState(after) assertState(after)
@ -154,7 +154,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("ve" + "p")) typeText(injector.parser.parseKeys("ve" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -182,7 +182,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("ve" + "p")) typeText(injector.parser.parseKeys("ve" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -210,7 +210,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("ve" + "p")) typeText(injector.parser.parseKeys("ve" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -238,7 +238,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("v$" + "p")) typeText(injector.parser.parseKeys("v$" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -455,7 +455,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("ve" + "p")) typeText(injector.parser.parseKeys("ve" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -491,7 +491,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("ve" + "p")) typeText(injector.parser.parseKeys("ve" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -529,7 +529,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("ve" + "2p")) typeText(injector.parser.parseKeys("ve" + "2p"))
val after = """ val after = """
A Discovery A Discovery
@ -640,7 +640,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("V" + "p")) typeText(injector.parser.parseKeys("V" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -666,7 +666,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("V" + "2p")) typeText(injector.parser.parseKeys("V" + "2p"))
val after = """ val after = """
A Discovery A Discovery
@ -703,7 +703,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("V" + "p")) typeText(injector.parser.parseKeys("V" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -876,7 +876,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("V" + "p")) typeText(injector.parser.parseKeys("V" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -902,7 +902,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("V" + "2p")) typeText(injector.parser.parseKeys("V" + "2p"))
val after = """ val after = """
A Discovery A Discovery
@ -939,7 +939,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("V" + "p")) typeText(injector.parser.parseKeys("V" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1117,7 +1117,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("V" + "p")) typeText(injector.parser.parseKeys("V" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1172,7 +1172,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("V" + "p")) typeText(injector.parser.parseKeys("V" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1233,7 +1233,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("V" + "2p")) typeText(injector.parser.parseKeys("V" + "2p"))
val after = """ val after = """
A Discovery A Discovery
@ -1409,7 +1409,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2e2j" + "p")) typeText(injector.parser.parseKeys("<C-V>2e2j" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1435,7 +1435,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("<C-V>3e2k" + "p")) typeText(injector.parser.parseKeys("<C-V>3e2k" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1461,7 +1461,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2e2j" + "2p")) typeText(injector.parser.parseKeys("<C-V>2e2j" + "2p"))
val after = """ val after = """
A Discovery A Discovery
@ -1487,7 +1487,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "Discovery", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("<C-V>3j$" + "p")) typeText(injector.parser.parseKeys("<C-V>3j$" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1526,7 +1526,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2e2j" + "p")) typeText(injector.parser.parseKeys("<C-V>2e2j" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1554,7 +1554,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2e2j" + "P")) typeText(injector.parser.parseKeys("<C-V>2e2j" + "P"))
val after = """ val after = """
A Discovery A Discovery
@ -1593,7 +1593,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2e2j" + "2p")) typeText(injector.parser.parseKeys("<C-V>2e2j" + "2p"))
val after = """ val after = """
A Discovery A Discovery
@ -1633,7 +1633,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2e3j" + "p")) typeText(injector.parser.parseKeys("<C-V>2e3j" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1672,7 +1672,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, before rangeOf "A Discovery\n", SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2j$" + "p")) typeText(injector.parser.parseKeys("<C-V>2j$" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1707,7 +1707,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2e2j" + "p")) typeText(injector.parser.parseKeys("<C-V>2e2j" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1743,7 +1743,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2e3j" + "p")) typeText(injector.parser.parseKeys("<C-V>2e3j" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1779,7 +1779,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2ej" + "p")) typeText(injector.parser.parseKeys("<C-V>2ej" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1815,7 +1815,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("<C-V>elj" + "p")) typeText(injector.parser.parseKeys("<C-V>elj" + "p"))
val after = """ val after = """
A Discovery A Discovery
@ -1852,7 +1852,7 @@ class PutVisualTextActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, editor.rangeOf("|found|", 2), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("<C-V>2j$" + "p")) typeText(injector.parser.parseKeys("<C-V>2j$" + "p"))
val after = """ val after = """
A Discovery A Discovery

View File

@ -33,7 +33,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 25), SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 25), SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("v2e" + "2gp")) typeText(injector.parser.parseKeys("v2e" + "2gp"))
val after = "legendarylegendary$c in a legendary land" val after = "legendarylegendary$c in a legendary land"
assertState(after) assertState(after)
@ -45,7 +45,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 25), SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 25), SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("v2e" + "gp")) typeText(injector.parser.parseKeys("v2e" + "gp"))
val after = """ val after = """
@ -61,7 +61,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 25), SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 25), SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("V" + "gp")) typeText(injector.parser.parseKeys("V" + "gp"))
val after = "legendary\n$c" val after = "legendary\n$c"
assertState(after) assertState(after)
@ -88,7 +88,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(file) val editor = configureByText(file)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(2, 11), SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(2, 11), SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("V" + "gp")) typeText(injector.parser.parseKeys("V" + "gp"))
assertState(newFile) assertState(newFile)
} }
@ -134,7 +134,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 25), SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 25), SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("v2e" + "gP")) typeText(injector.parser.parseKeys("v2e" + "gP"))
val after = """ val after = """
@ -150,7 +150,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 25), SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 25), SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("v2e" + "2gP")) typeText(injector.parser.parseKeys("v2e" + "2gP"))
val after = "legendarylegendary$c in a legendary land" val after = "legendarylegendary$c in a legendary land"
assertState(after) assertState(after)
@ -162,7 +162,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 25), SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 25), SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("v$" + "2gP")) typeText(injector.parser.parseKeys("v$" + "2gP"))
val after = "legendarylegendar${c}y" val after = "legendarylegendar${c}y"
assertState(after) assertState(after)
@ -174,7 +174,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 25), SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 25), SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("V" + "gP")) typeText(injector.parser.parseKeys("V" + "gP"))
val after = "legendary\n$c" val after = "legendary\n$c"
assertState(after) assertState(after)
@ -273,7 +273,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.BLOCK_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 19), SelectionType.BLOCK_WISE, false)
typeText(injector.parser.parseKeys("<S-v>" + "gp")) typeText(injector.parser.parseKeys("<S-v>" + "gp"))
val after = """ val after = """
${c}fgh ${c}fgh
@ -299,7 +299,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.LINE_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 19), SelectionType.LINE_WISE, false)
typeText(injector.parser.parseKeys("<C-v>" + "h" + "gp")) typeText(injector.parser.parseKeys("<C-v>" + "h" + "gp"))
val after = """ val after = """
q q
@ -320,7 +320,7 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
val before = "${c}qwe asd ${c}zxc rty ${c}fgh vbn" val before = "${c}qwe asd ${c}zxc rty ${c}fgh vbn"
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister().storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false) VimPlugin.getRegister().storeText(vimEditor, TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("v2e" + "2gp")) typeText(injector.parser.parseKeys("v2e" + "2gp"))
val after = "fghfgh$c fghfgh$c fghfgh$c" val after = "fghfgh$c fghfgh$c fghfgh$c"
assertState(after) assertState(after)

View File

@ -91,7 +91,7 @@ class YankVisualActionTest : VimTestCase() {
typeText(injector.parser.parseKeys("viw" + "y")) typeText(injector.parser.parseKeys("viw" + "y"))
val editor = fixture.editor.vim val editor = fixture.editor.vim
val lastRegister = injector.registerGroup.lastRegisterChar val lastRegister = injector.registerGroup.lastRegisterChar
val registers = editor.carets().map { it.registerStorage.getRegister(lastRegister)?.rawText } val registers = editor.carets().map { injector.registerGroup.getRegister(lastRegister)?.rawText }
kotlin.test.assertEquals(listOf("found", "was"), registers) kotlin.test.assertEquals(listOf("found", "was"), registers)
} }
@ -172,7 +172,7 @@ class YankVisualActionTest : VimTestCase() {
typeText(injector.parser.parseKeys("V" + "y")) typeText(injector.parser.parseKeys("V" + "y"))
val editor = fixture.editor.vim val editor = fixture.editor.vim
val lastRegister = injector.registerGroup.lastRegisterChar val lastRegister = injector.registerGroup.lastRegisterChar
val registers = editor.carets().map { it.registerStorage.getRegister(lastRegister)?.rawText } val registers = editor.carets().map { injector.registerGroup.getRegister(lastRegister)?.rawText }
kotlin.test.assertEquals( kotlin.test.assertEquals(
listOf("all rocks and lavender and tufted grass,\n", "hard by the torrent of a mountain pass.\n"), listOf("all rocks and lavender and tufted grass,\n", "hard by the torrent of a mountain pass.\n"),
registers, registers,

View File

@ -85,4 +85,33 @@ class MotionBackspaceActionTest : VimTestCase() {
enterCommand("set whichwrap=b") enterCommand("set whichwrap=b")
} }
} }
@TestWithoutNeovim(SkipNeovimReason.OPTION)
@Test
fun `test backspace motion with operator`() {
doTest(
"d<BS>",
"""
lorem ${c}ipsum dolor sit amet
""".trimIndent(),
"""
lorem${c}ipsum dolor sit amet
""".trimIndent(),
)
}
@TestWithoutNeovim(SkipNeovimReason.OPTION)
@Test
fun `test backspace motion with operator at start of line`() {
doTest(
"d<BS>",
"""
lorem ipsum dolor sit amet
${c}lorem ipsum dolor sit amet
""".trimIndent(),
"""
lorem ipsum dolor sit amet${c}lorem ipsum dolor sit amet
""".trimIndent(),
)
}
} }

View File

@ -85,4 +85,35 @@ class MotionSpaceActionTest : VimTestCase() {
enterCommand("set whichwrap=s") enterCommand("set whichwrap=s")
} }
} }
@Suppress("SpellCheckingInspection")
@TestWithoutNeovim(SkipNeovimReason.OPTION)
@Test
fun `test space motion with operator`() {
doTest(
"d<Space>",
"""
lorem ${c}ipsum dolor sit amet
""".trimIndent(),
"""
lorem ${c}psum dolor sit amet
""".trimIndent(),
)
}
@TestWithoutNeovim(SkipNeovimReason.OPTION)
@Test
fun `test space motion with operator at end of line`() {
doTest(
"d<Space>",
"""
lorem ipsum dolor sit ame${c}t
lorem ipsum dolor sit amet
""".trimIndent(),
"""
lorem ipsum dolor sit am${c}e
lorem ipsum dolor sit amet
""".trimIndent(),
)
}
} }

View File

@ -130,7 +130,7 @@ class MultipleCaretsTest : VimTestCase() {
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText(commandToKeys("pu")) typeText(commandToKeys("pu"))
val after = """ val after = """
qwe qwe
@ -165,7 +165,7 @@ class MultipleCaretsTest : VimTestCase() {
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText(commandToKeys("pu")) typeText(commandToKeys("pu"))
val after = """ val after = """
qwe qwe
@ -201,7 +201,7 @@ class MultipleCaretsTest : VimTestCase() {
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText(commandToKeys("4pu")) typeText(commandToKeys("4pu"))
val after = """ val after = """
qwe qwe
@ -237,7 +237,7 @@ class MultipleCaretsTest : VimTestCase() {
val editor = configureByText(before) val editor = configureByText(before)
val vimEditor = editor.vim val vimEditor = editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText(commandToKeys("4pu")) typeText(commandToKeys("4pu"))
val after = """ val after = """
qwe qwe
@ -258,7 +258,7 @@ class MultipleCaretsTest : VimTestCase() {
val before = "${c}qwe\n" + "rty\n" + "as${c}d\n" + "fgh\n" + "zxc\n" + "vbn\n" val before = "${c}qwe\n" + "rty\n" + "as${c}d\n" + "fgh\n" + "zxc\n" + "vbn\n"
val editor = configureByText(before) val editor = configureByText(before)
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(editor.vim, editor.vim.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false) .storeText(editor.vim, TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText("vj") typeText("vj")
typeText(commandToKeys("pu")) typeText(commandToKeys("pu"))

View File

@ -9,7 +9,6 @@
package org.jetbrains.plugins.ideavim.ex.implementation.commands package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.register.RegisterConstants import com.maddyhome.idea.vim.register.RegisterConstants
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -303,33 +302,4 @@ class YankLinesCommandTest : VimTestCase() {
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. |Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|""".trimMargin()) |""".trimMargin())
} }
@Test
fun `test multicaret yank`() {
configureByText(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|${c}Nunc sit amet tellus vel purus cursus posuere et at purus.
|${c}Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
enterCommand("y")
val carets = fixture.editor.vim.carets()
assertEquals(3, carets.size)
assertEquals(
"Morbi nec luctus tortor, id venenatis lacus.\n",
carets[0].registerStorage.getRegister(RegisterConstants.UNNAMED_REGISTER)?.text
)
assertEquals(
"Nunc sit amet tellus vel purus cursus posuere et at purus.\n",
carets[1].registerStorage.getRegister(RegisterConstants.UNNAMED_REGISTER)?.text
)
assertEquals(
"Ut id dapibus augue.\n",
carets[2].registerStorage.getRegister(RegisterConstants.UNNAMED_REGISTER)?.text
)
}
} }

View File

@ -15,7 +15,7 @@ class VimVariableServiceTest : VimTestCase() {
@Test @Test
fun `test v count variable without count specified`() { fun `test v count variable without count specified`() {
configureByText("\n") configureByText("\n")
enterCommand("nnoremap <expr> n ':echo ' .. v:count .. \"\\<CR>\"") enterCommand("""nnoremap <expr> n ':echo ' .. v:count .. "\<CR>"""")
typeText("n") typeText("n")
assertExOutput("0") assertExOutput("0")
} }
@ -23,15 +23,31 @@ class VimVariableServiceTest : VimTestCase() {
@Test @Test
fun `test v count variable`() { fun `test v count variable`() {
configureByText("\n") configureByText("\n")
enterCommand("nnoremap <expr> n ':' .. \"\\<C-u>\" .. 'echo ' .. v:count .. \"\\<CR>\"") enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count .. "\<CR>"""")
typeText("5n") typeText("5n")
assertExOutput("5") assertExOutput("5")
} }
@Test
fun `test v count variable with additional count during select register`() {
configureByText("\n")
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count .. "\<CR>"""")
typeText("2\"a5n")
assertExOutput("10")
}
@Test
fun `test v count variable with additional pathological count during select register`() {
configureByText("\n")
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count .. "\<CR>"""")
typeText("2\"a3\"b4\"c5n")
assertExOutput("120")
}
@Test @Test
fun `test v count1 variable without count specified`() { fun `test v count1 variable without count specified`() {
configureByText("\n") configureByText("\n")
enterCommand("nnoremap <expr> n ':echo ' .. v:count1 .. \"\\<CR>\"") enterCommand("""nnoremap <expr> n ':echo ' .. v:count1 .. "\<CR>"""")
typeText("n") typeText("n")
assertExOutput("1") assertExOutput("1")
} }
@ -39,11 +55,27 @@ class VimVariableServiceTest : VimTestCase() {
@Test @Test
fun `test v count1 variable`() { fun `test v count1 variable`() {
configureByText("\n") configureByText("\n")
enterCommand("nnoremap <expr> n ':' .. \"\\<C-u>\" .. 'echo ' .. v:count1 .. \"\\<CR>\"") enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count1 .. "\<CR>"""")
typeText("5n") typeText("5n")
assertExOutput("5") assertExOutput("5")
} }
@Test
fun `test v count1 variable with additional count during select register`() {
configureByText("\n")
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count1 .. "\<CR>"""")
typeText("2\"a5n")
assertExOutput("10")
}
@Test
fun `test v count1 variable with additional pathological count during select register`() {
configureByText("\n")
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count1 .. "\<CR>"""")
typeText("2\"a3\"b4\"c5n")
assertExOutput("120")
}
@Test @Test
fun `test mapping with updating jumplist`() { fun `test mapping with updating jumplist`() {
configureByText("${c}1\n2\n3\n4\n5\n6\n7\n8\n9\n") configureByText("${c}1\n2\n3\n4\n5\n6\n7\n8\n9\n")

View File

@ -141,6 +141,16 @@ Mode.INSERT,
) )
} }
@Test
fun testDeleteWithMultipleCounts() {
doTest(
"2d2aa",
"function(int <caret>arg1, char* arg<caret>2=\"a,b,c(d,e)\", bool arg3, string arg4, int arg5)",
"function(<caret>)",
Mode.NORMAL(),
)
}
@Test @Test
fun testSelectTwoArguments() { fun testSelectTwoArguments() {
doTest( doTest(

View File

@ -50,7 +50,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text) configureByText(text)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), text rangeOf "one", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, text rangeOf "one", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("griw")) typeText(injector.parser.parseKeys("griw"))
assertState("one on${c}e three") assertState("one on${c}e three")
assertEquals("one", VimPlugin.getRegister().lastRegister?.text) assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
@ -170,7 +170,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text) configureByText(text)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), text rangeOf "one", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, text rangeOf "one", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("3griw")) typeText(injector.parser.parseKeys("3griw"))
assertState("one on${c}e four") assertState("one on${c}e four")
assertEquals("one", VimPlugin.getRegister().lastRegister?.text) assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
@ -184,7 +184,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text) configureByText(text)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), text rangeOf "one", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, text rangeOf "one", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("griw")) typeText(injector.parser.parseKeys("griw"))
assertState("one two one four") assertState("one two one four")
assertEquals("one", VimPlugin.getRegister().lastRegister?.text) assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
@ -197,7 +197,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text) configureByText(text)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), text rangeOf "one", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, text rangeOf "one", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("griw" + "w" + ".")) typeText(injector.parser.parseKeys("griw" + "w" + "."))
assertState("one one on${c}e four") assertState("one one on${c}e four")
assertEquals("one", VimPlugin.getRegister().lastRegister?.text) assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
@ -247,7 +247,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text) configureByText(text)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), text rangeOf "legendary", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, text rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("grr")) typeText(injector.parser.parseKeys("grr"))
assertState( assertState(
""" """
@ -414,7 +414,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text) configureByText(text)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), text rangeOf "legendary", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, text rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("viw" + "gr")) typeText(injector.parser.parseKeys("viw" + "gr"))
assertState( assertState(
""" """
@ -485,7 +485,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text) configureByText(text)
val vimEditor = fixture.editor.vim val vimEditor = fixture.editor.vim
VimPlugin.getRegister() VimPlugin.getRegister()
.storeText(vimEditor, vimEditor.primaryCaret(), text rangeOf "legendary", SelectionType.CHARACTER_WISE, false) .storeText(vimEditor, text rangeOf "legendary", SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("V" + "gr")) typeText(injector.parser.parseKeys("V" + "gr"))
assertState( assertState(
""" """

View File

@ -95,7 +95,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie() val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie()
// Note: esc is always an option // Note: esc is always an option
val possibleKeys = (currentNode.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) } val possibleKeys = (currentNode.children.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) }
println("Keys: ${possibleKeys.joinToString(", ")}") println("Keys: ${possibleKeys.joinToString(", ")}")
val keyGenerator = Generator.integers(0, possibleKeys.lastIndex) val keyGenerator = Generator.integers(0, possibleKeys.lastIndex)
.suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList } .suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList }

View File

@ -17,14 +17,13 @@ import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode 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.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.impl.state.toMappingMode import com.maddyhome.idea.vim.impl.state.toMappingMode
import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyConsumer
import com.maddyhome.idea.vim.key.KeyStack import com.maddyhome.idea.vim.key.KeyStack
import com.maddyhome.idea.vim.key.RootNode
import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer
import com.maddyhome.idea.vim.key.consumers.CommandConsumer import com.maddyhome.idea.vim.key.consumers.CommandConsumer
import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer
@ -197,11 +196,9 @@ class KeyHandler {
} }
private fun onUnknownKey(editor: VimEditor, keyState: KeyHandlerState) { private fun onUnknownKey(editor: VimEditor, keyState: KeyHandlerState) {
logger.trace("Command builder is set to BAD")
keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
editor.resetOpPending() editor.resetOpPending()
injector.vimState.resetRegisterPending()
editor.isReplaceCharacter = false editor.isReplaceCharacter = false
// Note that this will also reset the CommandBuilder to NEW_COMMAND
reset(keyState, editor.mode) reset(keyState, editor.mode)
} }
@ -210,14 +207,6 @@ class KeyHandler {
injector.messages.indicateError() injector.messages.indicateError()
} }
fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode, keyState: KeyHandlerState): Boolean {
return isOperatorPending(mode, keyState) && keyState.commandBuilder.isDuplicateOperatorKeyStroke(key)
}
fun isOperatorPending(mode: Mode, keyState: KeyHandlerState): Boolean {
return mode is Mode.OP_PENDING && !keyState.commandBuilder.isEmpty
}
private fun executeCommand( private fun executeCommand(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
@ -226,11 +215,7 @@ class KeyHandler {
) { ) {
logger.trace("Command execution") logger.trace("Command execution")
val command = keyState.commandBuilder.buildCommand() val command = keyState.commandBuilder.buildCommand()
val operatorArguments = OperatorArguments( val operatorArguments = OperatorArguments(command.rawCount, editorState.mode)
editor.mode is Mode.OP_PENDING,
command.rawCount,
editorState.mode,
)
// If we were in "operator pending" mode, reset back to normal mode. // If we were in "operator pending" mode, reset back to normal mode.
// But opening command line should not reset operator pending mode (e.g. `d/foo` // But opening command line should not reset operator pending mode (e.g. `d/foo`
@ -295,7 +280,7 @@ class KeyHandler {
keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode())) keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode()))
} }
private fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand> { private fun getKeyRoot(mappingMode: MappingMode): RootNode<LazyVimCommand> {
return injector.keyGroup.getKeyRoot(mappingMode) return injector.keyGroup.getKeyRoot(mappingMode)
} }
@ -341,7 +326,7 @@ class KeyHandler {
) : Runnable { ) : Runnable {
override fun run() { override fun run() {
val editorState = injector.vimState val editorState = injector.vimState
keyState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
val register = cmd.register val register = cmd.register
if (register != null) { if (register != null) {
injector.registerGroup.selectRegister(register) injector.registerGroup.selectRegister(register)
@ -361,22 +346,15 @@ class KeyHandler {
// mode we were in. This handles commands in those modes that temporarily allow us to execute normal // mode we were in. This handles commands in those modes that temporarily allow us to execute normal
// mode commands. An exception is if this command should leave us in the temporary mode such as // mode commands. An exception is if this command should leave us in the temporary mode such as
// "select register" // "select register"
val myMode = editorState.mode if (editorState.mode is Mode.NORMAL && !cmd.flags.contains(CommandFlags.FLAG_EXPECT_MORE)) {
val returnTo = myMode.returnTo when (editorState.mode.returnTo) {
if (myMode is Mode.NORMAL && returnTo != null && !cmd.flags.contains(CommandFlags.FLAG_EXPECT_MORE)) { ReturnTo.INSERT -> editor.mode = Mode.INSERT
when (returnTo) { ReturnTo.REPLACE -> editor.mode = Mode.REPLACE
ReturnTo.INSERT -> { null -> {}
editor.mode = Mode.INSERT
}
ReturnTo.REPLACE -> {
editor.mode = Mode.REPLACE
}
} }
} }
if (keyState.commandBuilder.isDone()) {
getInstance().reset(keyState, editorState.mode) instance.reset(keyState, editorState.mode)
}
} }
} }

View File

@ -16,7 +16,7 @@ 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
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
class RedoAction : VimActionHandler.SingleExecution() { class RedoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@ -16,7 +16,7 @@ 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
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
class UndoAction : VimActionHandler.SingleExecution() { class UndoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup import com.maddyhome.idea.vim.api.VimChangeGroup
@ -23,7 +22,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
/** /**
* @author vlan * @author vlan
*/ */
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL]) @CommandOrMotion(keys = [], modes = [])
class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() { class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE override val type: Command.Type = Command.Type.CHANGE

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup import com.maddyhome.idea.vim.api.VimChangeGroup
@ -23,7 +22,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
/** /**
* @author vlan * @author vlan
*/ */
@CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL]) @CommandOrMotion(keys = [], modes = [])
class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() { class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE override val type: Command.Type = Command.Type.CHANGE

View File

@ -17,23 +17,17 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.lineLength import com.maddyhome.idea.vim.api.lineLength
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
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.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
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.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.KeyHandlerState
import java.util.*
@CommandOrMotion(keys = ["r"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["r"], modes = [Mode.NORMAL])
class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() { class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.DIGRAPH override val argumentType: Argument.Type = Argument.Type.DIGRAPH
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
editor.isReplaceCharacter = true editor.isReplaceCharacter = true
} }
@ -45,7 +39,7 @@ class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
return argument != null && changeCharacter(editor, caret, operatorArguments.count1, argument.character) return argument is Argument.Character && changeCharacter(editor, caret, operatorArguments.count1, argument.character)
} }
} }

View File

@ -37,12 +37,11 @@ class ChangeLineAction : ChangeInInsertSequenceAction() {
): Boolean { ): Boolean {
// `S` command is a synonym of `cc` // `S` command is a synonym of `cc`
val motion = MotionDownLess1FirstNonSpaceAction() val motion = MotionDownLess1FirstNonSpaceAction()
val command = Command(1, motion, motion.type, motion.flags)
return injector.changeGroup.changeMotion( return injector.changeGroup.changeMotion(
editor, editor,
caret, caret,
context, context,
Argument(command), Argument.Motion(motion, null),
operatorArguments, operatorArguments,
) )
} }

View File

@ -39,7 +39,6 @@ class ChangeVisualAction : VisualOperatorActionHandler.ForEachCaret() {
range.toVimTextRange(false), range.toVimTextRange(false),
range.type, range.type,
context, context,
operatorArguments,
) )
} }
} }

View File

@ -15,16 +15,13 @@ 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.command.Argument import com.maddyhome.idea.vim.command.Argument
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.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
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.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.KeyHandlerState
import java.util.*
/** /**
* @author vlan * @author vlan
@ -32,11 +29,8 @@ import java.util.*
@CommandOrMotion(keys = ["r"], modes = [Mode.VISUAL]) @CommandOrMotion(keys = ["r"], modes = [Mode.VISUAL])
class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() { class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.DIGRAPH override val argumentType: Argument.Type = Argument.Type.DIGRAPH
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
editor.isReplaceCharacter = true editor.isReplaceCharacter = true
} }
@ -50,7 +44,7 @@ class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
val argument = cmd.argument val argument = cmd.argument
return argument != null && return argument is Argument.Character &&
changeCharacterRange(editor, caret, range.toVimTextRange(false), argument.character) changeCharacterRange(editor, caret, range.toVimTextRange(false), argument.character)
} }
} }

View File

@ -56,7 +56,6 @@ class ChangeVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
lineRange, lineRange,
SelectionType.LINE_WISE, SelectionType.LINE_WISE,
context, context,
operatorArguments,
) )
} }
} }

View File

@ -53,7 +53,7 @@ class ChangeVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
} }
} }
val blockRange = TextRange(starts, ends) val blockRange = TextRange(starts, ends)
injector.changeGroup.changeRange(editor, caret, blockRange, SelectionType.BLOCK_WISE, context, operatorArguments) injector.changeGroup.changeRange(editor, caret, blockRange, SelectionType.BLOCK_WISE, context)
} else { } else {
val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset) val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset)
val endsWithNewLine = if (lineEndForOffset.toLong() == editor.fileSize()) 0 else 1 val endsWithNewLine = if (lineEndForOffset.toLong() == editor.fileSize()) 0 else 1
@ -61,7 +61,7 @@ class ChangeVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
editor.getLineStartForOffset(vimTextRange.startOffset), editor.getLineStartForOffset(vimTextRange.startOffset),
lineEndForOffset + endsWithNewLine, lineEndForOffset + endsWithNewLine,
) )
injector.changeGroup.changeRange(editor, caret, lineRange, SelectionType.LINE_WISE, context, operatorArguments) injector.changeGroup.changeRange(editor, caret, lineRange, SelectionType.LINE_WISE, context)
} }
} }
} }

View File

@ -31,7 +31,7 @@ class FilterVisualLinesAction : VimActionHandler.SingleExecution(), FilterComman
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
// Start ex entry with the initial text set to the calculated range and `!` // Start ex entry with the initial text set to the calculated range and `!`
startFilterCommand(editor, context, cmd) startFilterCommand(editor, context, cmd.rawCount)
return true return true
} }
} }
@ -63,13 +63,13 @@ class FilterMotionAction : VimActionHandler.SingleExecution(), FilterCommand, Du
// Start ex entry with the initial text set to the calculated range and `!` // Start ex entry with the initial text set to the calculated range and `!`
val count = if (start.line < end.line) end.line - start.line + 1 else 1 val count = if (start.line < end.line) end.line - start.line + 1 else 1
startFilterCommand(editor, context, Argument.EMPTY_COMMAND.copy(rawCount = count)) startFilterCommand(editor, context, count)
return true return true
} }
} }
interface FilterCommand { interface FilterCommand {
fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) { fun startFilterCommand(editor: VimEditor, context: ExecutionContext, count0: Int) {
injector.commandLine.createCommandPrompt(editor, context, cmd, initialText = "!") injector.commandLine.createCommandPrompt(editor, context, count0, initialText = "!")
} }
} }

View File

@ -38,6 +38,6 @@ class DeleteMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO
val (range, selectionType) = injector.changeGroup val (range, selectionType) = injector.changeGroup
.getDeleteRangeAndType(editor, caret, context, argument, false, operatorArguments) .getDeleteRangeAndType(editor, caret, context, argument, false, operatorArguments)
?: return false ?: return false
return injector.changeGroup.deleteRange(editor, caret, range, selectionType, false, operatorArguments) return injector.changeGroup.deleteRange(editor, caret, range, selectionType, false)
} }
} }

View File

@ -40,7 +40,6 @@ class DeleteVisualAction : VisualOperatorActionHandler.ForEachCaret() {
range.toVimTextRange(false), range.toVimTextRange(false),
selectionType, selectionType,
false, false,
operatorArguments,
) )
} }
} }

View File

@ -56,6 +56,6 @@ class DeleteVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
Triple(caret, lineRange, SelectionType.LINE_WISE) Triple(caret, lineRange, SelectionType.LINE_WISE)
} }
} }
return injector.changeGroup.deleteRange(editor, usedCaret, usedRange, usedType, false, operatorArguments) return injector.changeGroup.deleteRange(editor, usedCaret, usedRange, usedType, false)
} }
} }

View File

@ -58,7 +58,6 @@ class DeleteVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
blockRange, blockRange,
SelectionType.BLOCK_WISE, SelectionType.BLOCK_WISE,
false, false,
operatorArguments,
) )
} else { } else {
val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset) val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset)
@ -67,7 +66,7 @@ class DeleteVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
editor.getLineStartForOffset(vimTextRange.startOffset), editor.getLineStartForOffset(vimTextRange.startOffset),
lineEndForOffset + endsWithNewLine, lineEndForOffset + endsWithNewLine,
) )
injector.changeGroup.deleteRange(editor, caret, lineRange, SelectionType.LINE_WISE, false, operatorArguments) injector.changeGroup.deleteRange(editor, caret, lineRange, SelectionType.LINE_WISE, false)
} }
} }
} }

View File

@ -52,7 +52,8 @@ class InsertCompletedDigraphAction : 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 {
// The converted digraph character has been captured as an argument, push it back through key handler // The converted digraph character has been captured as an argument, push it back through key handler
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character) val argument = cmd.argument as? Argument.Character ?: return false
val keyStroke = KeyStroke.getKeyStroke(argument.character)
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState) keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
return true return true

View File

@ -52,7 +52,8 @@ class InsertCompletedLiteralAction : 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 {
// The converted literal character has been captured as an argument, push it back through key handler // The converted literal character has been captured as an argument, push it back through key handler
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character) val argument = cmd.argument as? Argument.Character ?: return false
val keyStroke = KeyStroke.getKeyStroke(argument.character)
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState) keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
return true return true

View File

@ -36,7 +36,7 @@ class InsertDeleteInsertedTextAction : ChangeEditorActionHandler.ForEachCaret()
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
return insertDeleteInsertedText(editor, caret, operatorArguments) return insertDeleteInsertedText(editor, caret)
} }
} }
@ -48,11 +48,7 @@ class InsertDeleteInsertedTextAction : ChangeEditorActionHandler.ForEachCaret()
* @param caret The caret on which the action is performed * @param caret The caret on which the action is performed
* @return true if able to delete the text, false if not * @return true if able to delete the text, false if not
*/ */
private fun insertDeleteInsertedText( private fun insertDeleteInsertedText(editor: VimEditor, caret: VimCaret): Boolean {
editor: VimEditor,
caret: VimCaret,
operatorArguments: OperatorArguments,
): Boolean {
var deleteTo = caret.vimInsertStart.startOffset var deleteTo = caret.vimInsertStart.startOffset
val offset = caret.offset val offset = caret.offset
if (offset == deleteTo) { if (offset == deleteTo) {
@ -65,7 +61,6 @@ private fun insertDeleteInsertedText(
TextRange(deleteTo, offset), TextRange(deleteTo, offset),
SelectionType.CHARACTER_WISE, SelectionType.CHARACTER_WISE,
false, false,
operatorArguments,
) )
return true return true
} }

View File

@ -37,7 +37,7 @@ class InsertDeletePreviousWordAction : ChangeEditorActionHandler.ForEachCaret()
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
return insertDeletePreviousWord(editor, caret, operatorArguments) return insertDeletePreviousWord(editor, caret)
} }
} }
@ -50,7 +50,7 @@ class InsertDeletePreviousWordAction : ChangeEditorActionHandler.ForEachCaret()
* @param editor The editor to delete the text from * @param editor The editor to delete the text from
* @return true if able to delete text, false if not * @return true if able to delete text, false if not
*/ */
private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret, operatorArguments: OperatorArguments): Boolean { private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret): Boolean {
val deleteTo: Int = if (caret.getBufferPosition().column == 0) { val deleteTo: Int = if (caret.getBufferPosition().column == 0) {
caret.offset - 1 caret.offset - 1
} else { } else {
@ -74,6 +74,6 @@ private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret, operato
return false return false
} }
val range = TextRange(deleteTo, caret.offset) val range = TextRange(deleteTo, caret.offset)
injector.changeGroup.deleteRange(editor, caret, range, SelectionType.CHARACTER_WISE, true, operatorArguments) injector.changeGroup.deleteRange(editor, caret, range, SelectionType.CHARACTER_WISE, true)
return true return true
} }

View File

@ -18,14 +18,10 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ExException import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.RWLockLabel import com.maddyhome.idea.vim.helper.RWLockLabel
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
import com.maddyhome.idea.vim.key.interceptors.VimInputInterceptorBase
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.register.Register import com.maddyhome.idea.vim.register.Register
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.Script import com.maddyhome.idea.vim.vimscript.model.Script
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["<C-R>"], modes = [Mode.INSERT])
class InsertRegisterAction : VimActionHandler.SingleExecution() { class InsertRegisterAction : VimActionHandler.SingleExecution() {
@ -39,9 +35,8 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
cmd: Command, cmd: Command,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
val argument = cmd.argument val argument = cmd.argument as? Argument.Character ?: return false
if (argument.character == '=') {
if (argument?.character == '=') {
injector.commandLine.readInputAndProcess(editor, context, "=", finishOn = null) { input -> injector.commandLine.readInputAndProcess(editor, context, "=", finishOn = null) { input ->
try { try {
if (input.isNotEmpty()) { if (input.isNotEmpty()) {
@ -50,7 +45,7 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
val textToStore = expression.toInsertableString() val textToStore = expression.toInsertableString()
injector.registerGroup.storeTextSpecial('=', textToStore) injector.registerGroup.storeTextSpecial('=', textToStore)
} }
insertRegister(editor, context, '=', operatorArguments) insertRegister(editor, context, '=')
} catch (e: ExException) { } catch (e: ExException) {
injector.messages.indicateError() injector.messages.indicateError()
injector.messages.showStatusBarMessage(editor, e.message) injector.messages.showStatusBarMessage(editor, e.message)
@ -58,7 +53,7 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
} }
return true return true
} else { } else {
return argument != null && insertRegister(editor, context, argument.character, operatorArguments) return insertRegister(editor, context, argument.character)
} }
} }
} }
@ -72,18 +67,13 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
* @return true if able to insert the register contents, false if not * @return true if able to insert the register contents, false if not
*/ */
@RWLockLabel.SelfSynchronized @RWLockLabel.SelfSynchronized
private fun insertRegister( private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Char): Boolean {
editor: VimEditor,
context: ExecutionContext,
key: Char,
operatorArguments: OperatorArguments,
): Boolean {
val register: Register? = injector.registerGroup.getRegister(key) val register: Register? = injector.registerGroup.getRegister(key)
if (register != null) { if (register != null) {
val text = register.rawText ?: injector.parser.toPrintableString(register.keys) val text = register.rawText ?: injector.parser.toPrintableString(register.keys)
val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), register.name) val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), register.name)
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true) val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
injector.put.putText(editor, context, putData, operatorArguments = operatorArguments) injector.put.putText(editor, context, putData)
return true return true
} }
return false return false

View File

@ -36,7 +36,7 @@ class VisualBlockAppendAction : VisualOperatorActionHandler.SingleExecution() {
if (editor.isOneLineMode()) return false if (editor.isOneLineMode()) return false
val range = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false val range = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false
return if (range.type == SelectionType.BLOCK_WISE) { return if (range.type == SelectionType.BLOCK_WISE) {
injector.changeGroup.blockInsert(editor, context, range.toVimTextRange(false), true, operatorArguments) injector.changeGroup.initBlockInsert(editor, context, range.toVimTextRange(false), true)
} else { } else {
injector.changeGroup.insertAfterLineEnd(editor, context) injector.changeGroup.insertAfterLineEnd(editor, context)
true true

View File

@ -36,7 +36,7 @@ class VisualBlockInsertAction : VisualOperatorActionHandler.SingleExecution() {
if (editor.isOneLineMode()) return false if (editor.isOneLineMode()) return false
val vimSelection = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false val vimSelection = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false
return if (vimSelection.type == SelectionType.BLOCK_WISE) { return if (vimSelection.type == SelectionType.BLOCK_WISE) {
injector.changeGroup.blockInsert(editor, context, vimSelection.toVimTextRange(false), false, operatorArguments) injector.changeGroup.initBlockInsert(editor, context, vimSelection.toVimTextRange(false), false)
} else { } else {
injector.changeGroup.insertBeforeFirstNonBlank(editor, context) injector.changeGroup.insertBeforeFirstNonBlank(editor, context)
true true

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.action.copy
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
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.command.Argument import com.maddyhome.idea.vim.command.Argument
@ -36,7 +35,15 @@ sealed class PutTextBaseAction(
val count = operatorArguments.count1 val count = operatorArguments.count1
val sortedCarets = editor.sortedCarets() val sortedCarets = editor.sortedCarets()
return if (sortedCarets.size > 1) { return if (sortedCarets.size > 1) {
val caretToPutData = sortedCarets.associateWith { getPutDataForCaret(it, count) } val putData = getPutData(count)
val splitText = putData.textData?.rawText?.split('\n')?.dropLastWhile(String::isEmpty)
val caretToPutData = if (splitText != null && splitText.size == sortedCarets.size) {
sortedCarets.mapIndexed { index, caret -> caret to putData.copy(textData = putData.textData.copy(rawText = splitText[splitText.lastIndex - index])) }.toMap()
} else {
sortedCarets.associateWith { putData }
}
var result = true var result = true
injector.application.runWriteAction { injector.application.runWriteAction {
caretToPutData.forEach { caretToPutData.forEach {
@ -45,28 +52,24 @@ sealed class PutTextBaseAction(
} }
result result
} else { } else {
val putData = getPutDataForCaret(sortedCarets.single(), count) injector.put.putText(editor, context, getPutData(count))
injector.put.putText(editor, context, putData, operatorArguments)
} }
} }
private fun getPutDataForCaret(caret: ImmutableVimCaret, count: Int): PutData { private fun getPutData(count: Int): PutData {
val registerService = injector.registerGroup return PutData(getRegisterTextData(), null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1)
val registerChar = if (caret.editor.carets().size == 1) { }
registerService.currentRegister }
} else {
registerService.getCurrentRegisterForMulticaret() fun getRegisterTextData(): TextData? {
} val register = injector.registerGroup.getRegister(injector.registerGroup.currentRegister)
val register = caret.registerStorage.getRegister(registerChar) return register?.let {
val textData = register?.let { TextData(
TextData( register.text ?: injector.parser.toPrintableString(register.keys),
register.text ?: injector.parser.toPrintableString(register.keys), register.type,
register.type, register.transferableData,
register.transferableData, register.name,
register.name, )
)
}
return PutData(textData, null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1)
} }
} }

View File

@ -41,7 +41,21 @@ sealed class PutVisualTextBaseAction(
): Boolean { ): Boolean {
if (caretsAndSelections.isEmpty()) return false if (caretsAndSelections.isEmpty()) return false
val count = cmd.count val count = cmd.count
val caretToPutData = editor.sortedCarets().associateWith { getPutDataForCaret(it, caretsAndSelections[it], count) } val sortedCarets = editor.sortedCarets()
val textData = getRegisterTextData()
val splitText = textData?.rawText?.split('\n')?.dropLastWhile(String::isEmpty)
val caretToTextData = if (splitText != null && splitText.size == sortedCarets.size) {
sortedCarets.mapIndexed { index, caret -> caret to textData.copy(rawText = splitText[splitText.lastIndex - index]) }.toMap()
} else {
sortedCarets.associateWith { textData }
}
val caretToPutData = caretToTextData.mapValues { (caret, textData) ->
getPutDataForCaret(textData, caret, caretsAndSelections[caret], count)
}
injector.registerGroup.resetRegister() injector.registerGroup.resetRegister()
var result = true var result = true
injector.application.runWriteAction { injector.application.runWriteAction {
@ -51,18 +65,8 @@ sealed class PutVisualTextBaseAction(
} }
return result return result
} }
private fun getPutDataForCaret(caret: VimCaret, selection: VimSelection?, count: Int): PutData { private fun getPutDataForCaret(textData: PutData.TextData?, caret: VimCaret, selection: VimSelection?, count: Int): PutData {
val lastRegisterChar = injector.registerGroup.lastRegisterChar
val register = caret.registerStorage.getRegister(lastRegisterChar)
val textData = register?.let {
PutData.TextData(
register.text ?: injector.parser.toPrintableString(register.keys),
register.type,
register.transferableData,
register.name,
)
}
val visualSelection = selection?.let { PutData.VisualSelection(mapOf(caret to it), it.type) } val visualSelection = selection?.let { PutData.VisualSelection(mapOf(caret to it), it.type) }
return PutData(textData, visualSelection, count, insertTextBeforeCaret, indent, caretAfterInsertedText) return PutData(textData, visualSelection, count, insertTextBeforeCaret, indent, caretAfterInsertedText)
} }

View File

@ -26,7 +26,7 @@ class ExEntryAction : 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 {
if (editor.isOneLineMode()) return false if (editor.isOneLineMode()) return false
injector.commandLine.createCommandPrompt(editor, context, cmd, initialText = "") injector.commandLine.createCommandPrompt(editor, context, cmd.rawCount, initialText = "")
return true return true
} }
} }

View File

@ -38,7 +38,8 @@ class InsertRegisterAction: VimActionHandler.SingleExecution() {
val caretOffset = cmdLine.caret.offset val caretOffset = cmdLine.caret.offset
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character) val argument = cmd.argument as? Argument.Character ?: return false
val keyStroke = KeyStroke.getKeyStroke(argument.character)
val pasteContent = if ((keyStroke.modifiers and KeyEvent.CTRL_DOWN_MASK) == 0) { val pasteContent = if ((keyStroke.modifiers and KeyEvent.CTRL_DOWN_MASK) == 0) {
injector.registerGroup.getRegister(keyStroke.keyChar)?.text injector.registerGroup.getRegister(keyStroke.keyChar)?.text
} else { } else {

View File

@ -13,6 +13,7 @@ import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
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.command.Argument
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.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
@ -27,9 +28,9 @@ class LeaveCommandLineAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY
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 true val argument = cmd.argument as? Argument.ExString ?: return true
val historyType = VimHistory.Type.getTypeByLabel(argument.character.toString()) val historyType = VimHistory.Type.getTypeByLabel(argument.label.toString())
injector.historyGroup.addEntry(historyType, argument.string) injector.historyGroup.addEntry(historyType, argument.string)
return true return true
} }
} }

View File

@ -34,8 +34,9 @@ class ProcessExEntryAction : MotionActionHandler.AmbiguousExecution() {
override var motionType: MotionType = MotionType.EXCLUSIVE override var motionType: MotionType = MotionType.EXCLUSIVE
override fun getMotionActionHandler(argument: Argument?): MotionActionHandler { override fun getMotionActionHandler(argument: Argument?): MotionActionHandler {
if (argument?.processing != null) return ExecuteDefinedInputProcessingAction() check(argument is Argument.ExString)
return if (argument?.character == ':') ProcessExCommandEntryAction() else ProcessSearchEntryAction(this) if (argument.processing != null) return ExecuteDefinedInputProcessingAction()
return if (argument.label == ':') ProcessExCommandEntryAction() else ProcessSearchEntryAction(this)
} }
} }
@ -48,7 +49,7 @@ class ExecuteDefinedInputProcessingAction : MotionActionHandler.SingleExecution(
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
if (argument == null) return Motion.Error if (argument !is Argument.ExString) return Motion.Error
val input = argument.string val input = argument.string
val processing = argument.processing!! val processing = argument.processing!!
@ -62,14 +63,13 @@ class ProcessSearchEntryAction(private val parentAction: ProcessExEntryAction) :
get() = throw RuntimeException("Parent motion type should be used, as only it is accessed by other code") get() = throw RuntimeException("Parent motion type should be used, as only it is accessed by other code")
override fun getOffset(editor: VimEditor, caret: ImmutableVimCaret, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion { override fun getOffset(editor: VimEditor, caret: ImmutableVimCaret, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion {
if (argument == null) return Motion.Error if (argument !is Argument.ExString) return Motion.Error
val offsetAndMotion = when (argument.character) { val offsetAndMotion = when (argument.label) {
'/' -> injector.searchGroup.processSearchCommand(editor, argument.string, caret.offset, operatorArguments.count1, Direction.FORWARDS) '/' -> injector.searchGroup.processSearchCommand(editor, argument.string, caret.offset, operatorArguments.count1, Direction.FORWARDS)
'?' -> injector.searchGroup.processSearchCommand(editor, argument.string, caret.offset, operatorArguments.count1, Direction.BACKWARDS) '?' -> injector.searchGroup.processSearchCommand(editor, argument.string, caret.offset, operatorArguments.count1, Direction.BACKWARDS)
else -> throw ExException("Unexpected search label ${argument.character}") else -> throw ExException("Unexpected search label ${argument.label}")
} }
// Vim doesn't treat not finding something as an error, although it might report either an error or warning message if (offsetAndMotion == null) return Motion.Error
if (offsetAndMotion == null) return Motion.NoMotion
parentAction.motionType = offsetAndMotion.second parentAction.motionType = offsetAndMotion.second
return offsetAndMotion.first.toMotionOrError() return offsetAndMotion.first.toMotionOrError()
} }
@ -79,7 +79,7 @@ class ProcessExCommandEntryAction : MotionActionHandler.SingleExecution() {
override val motionType: MotionType = MotionType.LINE_WISE override val motionType: MotionType = MotionType.LINE_WISE
override fun getOffset(editor: VimEditor, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion { override fun getOffset(editor: VimEditor, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion {
if (argument == null) return Motion.Error if (argument !is Argument.ExString) return Motion.Error
try { try {
// Exit Command-line mode and return to the previous mode before executing the command (this is set to Normal in // Exit Command-line mode and return to the previous mode before executing the command (this is set to Normal in

View File

@ -31,7 +31,7 @@ class PlaybackRegisterAction : VimActionHandler.SingleExecution() {
cmd: Command, cmd: Command,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
val argument = cmd.argument ?: return false val argument = cmd.argument as? Argument.Character ?: return false
val reg = argument.character val reg = argument.character
val application = injector.application val application = injector.application
val res = arrayOf(false) val res = arrayOf(false)
@ -49,7 +49,7 @@ class PlaybackRegisterAction : VimActionHandler.SingleExecution() {
if (reg != '@') { // @ is not a register itself, it just tells vim to use the last register if (reg != '@') { // @ is not a register itself, it just tells vim to use the last register
injector.macro.lastRegister = reg injector.macro.lastRegister = reg
} }
} catch (e: ExException) { } catch (_: ExException) {
res[0] = false res[0] = false
} }
} }

View File

@ -26,7 +26,7 @@ class ToggleRecordingAction : 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 {
return if (!injector.registerGroup.isRecording) { return if (!injector.registerGroup.isRecording) {
val argument = cmd.argument ?: return false val argument = cmd.argument as? Argument.Character ?: return false
val reg = argument.character val reg = argument.character
injector.registerGroup.startRecording(reg) injector.registerGroup.startRecording(reg)
} else { } else {

View File

@ -19,10 +19,21 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.command.MotionType
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.MotionActionHandler
import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING]) private fun doMotion(
class MotionArrowLeftAction : NonShiftedSpecialKeyHandler() { editor: VimEditor,
caret: ImmutableVimCaret,
count1: Int,
whichwrapKey: String,
allowPastEnd: Boolean,
): Motion {
val allowWrap = injector.options(editor).whichwrap.contains(whichwrapKey)
return injector.motion.getHorizontalMotion(editor, caret, count1, allowPastEnd, allowWrap)
}
abstract class MotionNonShiftedArrowLeftBaseAction() : NonShiftedSpecialKeyHandler() {
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE
override fun motion( override fun motion(
@ -32,8 +43,38 @@ class MotionArrowLeftAction : NonShiftedSpecialKeyHandler() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
val allowWrap = injector.options(editor).whichwrap.contains("<") return doMotion(editor, caret, -operatorArguments.count1, "<", allowPastEnd)
val allowEnd = operatorArguments.isOperatorPending // d<Left> deletes \n with wrap enabled }
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowEnd, allowWrap)
protected open val allowPastEnd: Boolean = false
}
// Note that Select mode is handled in [SelectMotionArrowLeftAction]
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.NORMAL, Mode.VISUAL])
class MotionArrowLeftAction : MotionNonShiftedArrowLeftBaseAction()
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.OP_PENDING])
class MotionArrowLeftOpPendingAction : MotionNonShiftedArrowLeftBaseAction() {
// When the motion is used with an operator, the EOL character is counted.
// This allows e.g., `d<Left>` to delete the end of line character on the previous line when wrap is active
// ('whichwrap' contains "<")
// See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
override val allowPastEnd = true
}
// Just needs to be a plain motion handler - it's not shifted, and the non-shifted actions don't apply in Insert mode
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.INSERT])
class MotionArrowLeftInsertModeAction : MotionActionHandler.ForEachCaret() {
override val motionType: MotionType = MotionType.EXCLUSIVE
override fun getOffset(
editor: VimEditor,
caret: ImmutableVimCaret,
context: ExecutionContext,
argument: Argument?,
operatorArguments: OperatorArguments,
): Motion {
// Insert mode is always allowed past the end of the line
return doMotion(editor, caret, -operatorArguments.count1, "[", allowPastEnd = true)
} }
} }

View File

@ -19,12 +19,23 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.command.MotionType
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.MotionActionHandler
import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.usesVirtualSpace import com.maddyhome.idea.vim.helper.usesVirtualSpace
@CommandOrMotion(keys = ["<Right>", "<kRight>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING]) private fun doMotion(
class MotionArrowRightAction : NonShiftedSpecialKeyHandler() { editor: VimEditor,
caret: ImmutableVimCaret,
count1: Int,
whichwrapKey: String,
allowPastEnd: Boolean,
): Motion {
val allowWrap = injector.options(editor).whichwrap.contains(whichwrapKey)
return injector.motion.getHorizontalMotion(editor, caret, count1, allowPastEnd, allowWrap)
}
abstract class MotionNonShiftedArrowRightBaseAction() : NonShiftedSpecialKeyHandler() {
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE
override fun motion( override fun motion(
@ -34,9 +45,38 @@ class MotionArrowRightAction : NonShiftedSpecialKeyHandler() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
val allowPastEnd = editor.usesVirtualSpace || editor.isEndAllowed || return doMotion(editor, caret, operatorArguments.count1, ">", allowPastEnd(editor))
operatorArguments.isOperatorPending // because of `d<Right>` removing the last character }
val allowWrap = injector.options(editor).whichwrap.contains(">")
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd, allowWrap) protected open fun allowPastEnd(editor: VimEditor) = editor.usesVirtualSpace || editor.isEndAllowed
}
// Note that Select mode is handled with [SelectMotionArrowRightAction]
@CommandOrMotion(keys = ["<Right>", "<kRight>"], modes = [Mode.NORMAL, Mode.VISUAL])
class MotionArrowRightAction : MotionNonShiftedArrowRightBaseAction()
@CommandOrMotion(keys = ["<Right>", "<kRight>"], modes = [Mode.OP_PENDING])
class MotionArrowRightOpPendingAction : MotionNonShiftedArrowRightBaseAction() {
// When the motion is used with an operator, the EOL character is counted.
// This allows e.g., `d<Right>` to delete the last character in a line. Note that we can't use editor.isEndAllowed to
// give us this because the current mode when we execute the operator/motion is no longer OP_PENDING.
// See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
override fun allowPastEnd(editor: VimEditor) = true
}
// Just needs to be a plain motion handler - it's not shifted, and the non-shifted actions don't apply in Insert mode
@CommandOrMotion(keys = ["<Right>", "<kRight>"], modes = [Mode.INSERT])
class MotionArrowRightInsertModeAction : MotionActionHandler.ForEachCaret() {
override val motionType: MotionType = MotionType.EXCLUSIVE
override fun getOffset(
editor: VimEditor,
caret: ImmutableVimCaret,
context: ExecutionContext,
argument: Argument?,
operatorArguments: OperatorArguments,
): Motion {
// Insert mode is always allowed past the end of the line
return doMotion(editor, caret, operatorArguments.count1, "]", allowPastEnd = true)
} }
} }

View File

@ -20,8 +20,8 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.MotionActionHandler import com.maddyhome.idea.vim.handler.MotionActionHandler
@CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING]) @CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.NORMAL, Mode.VISUAL])
class MotionBackspaceAction : MotionActionHandler.ForEachCaret() { open class MotionBackspaceAction(private val allowPastEnd: Boolean = false) : MotionActionHandler.ForEachCaret() {
override fun getOffset( override fun getOffset(
editor: VimEditor, editor: VimEditor,
caret: ImmutableVimCaret, caret: ImmutableVimCaret,
@ -30,24 +30,15 @@ class MotionBackspaceAction : MotionActionHandler.ForEachCaret() {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
val allowWrap = injector.options(editor).whichwrap.contains("b") val allowWrap = injector.options(editor).whichwrap.contains("b")
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowPastEnd = false, allowWrap) return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowPastEnd, allowWrap)
} }
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE
} }
@CommandOrMotion(keys = ["<Space>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING]) // When the motion is used with an operator, the EOL character is counted.
class MotionSpaceAction : MotionActionHandler.ForEachCaret() { // This allows e.g., `d<BS>` to delete the end of line character on the previous line when wrap is active
override fun getOffset( // ('whichwrap' contains "b")
editor: VimEditor, // See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
caret: ImmutableVimCaret, @CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.OP_PENDING])
context: ExecutionContext, class MotionBackspaceOpPendingModeAction : MotionBackspaceAction(allowPastEnd = true)
argument: Argument?,
operatorArguments: OperatorArguments,
): Motion {
val allowWrap = injector.options(editor).whichwrap.contains("s")
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd = false, allowWrap)
}
override val motionType: MotionType = MotionType.EXCLUSIVE
}

View File

@ -27,13 +27,9 @@ import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import java.util.* import java.util.*
@CommandOrMotion(keys = ["<End>"], modes = [Mode.INSERT]) abstract class MotionLastColumnBaseAction(private val isMotionForOperator: Boolean = false)
class MotionLastColumnInsertAction : MotionLastColumnAction() { : MotionActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
}
@CommandOrMotion(keys = ["$"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
open class MotionLastColumnAction : MotionActionHandler.ForEachCaret() {
override val motionType: MotionType = MotionType.INCLUSIVE override val motionType: MotionType = MotionType.INCLUSIVE
override fun getOffset( override fun getOffset(
@ -43,13 +39,26 @@ open class MotionLastColumnAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
val allow = if (editor.inVisualMode) { val allowPastEnd = if (editor.inVisualMode) {
injector.options(editor).selection != "old" injector.options(editor).selection != "old"
} else { } else {
if (operatorArguments.isOperatorPending) false else editor.isEndAllowed // Don't allow past end if this motion is for an operator. I.e., for something like `d$`, we don't want to delete
// the end of line character
if (isMotionForOperator) false else editor.isEndAllowed
} }
val offset = injector.motion.moveCaretToRelativeLineEnd(editor, caret, operatorArguments.count1 - 1, allow) val offset = injector.motion.moveCaretToRelativeLineEnd(editor, caret, operatorArguments.count1 - 1, allowPastEnd)
return Motion.AdjustedOffset(offset, VimMotionGroupBase.LAST_COLUMN) return Motion.AdjustedOffset(offset, VimMotionGroupBase.LAST_COLUMN)
} }
} }
@CommandOrMotion(keys = ["$"], modes = [Mode.NORMAL, Mode.VISUAL])
open class MotionLastColumnAction : MotionLastColumnBaseAction()
@CommandOrMotion(keys = ["$"], modes = [Mode.OP_PENDING])
class MotionLastColumnOpPendingAction : MotionLastColumnBaseAction(isMotionForOperator = true)
@CommandOrMotion(keys = ["<End>"], modes = [Mode.INSERT])
class MotionLastColumnInsertAction : MotionLastColumnAction() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
}

View File

@ -21,8 +21,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.MotionActionHandler import com.maddyhome.idea.vim.handler.MotionActionHandler
@CommandOrMotion(keys = ["h"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING]) abstract class MotionLeftBaseAction(private val allowPastEnd: Boolean) : MotionActionHandler.ForEachCaret() {
class MotionLeftAction : MotionActionHandler.ForEachCaret() {
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE
override fun getOffset( override fun getOffset(
@ -33,23 +32,16 @@ class MotionLeftAction : MotionActionHandler.ForEachCaret() {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
val allowWrap = injector.options(editor).whichwrap.contains("h") val allowWrap = injector.options(editor).whichwrap.contains("h")
val allowEnd = operatorArguments.isOperatorPending // dh deletes \n with wrap enabled return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowPastEnd, allowWrap)
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowEnd, allowWrap)
} }
} }
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["h"], modes = [Mode.NORMAL, Mode.VISUAL])
class MotionLeftInsertModeAction : MotionActionHandler.ForEachCaret() { class MotionLeftAction : MotionLeftBaseAction(allowPastEnd = false)
override val motionType: MotionType = MotionType.EXCLUSIVE
override fun getOffset( // When the motion is used with an operator, the EOL character is counted.
editor: VimEditor, // This allows e.g., `dh` to delete the end of line character on the previous line when wrap is active
caret: ImmutableVimCaret, // ('whichwrap' contains "h")
context: ExecutionContext, // See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
argument: Argument?, @CommandOrMotion(keys = ["h"], modes = [Mode.OP_PENDING])
operatorArguments: OperatorArguments, class MotionLeftOpPendingModeAction : MotionLeftBaseAction(allowPastEnd = true)
): Motion {
val allowWrap = injector.options(editor).whichwrap.contains("[")
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, true, allowWrap)
}
}

Some files were not shown because too many files have changed in this diff Show More