mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2024-11-24 22:42:53 +01:00
Compare commits
23 Commits
822dad52f3
...
cc9385f2a9
Author | SHA1 | Date | |
---|---|---|---|
cc9385f2a9 | |||
32a2384a46 | |||
fdd850de5a | |||
0f7116b136 | |||
db8f0251fb | |||
2ca2c1e774 | |||
f3c32da4d1 | |||
1a3b34d457 | |||
1f9159996d | |||
65c3acd891 | |||
223f65c003 | |||
293b854620 | |||
9e7c5fd603 | |||
00c799595f | |||
8577b5ed20 | |||
3af7a991a0 | |||
212af1798d | |||
002ef8f72f | |||
9115af6b3d | |||
25ca42d371 | |||
408687c9b3 | |||
85e00bf8fc | |||
bd6f2d4b2f |
1
.teamcity/_Self/Project.kt
vendored
1
.teamcity/_Self/Project.kt
vendored
@ -27,7 +27,6 @@ object Project : Project({
|
||||
// Active tests
|
||||
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||
buildType(TestingBuildType("2024.1.1", "<default>"))
|
||||
buildType(TestingBuildType("2024.2", "<default>"))
|
||||
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||
|
||||
buildType(PropertyBased)
|
||||
|
@ -535,10 +535,6 @@ Contributors:
|
||||
[![icon][github]](https://github.com/igorbabko)
|
||||
|
||||
Igor Babko
|
||||
* [![icon][mail]](mailto:533601+felixwiemuth@users.noreply.github.com)
|
||||
[![icon][github]](https://github.com/felixwiemuth)
|
||||
|
||||
Felix Wiemuth
|
||||
|
||||
Previous contributors:
|
||||
|
||||
|
@ -8,7 +8,7 @@ Every effort is made to make these options compatible with Vim behaviour.
|
||||
However, some differences are inevitable.
|
||||
|
||||
```
|
||||
'clipboard' 'cb' Defines clipboard behavior
|
||||
'clipboard' 'cb' Defines clipboard behavioue
|
||||
A comma-separated list of words to control clipboard behaviour:
|
||||
unnamed The clipboard register '*' is used instead of the
|
||||
unnamed register
|
||||
|
@ -20,7 +20,7 @@ ideaVersion=2024.2
|
||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||
ideaType=IC
|
||||
instrumentPluginCode=true
|
||||
version=chylex-41
|
||||
version=chylex-40
|
||||
javaVersion=17
|
||||
remoteRobotVersion=0.11.23
|
||||
antlrVersion=4.10.1
|
||||
|
@ -8,10 +8,6 @@
|
||||
|
||||
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.fileEditor.ex.FileEditorManagerEx
|
||||
import com.intellij.openapi.project.Project
|
||||
@ -44,18 +40,6 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
|
||||
|
||||
// This code should be executed once
|
||||
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")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
|
||||
|
||||
// todo make it multicaret
|
||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, motionType: SelectionType): Boolean {
|
||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
|
||||
val func = injector.globalOptions().operatorfunc
|
||||
if (func.isEmpty()) {
|
||||
VimPlugin.showMessage(MessageHelper.message("E774"))
|
||||
@ -57,9 +57,9 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
|
||||
if (value is VimFuncref) {
|
||||
handler = value.handler
|
||||
}
|
||||
} catch (_: ExException) {
|
||||
} catch (ex: ExException) {
|
||||
// Get the argument for function('...') or funcref('...') for the error message
|
||||
val functionName = if (expression is FunctionCallExpression && expression.arguments.isNotEmpty()) {
|
||||
val functionName = if (expression is FunctionCallExpression && expression.arguments.size > 0) {
|
||||
expression.arguments[0].evaluate(editor, context, scriptContext).toString()
|
||||
}
|
||||
else {
|
||||
@ -77,7 +77,7 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
|
||||
return false
|
||||
}
|
||||
|
||||
val arg = when (motionType) {
|
||||
val arg = when (selectionType) {
|
||||
SelectionType.LINE_WISE -> "line"
|
||||
SelectionType.CHARACTER_WISE -> "char"
|
||||
SelectionType.BLOCK_WISE -> "block"
|
||||
@ -101,13 +101,19 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
|
||||
override val argumentType: Argument.Type = Argument.Type.MOTION
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
val argument = cmd.argument as? Argument.Motion ?: return false
|
||||
val argument = cmd.argument ?: return false
|
||||
if (!editor.inRepeatMode) {
|
||||
argumentCaptured = argument
|
||||
}
|
||||
val range = getMotionRange(editor, context, argument, operatorArguments)
|
||||
|
||||
if (range != null) {
|
||||
return doOperatorAction(editor, context, range, argument.getMotionType())
|
||||
val selectionType = if (argument.motion.isLinewiseMotion()) {
|
||||
SelectionType.LINE_WISE
|
||||
} else {
|
||||
SelectionType.CHARACTER_WISE
|
||||
}
|
||||
return doOperatorAction(editor, context, range, selectionType)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -115,7 +121,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
|
||||
private fun getMotionRange(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
argument: Argument.Motion,
|
||||
argument: Argument,
|
||||
operatorArguments: OperatorArguments,
|
||||
): TextRange? {
|
||||
// Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start
|
||||
@ -130,7 +136,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
|
||||
operatorArguments,
|
||||
)?.normalize()?.let {
|
||||
// If we're linewise, make sure the end offset isn't just the EOL char
|
||||
if (argument.getMotionType() == SelectionType.LINE_WISE && it.endOffset < editor.fileSize()) {
|
||||
if (argument.motion.isLinewiseMotion() && it.endOffset < editor.fileSize()) {
|
||||
TextRange(it.startOffset, it.endOffset + 1)
|
||||
} else {
|
||||
it
|
||||
|
@ -25,7 +25,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
val state = injector.vimState
|
||||
var lastCommand = VimRepeater.lastChangeCommand
|
||||
val lastCommand = VimRepeater.lastChangeCommand
|
||||
|
||||
if (lastCommand == null && Extension.lastExtensionHandler == null) return false
|
||||
|
||||
@ -57,7 +57,12 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
|
||||
)
|
||||
} else if (!repeatHandler && lastCommand != null) {
|
||||
if (cmd.rawCount > 0) {
|
||||
lastCommand = lastCommand.copy(rawCount = cmd.rawCount)
|
||||
lastCommand.rawCount = cmd.count
|
||||
val arg = lastCommand.argument
|
||||
if (arg != null) {
|
||||
val mot = arg.motion
|
||||
mot.rawCount = 0
|
||||
}
|
||||
}
|
||||
state.executingCommand = lastCommand
|
||||
|
||||
|
@ -40,7 +40,7 @@ class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecuti
|
||||
): Boolean {
|
||||
injector.editorGroup.notifyIdeaJoin(editor)
|
||||
|
||||
return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false)
|
||||
return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false, operatorArguments)
|
||||
}
|
||||
|
||||
override fun execute(
|
||||
|
@ -35,7 +35,7 @@ class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution()
|
||||
injector.editorGroup.notifyIdeaJoin(editor)
|
||||
var res = true
|
||||
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
|
||||
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true)) {
|
||||
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) {
|
||||
res = false
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,11 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import java.util.*
|
||||
|
||||
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
|
||||
internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE) {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
|
||||
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE) {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
@ -33,6 +33,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
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.putKeyMappingIfMissing;
|
||||
@ -63,8 +64,8 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
*/
|
||||
private static class BracketPairs {
|
||||
// NOTE: brackets must match by the position, and ordered by rank (highest to lowest).
|
||||
private final @NotNull String openBrackets;
|
||||
private final @NotNull String closeBrackets;
|
||||
@NotNull private final String openBrackets;
|
||||
@NotNull private final String closeBrackets;
|
||||
|
||||
static class ParseException extends Exception {
|
||||
public ParseException(@NotNull String message) {
|
||||
@ -86,7 +87,8 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
* @param bracketPairs comma-separated list of colon-separated bracket pairs.
|
||||
* @throws ParseException if a syntax error is detected.
|
||||
*/
|
||||
static @NotNull BracketPairs fromBracketPairList(final @NotNull String bracketPairs) throws ParseException {
|
||||
@NotNull
|
||||
static BracketPairs fromBracketPairList(@NotNull final String bracketPairs) throws ParseException {
|
||||
StringBuilder openBrackets = new StringBuilder();
|
||||
StringBuilder closeBrackets = new StringBuilder();
|
||||
ParseState state = ParseState.OPEN;
|
||||
@ -126,7 +128,7 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
return new BracketPairs(openBrackets.toString(), closeBrackets.toString());
|
||||
}
|
||||
|
||||
BracketPairs(final @NotNull String openBrackets, final @NotNull String closeBrackets) {
|
||||
BracketPairs(@NotNull final String openBrackets, @NotNull final String closeBrackets) {
|
||||
assert openBrackets.length() == closeBrackets.length();
|
||||
this.openBrackets = openBrackets;
|
||||
this.closeBrackets = closeBrackets;
|
||||
@ -156,9 +158,10 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")");
|
||||
public static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")");
|
||||
|
||||
private static @Nullable String bracketPairsVariable() {
|
||||
@Nullable
|
||||
private static String bracketPairsVariable() {
|
||||
final Object value = VimPlugin.getVariableService().getGlobalVariableValue("argtextobj_pairs");
|
||||
if (value instanceof VimString vimValue) {
|
||||
return vimValue.getValue();
|
||||
@ -189,8 +192,9 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
this.isInner = isInner;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @Nullable TextRange getRange(@NotNull VimEditor editor,
|
||||
public TextRange getRange(@NotNull VimEditor editor,
|
||||
@NotNull ImmutableVimCaret caret,
|
||||
@NotNull ExecutionContext context,
|
||||
int count,
|
||||
@ -232,22 +236,24 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
return new TextRange(finder.getLeftBound(), finder.getRightBound());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public @NotNull TextObjectVisualType getVisualType() {
|
||||
public TextObjectVisualType getVisualType() {
|
||||
return TextObjectVisualType.CHARACTER_WISE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||
|
||||
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
||||
//noinspection DuplicatedCode
|
||||
if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
|
||||
int count0 = operatorArguments.getCount0();
|
||||
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||
editor.nativeCarets().forEach((VimCaret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, caret, context, Math.max(1, count0), count0);
|
||||
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||
@ -259,7 +265,8 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
|
||||
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -269,9 +276,9 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
* position
|
||||
*/
|
||||
private static class ArgBoundsFinder {
|
||||
private final @NotNull CharSequence text;
|
||||
private final @NotNull Document document;
|
||||
private final @NotNull BracketPairs brackets;
|
||||
@NotNull private final CharSequence text;
|
||||
@NotNull private final Document document;
|
||||
@NotNull private final BracketPairs brackets;
|
||||
private int leftBound = Integer.MAX_VALUE;
|
||||
private int rightBound = Integer.MIN_VALUE;
|
||||
private int leftBracket;
|
||||
@ -298,7 +305,7 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
* @param position starting position.
|
||||
*/
|
||||
boolean findBoundsAt(int position) throws IllegalStateException {
|
||||
if (text.isEmpty()) {
|
||||
if (text.length() == 0) {
|
||||
error = "empty document";
|
||||
return false;
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.getLineEndOffset
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.command.TextObjectVisualType
|
||||
@ -49,6 +52,7 @@ import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import java.util.*
|
||||
|
||||
internal class CommentaryExtension : VimExtension {
|
||||
|
||||
@ -180,8 +184,10 @@ internal class CommentaryExtension : VimExtension {
|
||||
override val isRepeatable = true
|
||||
|
||||
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
|
||||
keyState.commandBuilder.addAction(CommentaryTextObjectMotionHandler)
|
||||
keyState.commandBuilder.completeCommandPart(Argument(command))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,6 @@ import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@ -94,29 +93,34 @@ internal class Matchit : VimExtension {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
val keyState = keyHandler.keyHandlerState
|
||||
val count = keyState.commandBuilder.count
|
||||
|
||||
// Reset the command count so it doesn't transfer onto subsequent commands.
|
||||
keyState.commandBuilder.resetCount()
|
||||
|
||||
// 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.
|
||||
if (editor.mode is Mode.OP_PENDING) {
|
||||
val isInOpPending = keyHandler.isOperatorPending(editor.mode, keyState)
|
||||
|
||||
if (isInOpPending) {
|
||||
val matchitAction = MatchitAction()
|
||||
matchitAction.reverse = reverse
|
||||
matchitAction.isInOpPending = true
|
||||
|
||||
keyState.commandBuilder.addAction(matchitAction)
|
||||
keyState.commandBuilder.completeCommandPart(
|
||||
Argument(
|
||||
Command(
|
||||
count,
|
||||
matchitAction,
|
||||
Command.Type.MOTION,
|
||||
EnumSet.noneOf(CommandFlags::class.java),
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
editor.sortedCarets().forEach { caret ->
|
||||
injector.jumpService.saveJumpLocation(editor)
|
||||
caret.moveToOffset(
|
||||
getMatchitOffset(
|
||||
editor.ij,
|
||||
caret.ij,
|
||||
operatorArguments.count0,
|
||||
isInOpPending = false,
|
||||
reverse
|
||||
))
|
||||
caret.moveToOffset(getMatchitOffset(editor.ij, caret.ij, count, isInOpPending, reverse))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -350,7 +354,7 @@ private object FileTypePatterns {
|
||||
|
||||
private val DEFAULT_PAIRS = setOf('(', ')', '[', ']', '{', '}')
|
||||
|
||||
private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPending: Boolean, reverse: Boolean): Int {
|
||||
private fun getMatchitOffset(editor: Editor, caret: Caret, count: Int, isInOpPending: Boolean, reverse: Boolean): Int {
|
||||
val virtualFile = EditorHelper.getVirtualFile(editor)
|
||||
var caretOffset = caret.offset
|
||||
|
||||
@ -363,9 +367,9 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPe
|
||||
val currentChar = editor.document.charsSequence[caretOffset]
|
||||
var motionOffset: Int? = null
|
||||
|
||||
if (count0 > 0) {
|
||||
if (count > 0) {
|
||||
// Matchit doesn't affect the percent motion, so we fall back to the default behavior.
|
||||
motionOffset = VimPlugin.getMotion().moveCaretToLinePercent(editor.vim, caret.vim, count0)
|
||||
motionOffset = VimPlugin.getMotion().moveCaretToLinePercent(editor.vim, caret.vim, count)
|
||||
} else {
|
||||
// Check the simplest case first.
|
||||
if (DEFAULT_PAIRS.contains(currentChar)) {
|
||||
@ -396,7 +400,8 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPe
|
||||
|
||||
private fun getMotionOffset(motion: Motion): Int? {
|
||||
return when (motion) {
|
||||
is Motion.AdjustedOffset, is Motion.AbsoluteOffset -> motion.offset
|
||||
is Motion.AbsoluteOffset -> motion.offset
|
||||
is Motion.AdjustedOffset -> motion.offset
|
||||
is Motion.Error, is Motion.NoMotion -> null
|
||||
}
|
||||
}
|
||||
|
@ -555,13 +555,12 @@ private fun registerCommand(default: String, action: NerdAction) {
|
||||
}
|
||||
|
||||
|
||||
private val actionsRoot: RootNode<NerdAction> = RootNode("NERDTree")
|
||||
private val actionsRoot: RootNode<NerdAction> = RootNode()
|
||||
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
|
||||
|
||||
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
|
||||
return if (node is CommandPartNode<NerdAction>) {
|
||||
val res = node.children.keys.toMutableSet()
|
||||
res += node.children.values.map { collectShortcuts(it) }.flatten()
|
||||
val res = node.keys.toMutableSet()
|
||||
res += node.values.map { collectShortcuts(it) }.flatten()
|
||||
res
|
||||
} else {
|
||||
emptySet()
|
||||
|
@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.extension.replacewithregister
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
@ -165,11 +166,17 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
|
||||
putToLine = -1,
|
||||
)
|
||||
val vimEditor = editor.vim
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
ClipboardOptionHelper.IdeaputDisabler().use {
|
||||
VimPlugin.getPut().putText(
|
||||
vimEditor,
|
||||
context.vim,
|
||||
putData,
|
||||
operatorArguments = OperatorArguments(
|
||||
keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState),
|
||||
0,
|
||||
editor.vim.mode,
|
||||
),
|
||||
saveToRegister = false
|
||||
)
|
||||
}
|
||||
|
@ -29,12 +29,14 @@ import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.putKeyMappingIfMissing;
|
||||
|
||||
/**
|
||||
* Port of vim-entire:
|
||||
* <a href="https://github.com/kana/vim-textobj-entire">vim-textobj-entire</a>
|
||||
* https://github.com/kana/vim-textobj-entire
|
||||
*
|
||||
* <p>
|
||||
* vim-textobj-entire provides two text objects:
|
||||
@ -49,7 +51,7 @@ import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingI
|
||||
* </ul>
|
||||
*
|
||||
* See also the reference manual for more details at:
|
||||
* <a href="https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt">text-obj-entire.txt</a>
|
||||
* https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt
|
||||
*
|
||||
* @author Alexandre Grison (@agrison)
|
||||
*/
|
||||
@ -92,8 +94,9 @@ public class VimTextObjEntireExtension implements VimExtension {
|
||||
this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @Nullable TextRange getRange(@NotNull VimEditor editor,
|
||||
public TextRange getRange(@NotNull VimEditor editor,
|
||||
@NotNull ImmutableVimCaret caret,
|
||||
@NotNull ExecutionContext context,
|
||||
int count,
|
||||
@ -122,22 +125,24 @@ public class VimTextObjEntireExtension implements VimExtension {
|
||||
return new TextRange(start, end);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public @NotNull TextObjectVisualType getVisualType() {
|
||||
public TextObjectVisualType getVisualType() {
|
||||
return TextObjectVisualType.CHARACTER_WISE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||
|
||||
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
|
||||
//noinspection DuplicatedCode
|
||||
if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
|
||||
int count0 = operatorArguments.getCount0();
|
||||
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, Math.max(1, count0), count0);
|
||||
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||
@ -150,7 +155,9 @@ public class VimTextObjEntireExtension implements VimExtension {
|
||||
|
||||
});
|
||||
} else {
|
||||
keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
|
||||
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION,
|
||||
EnumSet.noneOf(CommandFlags.class))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,12 +30,14 @@ import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.putKeyMapping;
|
||||
|
||||
/**
|
||||
* Port of vim-indent-object:
|
||||
* <a href="https://github.com/michaeljsmith/vim-indent-object">vim-indent-object</a>
|
||||
* https://github.com/michaeljsmith/vim-indent-object
|
||||
*
|
||||
* <p>
|
||||
* vim-indent-object provides these text objects based on the cursor line's indentation:
|
||||
@ -47,7 +49,7 @@ import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
|
||||
* </ul>
|
||||
*
|
||||
* See also the reference manual for more details at:
|
||||
* <a href="https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt">indent-object.txt</a>
|
||||
* https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt
|
||||
*
|
||||
* @author Shrikant Kandula (@sharat87)
|
||||
*/
|
||||
@ -96,8 +98,9 @@ public class VimIndentObject implements VimExtension {
|
||||
this.includeBelow = includeBelow;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @Nullable TextRange getRange(@NotNull VimEditor editor,
|
||||
public TextRange getRange(@NotNull VimEditor editor,
|
||||
@NotNull ImmutableVimCaret caret,
|
||||
@NotNull ExecutionContext context,
|
||||
int count,
|
||||
@ -246,8 +249,9 @@ public class VimIndentObject implements VimExtension {
|
||||
return new TextRange(upperBoundaryOffset, lowerBoundaryOffset);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public @NotNull TextObjectVisualType getVisualType() {
|
||||
public TextObjectVisualType getVisualType() {
|
||||
return TextObjectVisualType.LINE_WISE;
|
||||
}
|
||||
|
||||
@ -260,14 +264,15 @@ public class VimIndentObject implements VimExtension {
|
||||
@Override
|
||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||
IjVimEditor vimEditor = (IjVimEditor)editor;
|
||||
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||
|
||||
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
|
||||
|
||||
if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
|
||||
int count0 = operatorArguments.getCount0();
|
||||
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, Math.max(1, count0), count0);
|
||||
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||
@ -280,7 +285,9 @@ public class VimIndentObject implements VimExtension {
|
||||
|
||||
});
|
||||
} else {
|
||||
keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
|
||||
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION,
|
||||
EnumSet.noneOf(CommandFlags.class))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,11 +103,6 @@ class ChangeGroup : VimChangeGroupBase() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun processBackspace(editor: VimEditor, context: ExecutionContext) {
|
||||
injector.actionExecutor.executeAction(editor, name = IdeActions.ACTION_EDITOR_BACKSPACE, context = context)
|
||||
injector.scroll.scrollCaretIntoView(editor)
|
||||
}
|
||||
|
||||
private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) {
|
||||
if (caret != editor.primaryCaret()) {
|
||||
(editor as IjVimEditor).editor.caretModel.addCaret(
|
||||
|
@ -35,7 +35,7 @@ import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.MotionType
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.handler.ExternalActionHandler
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
@ -193,16 +193,21 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
argument: Argument,
|
||||
operatorArguments: OperatorArguments,
|
||||
): TextRange? {
|
||||
if (argument !is Argument.Motion) {
|
||||
throw RuntimeException("Unexpected argument passed to getMotionRange2: $argument")
|
||||
}
|
||||
|
||||
var start: 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
|
||||
start = caret.offset
|
||||
|
||||
@ -211,8 +216,8 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
editor.vim,
|
||||
caret.vim,
|
||||
IjEditorExecutionContext(context!!),
|
||||
argument.argument,
|
||||
operatorArguments
|
||||
cmd.argument,
|
||||
operatorArguments.withCount0(raw),
|
||||
)
|
||||
|
||||
// Invalid motion
|
||||
@ -228,32 +233,22 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
end++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is TextObjectActionHandler -> {
|
||||
val range = action.getRange(
|
||||
editor.vim,
|
||||
caret.vim,
|
||||
IjEditorExecutionContext(context!!),
|
||||
operatorArguments.count1,
|
||||
operatorArguments.count0
|
||||
) ?: return null
|
||||
} else if (cmd.action is TextObjectActionHandler) {
|
||||
val action = cmd.action as TextObjectActionHandler
|
||||
val range =
|
||||
action.getRange(editor.vim, caret.vim, IjEditorExecutionContext(context!!), cnt, raw) ?: return null
|
||||
start = range.startOffset
|
||||
end = range.endOffset
|
||||
if (argument.isLinewiseMotion()) end--
|
||||
if (cmd.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.
|
||||
val id = argument.motion.id
|
||||
val id = argument.motion.action.id
|
||||
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 lastNewLine = text.lastIndexOf('\n')
|
||||
@ -263,7 +258,6 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TextRange(start, end)
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,6 @@ internal class IjActionExecutor : VimActionExecutor {
|
||||
override val ACTION_EXPAND_REGION_RECURSIVELY: String
|
||||
get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY
|
||||
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"
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.getLineEndForOffset
|
||||
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.newapi.IjEditorExecutionContext
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
@ -93,6 +94,6 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Editor.exitInsertMode(context: DataContext) {
|
||||
VimPlugin.getChange().processEscape(IjVimEditor(this), IjEditorExecutionContext(context))
|
||||
internal fun Editor.exitInsertMode(context: DataContext, operatorArguments: OperatorArguments) {
|
||||
VimPlugin.getChange().processEscape(IjVimEditor(this), IjEditorExecutionContext(context), operatorArguments)
|
||||
}
|
||||
|
@ -24,9 +24,7 @@ import com.maddyhome.idea.vim.common.InsertSequence
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
||||
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
|
||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
||||
@ -125,7 +123,6 @@ internal var Editor.vimExOutput: ExOutputModel? by userData()
|
||||
internal var Editor.vimTestInputModel: TestInputModel? by userData()
|
||||
|
||||
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
|
||||
internal var Editor.replaceMask: VimEditorReplaceMask? by userData()
|
||||
|
||||
internal var Caret.currentInsert: InsertSequence? by userData()
|
||||
internal val Caret.insertHistory: MutableList<InsertSequence> by userDataOr { mutableListOf() }
|
||||
|
@ -15,6 +15,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.EditorListener
|
||||
import com.maddyhome.idea.vim.helper.inInsertMode
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
@ -64,7 +65,7 @@ class IJEditorFocusListener : EditorListener {
|
||||
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
|
||||
val mode = injector.vimState.mode
|
||||
when (mode) {
|
||||
is Mode.INSERT -> editor.exitInsertMode(context)
|
||||
is Mode.INSERT -> editor.exitInsertMode(context, OperatorArguments(false, 0, mode))
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@ -78,4 +79,3 @@ class IJEditorFocusListener : EditorListener {
|
||||
KeyHandler.getInstance().reset(editor)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,7 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
|
||||
import com.intellij.codeInsight.template.Template
|
||||
import com.intellij.codeInsight.template.TemplateEditingAdapter
|
||||
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.actions.NextVariableAction
|
||||
import com.intellij.find.FindModelListener
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
@ -154,10 +152,6 @@ internal object IdeaSpecifics {
|
||||
KeyHandler.getInstance().reset(it.vim)
|
||||
}
|
||||
}
|
||||
else if (action is NextVariableAction && TemplateManagerImpl.getTemplateState(editor) == null) {
|
||||
editor.vim.exitInsertMode(event.dataContext.vim)
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
//endregion
|
||||
|
||||
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
|
||||
|
@ -66,6 +66,7 @@ import com.maddyhome.idea.vim.api.coerceOffset
|
||||
import com.maddyhome.idea.vim.api.getLineEndForOffset
|
||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||
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.FileGroup
|
||||
import com.maddyhome.idea.vim.group.IjOptions
|
||||
@ -394,8 +395,7 @@ internal object VimListenerManager {
|
||||
editor.vim.mode = Mode.NORMAL()
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
// Breaks relativenumber for some reason
|
||||
// injector.scroll.scrollCaretIntoView(editor.vim)
|
||||
injector.scroll.scrollCaretIntoView(editor.vim)
|
||||
}
|
||||
|
||||
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
|
@ -17,24 +17,6 @@ internal class IjLiveRange(val marker: RangeMarker) : LiveRange {
|
||||
|
||||
override val endOffset: Int
|
||||
get() = marker.endOffset
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as IjLiveRange
|
||||
|
||||
if (startOffset != other.startOffset) return false
|
||||
if (endOffset != other.endOffset) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startOffset
|
||||
result = 31 * result + endOffset
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val RangeMarker.vim: LiveRange
|
||||
|
@ -38,12 +38,12 @@ import com.maddyhome.idea.vim.api.VimSelectionModel
|
||||
import com.maddyhome.idea.vim.api.VimVisualPosition
|
||||
import com.maddyhome.idea.vim.api.VirtualFile
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.IndentConfig
|
||||
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.common.ModeChangeListener
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
||||
import com.maddyhome.idea.vim.common.forgetAllReplaceMasks
|
||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemBlockSelectionSilently
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
@ -53,7 +53,6 @@ import com.maddyhome.idea.vim.helper.fileSize
|
||||
import com.maddyhome.idea.vim.helper.getTopLevelEditor
|
||||
import com.maddyhome.idea.vim.helper.inExMode
|
||||
import com.maddyhome.idea.vim.helper.isTemplateActive
|
||||
import com.maddyhome.idea.vim.helper.replaceMask
|
||||
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
|
||||
import com.maddyhome.idea.vim.helper.vimLastSelectionType
|
||||
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
|
||||
@ -76,11 +75,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
|
||||
// TBH, I don't like the names. Need to think a bit more about this
|
||||
val editor = editor.getTopLevelEditor()
|
||||
val originalEditor = editor
|
||||
override var replaceMask: VimEditorReplaceMask?
|
||||
get() = editor.replaceMask
|
||||
set(value) {
|
||||
editor.replaceMask = value
|
||||
}
|
||||
|
||||
override fun updateMode(mode: Mode) {
|
||||
(injector.vimState as VimStateMachineImpl).mode = mode
|
||||
@ -97,7 +91,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
|
||||
editor.vimChangeActionSwitchMode = value
|
||||
}
|
||||
override val indentConfig: VimIndentConfig
|
||||
get() = IndentConfig.create(editor)
|
||||
get() = create(editor)
|
||||
|
||||
override fun fileSize(): Long = editor.fileSize.toLong()
|
||||
|
||||
@ -404,8 +398,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
|
||||
return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight))
|
||||
}
|
||||
|
||||
override fun exitInsertMode(context: ExecutionContext) {
|
||||
editor.exitInsertMode(context.ij)
|
||||
override fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
editor.exitInsertMode(context.ij, operatorArguments)
|
||||
}
|
||||
|
||||
override fun exitSelectModeNative(adjustCaret: Boolean) {
|
||||
@ -477,7 +471,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
|
||||
get() = (editor as? EditorEx)?.isInsertMode ?: false
|
||||
set(value) {
|
||||
(editor as? EditorEx)?.isInsertMode = value
|
||||
forgetAllReplaceMasks()
|
||||
}
|
||||
|
||||
override val document: VimDocument
|
||||
|
@ -192,13 +192,6 @@ private object VimActionsPopup {
|
||||
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.add(JoinEap)
|
||||
|
@ -344,12 +344,12 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
}
|
||||
}
|
||||
|
||||
// Get a snapshot of the count for the in progress command, and coerce it to 1. This value will include all
|
||||
// count components - selecting register(s), operator and motions. E.g. `2"a3"b4"c5d6/` will return 720.
|
||||
// If we're showing highlights for an Ex command like `:s`, the command builder will be empty, but we'll still
|
||||
// get a valid value.
|
||||
int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder()
|
||||
.calculateCount0Snapshot());
|
||||
// Get the current count from the command builder. This value is coerced to at least 1, so will always be valid.
|
||||
// The aggregated value includes any counts for operator and register selections, and the uncommitted count for
|
||||
// the search command (`/` or `?`). E.g., `2"a3"b4"c5d6/` would return 720.
|
||||
// If we're showing highlights for an ex command like `:s`, there won't be a command, but the value is already
|
||||
// coerced to at least 1.
|
||||
int count1 = KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getAggregatedUncommittedCount();
|
||||
|
||||
if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
|
||||
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
||||
@ -531,8 +531,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
|
||||
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public @NotNull VimCommandLineCaret getCaret() {
|
||||
public VimCommandLineCaret getCaret() {
|
||||
return (VimCommandLineCaret) entry.getCaret();
|
||||
}
|
||||
|
||||
@ -550,8 +551,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
entry.clearCurrentAction();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @Nullable Integer getPromptCharacterOffset() {
|
||||
public Integer getPromptCharacterOffset() {
|
||||
int offset = entry.currentActionPromptCharacterOffset;
|
||||
return offset == -1 ? null : offset;
|
||||
}
|
||||
@ -571,7 +573,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
IdeFocusManager.findInstance().requestFocus(entry, true);
|
||||
}
|
||||
|
||||
public @Nullable VimInputInterceptor<?> getInputInterceptor() {
|
||||
@Nullable
|
||||
public VimInputInterceptor<?> getInputInterceptor() {
|
||||
return myInputInterceptor;
|
||||
}
|
||||
|
||||
@ -584,13 +587,15 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
myInputInterceptor = vimInputInterceptor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @Nullable Function1<String, Unit> getInputProcessing() {
|
||||
public Function1<String, Unit> getInputProcessing() {
|
||||
return inputProcessing;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @Nullable Character getFinishOn() {
|
||||
public Character getFinishOn() {
|
||||
return finishOn;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,6 @@ viminfo
|
||||
virtualedit
|
||||
visualbell
|
||||
visualdelay
|
||||
whichwrap
|
||||
wrapscan
|
||||
|
||||
nobomb
|
||||
|
@ -4,6 +4,16 @@
|
||||
"class": "com.maddyhome.idea.vim.action.change.RepeatChangeAction",
|
||||
"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>",
|
||||
"class": "com.maddyhome.idea.vim.action.editor.VimEditorTab",
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
package org.jetbrains.plugins.ideavim.action
|
||||
|
||||
import com.intellij.idea.TestFor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||
@ -1069,14 +1068,4 @@ foobaz
|
||||
Mode.NORMAL(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["VIM-2074"])
|
||||
fun `backspace with replace mode`() {
|
||||
configureByText("${c}Hello world")
|
||||
typeText("R1111")
|
||||
assertState("1111o world")
|
||||
typeText("<BS><BS><BS>")
|
||||
assertState("1ello world")
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class CopyActionTest : VimTestCase() {
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||
@Test
|
||||
fun testYankRegisterUsesLastEnteredRegister() {
|
||||
typeTextInFile("\"a\"byl" + "\"bp", "hel<caret>lo world\n")
|
||||
typeTextInFile("\"a\"byl" + "\"ap", "hel<caret>lo world\n")
|
||||
assertState("helllo world\n")
|
||||
}
|
||||
|
||||
|
@ -85,33 +85,4 @@ class MotionBackspaceActionTest : VimTestCase() {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -85,35 +85,4 @@ class MotionSpaceActionTest : VimTestCase() {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class VimVariableServiceTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test v count variable without count specified`() {
|
||||
configureByText("\n")
|
||||
enterCommand("""nnoremap <expr> n ':echo ' .. v:count .. "\<CR>"""")
|
||||
enterCommand("nnoremap <expr> n ':echo ' .. v:count .. \"\\<CR>\"")
|
||||
typeText("n")
|
||||
assertExOutput("0")
|
||||
}
|
||||
@ -23,31 +23,15 @@ class VimVariableServiceTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test v count variable`() {
|
||||
configureByText("\n")
|
||||
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count .. "\<CR>"""")
|
||||
enterCommand("nnoremap <expr> n ':' .. \"\\<C-u>\" .. 'echo ' .. v:count .. \"\\<CR>\"")
|
||||
typeText("5n")
|
||||
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
|
||||
fun `test v count1 variable without count specified`() {
|
||||
configureByText("\n")
|
||||
enterCommand("""nnoremap <expr> n ':echo ' .. v:count1 .. "\<CR>"""")
|
||||
enterCommand("nnoremap <expr> n ':echo ' .. v:count1 .. \"\\<CR>\"")
|
||||
typeText("n")
|
||||
assertExOutput("1")
|
||||
}
|
||||
@ -55,27 +39,11 @@ class VimVariableServiceTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test v count1 variable`() {
|
||||
configureByText("\n")
|
||||
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count1 .. "\<CR>"""")
|
||||
enterCommand("nnoremap <expr> n ':' .. \"\\<C-u>\" .. 'echo ' .. v:count1 .. \"\\<CR>\"")
|
||||
typeText("5n")
|
||||
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
|
||||
fun `test mapping with updating jumplist`() {
|
||||
configureByText("${c}1\n2\n3\n4\n5\n6\n7\n8\n9\n")
|
||||
|
@ -141,16 +141,6 @@ 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
|
||||
fun testSelectTwoArguments() {
|
||||
doTest(
|
||||
|
@ -95,7 +95,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
|
||||
val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie()
|
||||
|
||||
// Note: esc is always an option
|
||||
val possibleKeys = (currentNode.children.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) }
|
||||
val possibleKeys = (currentNode.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) }
|
||||
println("Keys: ${possibleKeys.joinToString(", ")}")
|
||||
val keyGenerator = Generator.integers(0, possibleKeys.lastIndex)
|
||||
.suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList }
|
||||
|
@ -17,13 +17,14 @@ import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.command.MappingProcessor
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.CurrentCommandState
|
||||
import com.maddyhome.idea.vim.diagnostic.VimLogger
|
||||
import com.maddyhome.idea.vim.diagnostic.trace
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.impl.state.toMappingMode
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||
import com.maddyhome.idea.vim.key.KeyStack
|
||||
import com.maddyhome.idea.vim.key.RootNode
|
||||
import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.CommandConsumer
|
||||
import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer
|
||||
@ -196,9 +197,11 @@ class KeyHandler {
|
||||
}
|
||||
|
||||
private fun onUnknownKey(editor: VimEditor, keyState: KeyHandlerState) {
|
||||
logger.trace("Command builder is set to BAD")
|
||||
keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
|
||||
editor.resetOpPending()
|
||||
injector.vimState.resetRegisterPending()
|
||||
editor.isReplaceCharacter = false
|
||||
// Note that this will also reset the CommandBuilder to NEW_COMMAND
|
||||
reset(keyState, editor.mode)
|
||||
}
|
||||
|
||||
@ -207,6 +210,14 @@ class KeyHandler {
|
||||
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(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
@ -215,7 +226,11 @@ class KeyHandler {
|
||||
) {
|
||||
logger.trace("Command execution")
|
||||
val command = keyState.commandBuilder.buildCommand()
|
||||
val operatorArguments = OperatorArguments(command.rawCount, editorState.mode)
|
||||
val operatorArguments = OperatorArguments(
|
||||
editor.mode is Mode.OP_PENDING,
|
||||
command.rawCount,
|
||||
editorState.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`
|
||||
@ -280,7 +295,7 @@ class KeyHandler {
|
||||
keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode()))
|
||||
}
|
||||
|
||||
private fun getKeyRoot(mappingMode: MappingMode): RootNode<LazyVimCommand> {
|
||||
private fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand> {
|
||||
return injector.keyGroup.getKeyRoot(mappingMode)
|
||||
}
|
||||
|
||||
@ -326,7 +341,7 @@ class KeyHandler {
|
||||
) : Runnable {
|
||||
override fun run() {
|
||||
val editorState = injector.vimState
|
||||
|
||||
keyState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
|
||||
val register = cmd.register
|
||||
if (register != null) {
|
||||
injector.registerGroup.selectRegister(register)
|
||||
@ -346,15 +361,22 @@ class KeyHandler {
|
||||
// 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
|
||||
// "select register"
|
||||
if (editorState.mode is Mode.NORMAL && !cmd.flags.contains(CommandFlags.FLAG_EXPECT_MORE)) {
|
||||
when (editorState.mode.returnTo) {
|
||||
ReturnTo.INSERT -> editor.mode = Mode.INSERT
|
||||
ReturnTo.REPLACE -> editor.mode = Mode.REPLACE
|
||||
null -> {}
|
||||
}
|
||||
val myMode = editorState.mode
|
||||
val returnTo = myMode.returnTo
|
||||
if (myMode is Mode.NORMAL && returnTo != null && !cmd.flags.contains(CommandFlags.FLAG_EXPECT_MORE)) {
|
||||
when (returnTo) {
|
||||
ReturnTo.INSERT -> {
|
||||
editor.mode = Mode.INSERT
|
||||
}
|
||||
|
||||
instance.reset(keyState, editorState.mode)
|
||||
ReturnTo.REPLACE -> {
|
||||
editor.mode = Mode.REPLACE
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keyState.commandBuilder.isDone()) {
|
||||
getInstance().reset(keyState, editorState.mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,17 +17,23 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.lineLength
|
||||
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.OperatorArguments
|
||||
import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import java.util.*
|
||||
|
||||
@CommandOrMotion(keys = ["r"], modes = [Mode.NORMAL])
|
||||
class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() {
|
||||
override val type: Command.Type = Command.Type.CHANGE
|
||||
|
||||
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) {
|
||||
editor.isReplaceCharacter = true
|
||||
}
|
||||
@ -39,7 +45,7 @@ class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() {
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
return argument is Argument.Character && changeCharacter(editor, caret, operatorArguments.count1, argument.character)
|
||||
return argument != null && changeCharacter(editor, caret, operatorArguments.count1, argument.character)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,11 +37,12 @@ class ChangeLineAction : ChangeInInsertSequenceAction() {
|
||||
): Boolean {
|
||||
// `S` command is a synonym of `cc`
|
||||
val motion = MotionDownLess1FirstNonSpaceAction()
|
||||
val command = Command(1, motion, motion.type, motion.flags)
|
||||
return injector.changeGroup.changeMotion(
|
||||
editor,
|
||||
caret,
|
||||
context,
|
||||
Argument.Motion(motion, null),
|
||||
Argument(command),
|
||||
operatorArguments,
|
||||
)
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ 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.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
|
||||
@CommandOrMotion(keys = ["R"], modes = [Mode.NORMAL])
|
||||
@ -40,5 +39,4 @@ class ChangeReplaceAction : ChangeEditorActionHandler.SingleExecution() {
|
||||
*/
|
||||
private fun changeReplace(editor: VimEditor, context: ExecutionContext) {
|
||||
injector.changeGroup.initInsert(editor, context, com.maddyhome.idea.vim.state.mode.Mode.REPLACE)
|
||||
editor.replaceMask = VimEditorReplaceMask()
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ class ChangeVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
range.toVimTextRange(false),
|
||||
range.type,
|
||||
context,
|
||||
operatorArguments,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,16 @@ import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
@ -29,8 +32,11 @@ import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
@CommandOrMotion(keys = ["r"], modes = [Mode.VISUAL])
|
||||
class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
override val type: Command.Type = Command.Type.CHANGE
|
||||
|
||||
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) {
|
||||
editor.isReplaceCharacter = true
|
||||
}
|
||||
@ -44,7 +50,7 @@ class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
val argument = cmd.argument
|
||||
return argument is Argument.Character &&
|
||||
return argument != null &&
|
||||
changeCharacterRange(editor, caret, range.toVimTextRange(false), argument.character)
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ class ChangeVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
lineRange,
|
||||
SelectionType.LINE_WISE,
|
||||
context,
|
||||
operatorArguments,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ class ChangeVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
}
|
||||
}
|
||||
val blockRange = TextRange(starts, ends)
|
||||
injector.changeGroup.changeRange(editor, caret, blockRange, SelectionType.BLOCK_WISE, context)
|
||||
injector.changeGroup.changeRange(editor, caret, blockRange, SelectionType.BLOCK_WISE, context, operatorArguments)
|
||||
} else {
|
||||
val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset)
|
||||
val endsWithNewLine = if (lineEndForOffset.toLong() == editor.fileSize()) 0 else 1
|
||||
@ -61,7 +61,7 @@ class ChangeVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
editor.getLineStartForOffset(vimTextRange.startOffset),
|
||||
lineEndForOffset + endsWithNewLine,
|
||||
)
|
||||
injector.changeGroup.changeRange(editor, caret, lineRange, SelectionType.LINE_WISE, context)
|
||||
injector.changeGroup.changeRange(editor, caret, lineRange, SelectionType.LINE_WISE, context, operatorArguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class FilterVisualLinesAction : VimActionHandler.SingleExecution(), FilterComman
|
||||
|
||||
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 `!`
|
||||
startFilterCommand(editor, context, cmd.rawCount)
|
||||
startFilterCommand(editor, context, cmd)
|
||||
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 `!`
|
||||
val count = if (start.line < end.line) end.line - start.line + 1 else 1
|
||||
startFilterCommand(editor, context, count)
|
||||
startFilterCommand(editor, context, Argument.EMPTY_COMMAND.copy(rawCount = count))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
interface FilterCommand {
|
||||
fun startFilterCommand(editor: VimEditor, context: ExecutionContext, count0: Int) {
|
||||
injector.commandLine.createCommandPrompt(editor, context, count0, initialText = "!")
|
||||
fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||
injector.commandLine.createCommandPrompt(editor, context, cmd, initialText = "!")
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,6 @@ class DeleteMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO
|
||||
val (range, selectionType) = injector.changeGroup
|
||||
.getDeleteRangeAndType(editor, caret, context, argument, false, operatorArguments)
|
||||
?: return false
|
||||
return injector.changeGroup.deleteRange(editor, caret, range, selectionType, false)
|
||||
return injector.changeGroup.deleteRange(editor, caret, range, selectionType, false, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ class DeleteVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
range.toVimTextRange(false),
|
||||
selectionType,
|
||||
false,
|
||||
operatorArguments,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,6 @@ class DeleteVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
Triple(caret, lineRange, SelectionType.LINE_WISE)
|
||||
}
|
||||
}
|
||||
return injector.changeGroup.deleteRange(editor, usedCaret, usedRange, usedType, false)
|
||||
return injector.changeGroup.deleteRange(editor, usedCaret, usedRange, usedType, false, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ class DeleteVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
blockRange,
|
||||
SelectionType.BLOCK_WISE,
|
||||
false,
|
||||
operatorArguments,
|
||||
)
|
||||
} else {
|
||||
val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset)
|
||||
@ -66,7 +67,7 @@ class DeleteVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
editor.getLineStartForOffset(vimTextRange.startOffset),
|
||||
lineEndForOffset + endsWithNewLine,
|
||||
)
|
||||
injector.changeGroup.deleteRange(editor, caret, lineRange, SelectionType.LINE_WISE, false)
|
||||
injector.changeGroup.deleteRange(editor, caret, lineRange, SelectionType.LINE_WISE, false, operatorArguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.action.change.insert
|
||||
|
||||
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.VimEditor
|
||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
|
||||
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
|
||||
internal class InsertBackspaceAction : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_WRITABLE
|
||||
|
||||
override fun execute( editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments, ): Boolean {
|
||||
if (editor.insertMode) {
|
||||
injector.changeGroup.processBackspace(editor, context)
|
||||
} else {
|
||||
for (caret in editor.carets()) {
|
||||
val offset = (caret.offset - 1).takeIf { it > 0 } ?: continue
|
||||
val oldChar = editor.replaceMask?.popChange(editor, offset)
|
||||
if (oldChar != null) {
|
||||
injector.changeGroup.replaceText(editor, caret, offset, offset + 1, oldChar.toString())
|
||||
}
|
||||
caret.moveToOffset(offset)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
@ -52,8 +52,7 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
// The converted digraph character has been captured as an argument, push it back through key handler
|
||||
val argument = cmd.argument as? Argument.Character ?: return false
|
||||
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
||||
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
|
||||
return true
|
||||
|
@ -52,8 +52,7 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
// The converted literal character has been captured as an argument, push it back through key handler
|
||||
val argument = cmd.argument as? Argument.Character ?: return false
|
||||
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
||||
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
|
||||
return true
|
||||
|
@ -36,7 +36,7 @@ class InsertDeleteInsertedTextAction : ChangeEditorActionHandler.ForEachCaret()
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
return insertDeleteInsertedText(editor, caret)
|
||||
return insertDeleteInsertedText(editor, caret, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,11 @@ class InsertDeleteInsertedTextAction : ChangeEditorActionHandler.ForEachCaret()
|
||||
* @param caret The caret on which the action is performed
|
||||
* @return true if able to delete the text, false if not
|
||||
*/
|
||||
private fun insertDeleteInsertedText(editor: VimEditor, caret: VimCaret): Boolean {
|
||||
private fun insertDeleteInsertedText(
|
||||
editor: VimEditor,
|
||||
caret: VimCaret,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
var deleteTo = caret.vimInsertStart.startOffset
|
||||
val offset = caret.offset
|
||||
if (offset == deleteTo) {
|
||||
@ -61,6 +65,7 @@ private fun insertDeleteInsertedText(editor: VimEditor, caret: VimCaret): Boolea
|
||||
TextRange(deleteTo, offset),
|
||||
SelectionType.CHARACTER_WISE,
|
||||
false,
|
||||
operatorArguments,
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class InsertDeletePreviousWordAction : ChangeEditorActionHandler.ForEachCaret()
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
return insertDeletePreviousWord(editor, caret)
|
||||
return insertDeletePreviousWord(editor, caret, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ class InsertDeletePreviousWordAction : ChangeEditorActionHandler.ForEachCaret()
|
||||
* @param editor The editor to delete the text from
|
||||
* @return true if able to delete text, false if not
|
||||
*/
|
||||
private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret): Boolean {
|
||||
private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret, operatorArguments: OperatorArguments): Boolean {
|
||||
val deleteTo: Int = if (caret.getBufferPosition().column == 0) {
|
||||
caret.offset - 1
|
||||
} else {
|
||||
@ -74,6 +74,6 @@ private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret): Boolea
|
||||
return false
|
||||
}
|
||||
val range = TextRange(deleteTo, caret.offset)
|
||||
injector.changeGroup.deleteRange(editor, caret, range, SelectionType.CHARACTER_WISE, true)
|
||||
injector.changeGroup.deleteRange(editor, caret, range, SelectionType.CHARACTER_WISE, true, operatorArguments)
|
||||
return true
|
||||
}
|
||||
|
@ -18,10 +18,14 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.ex.ExException
|
||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
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.register.Register
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.vimscript.model.Script
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.INSERT])
|
||||
class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
||||
@ -35,8 +39,9 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
||||
cmd: Command,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
val argument = cmd.argument as? Argument.Character ?: return false
|
||||
if (argument.character == '=') {
|
||||
val argument = cmd.argument
|
||||
|
||||
if (argument?.character == '=') {
|
||||
injector.commandLine.readInputAndProcess(editor, context, "=", finishOn = null) { input ->
|
||||
try {
|
||||
if (input.isNotEmpty()) {
|
||||
@ -45,7 +50,7 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
||||
val textToStore = expression.toInsertableString()
|
||||
injector.registerGroup.storeTextSpecial('=', textToStore)
|
||||
}
|
||||
insertRegister(editor, context, '=')
|
||||
insertRegister(editor, context, '=', operatorArguments)
|
||||
} catch (e: ExException) {
|
||||
injector.messages.indicateError()
|
||||
injector.messages.showStatusBarMessage(editor, e.message)
|
||||
@ -53,7 +58,7 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return insertRegister(editor, context, argument.character)
|
||||
return argument != null && insertRegister(editor, context, argument.character, operatorArguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,13 +72,18 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
||||
* @return true if able to insert the register contents, false if not
|
||||
*/
|
||||
@RWLockLabel.SelfSynchronized
|
||||
private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Char): Boolean {
|
||||
private fun insertRegister(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
key: Char,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
val register: Register? = injector.registerGroup.getRegister(key)
|
||||
if (register != null) {
|
||||
val text = register.rawText ?: injector.parser.toPrintableString(register.keys)
|
||||
val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), register.name)
|
||||
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
|
||||
injector.put.putText(editor, context, putData)
|
||||
injector.put.putText(editor, context, putData, operatorArguments = operatorArguments)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -36,7 +36,7 @@ class VisualBlockAppendAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
if (editor.isOneLineMode()) return false
|
||||
val range = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false
|
||||
return if (range.type == SelectionType.BLOCK_WISE) {
|
||||
injector.changeGroup.initBlockInsert(editor, context, range.toVimTextRange(false), true)
|
||||
injector.changeGroup.blockInsert(editor, context, range.toVimTextRange(false), true, operatorArguments)
|
||||
} else {
|
||||
injector.changeGroup.insertAfterLineEnd(editor, context)
|
||||
true
|
||||
|
@ -36,7 +36,7 @@ class VisualBlockInsertAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
if (editor.isOneLineMode()) return false
|
||||
val vimSelection = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false
|
||||
return if (vimSelection.type == SelectionType.BLOCK_WISE) {
|
||||
injector.changeGroup.initBlockInsert(editor, context, vimSelection.toVimTextRange(false), false)
|
||||
injector.changeGroup.blockInsert(editor, context, vimSelection.toVimTextRange(false), false, operatorArguments)
|
||||
} else {
|
||||
injector.changeGroup.insertBeforeFirstNonBlank(editor, context)
|
||||
true
|
||||
|
@ -52,7 +52,7 @@ sealed class PutTextBaseAction(
|
||||
}
|
||||
result
|
||||
} else {
|
||||
injector.put.putText(editor, context, getPutData(count))
|
||||
injector.put.putText(editor, context, getPutData(count), operatorArguments)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ class ExEntryAction : VimActionHandler.SingleExecution() {
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
if (editor.isOneLineMode()) return false
|
||||
injector.commandLine.createCommandPrompt(editor, context, cmd.rawCount, initialText = "")
|
||||
injector.commandLine.createCommandPrompt(editor, context, cmd, initialText = "")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,7 @@ class InsertRegisterAction: VimActionHandler.SingleExecution() {
|
||||
|
||||
val caretOffset = cmdLine.caret.offset
|
||||
|
||||
val argument = cmd.argument as? Argument.Character ?: return false
|
||||
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
||||
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
|
||||
val pasteContent = if ((keyStroke.modifiers and KeyEvent.CTRL_DOWN_MASK) == 0) {
|
||||
injector.registerGroup.getRegister(keyStroke.keyChar)?.text
|
||||
} else {
|
||||
|
@ -13,7 +13,6 @@ import com.intellij.vim.annotations.Mode
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
@ -28,8 +27,8 @@ class LeaveCommandLineAction : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_READONLY
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
val argument = cmd.argument as? Argument.ExString ?: return true
|
||||
val historyType = VimHistory.Type.getTypeByLabel(argument.label.toString())
|
||||
val argument = cmd.argument ?: return true
|
||||
val historyType = VimHistory.Type.getTypeByLabel(argument.character.toString())
|
||||
injector.historyGroup.addEntry(historyType, argument.string)
|
||||
return true
|
||||
}
|
||||
|
@ -34,9 +34,8 @@ class ProcessExEntryAction : MotionActionHandler.AmbiguousExecution() {
|
||||
override var motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
override fun getMotionActionHandler(argument: Argument?): MotionActionHandler {
|
||||
check(argument is Argument.ExString)
|
||||
if (argument.processing != null) return ExecuteDefinedInputProcessingAction()
|
||||
return if (argument.label == ':') ProcessExCommandEntryAction() else ProcessSearchEntryAction(this)
|
||||
if (argument?.processing != null) return ExecuteDefinedInputProcessingAction()
|
||||
return if (argument?.character == ':') ProcessExCommandEntryAction() else ProcessSearchEntryAction(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +48,7 @@ class ExecuteDefinedInputProcessingAction : MotionActionHandler.SingleExecution(
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.ExString) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
val input = argument.string
|
||||
val processing = argument.processing!!
|
||||
|
||||
@ -63,11 +62,11 @@ class ProcessSearchEntryAction(private val parentAction: ProcessExEntryAction) :
|
||||
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 {
|
||||
if (argument !is Argument.ExString) return Motion.Error
|
||||
val offsetAndMotion = when (argument.label) {
|
||||
if (argument == null) return Motion.Error
|
||||
val offsetAndMotion = when (argument.character) {
|
||||
'/' -> injector.searchGroup.processSearchCommand(editor, argument.string, caret.offset, operatorArguments.count1, Direction.FORWARDS)
|
||||
'?' -> injector.searchGroup.processSearchCommand(editor, argument.string, caret.offset, operatorArguments.count1, Direction.BACKWARDS)
|
||||
else -> throw ExException("Unexpected search label ${argument.label}")
|
||||
else -> throw ExException("Unexpected search label ${argument.character}")
|
||||
}
|
||||
if (offsetAndMotion == null) return Motion.Error
|
||||
parentAction.motionType = offsetAndMotion.second
|
||||
@ -79,7 +78,7 @@ class ProcessExCommandEntryAction : MotionActionHandler.SingleExecution() {
|
||||
override val motionType: MotionType = MotionType.LINE_WISE
|
||||
|
||||
override fun getOffset(editor: VimEditor, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion {
|
||||
if (argument !is Argument.ExString) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
try {
|
||||
// Exit Command-line mode and return to the previous mode before executing the command (this is set to Normal in
|
||||
|
@ -31,7 +31,7 @@ class PlaybackRegisterAction : VimActionHandler.SingleExecution() {
|
||||
cmd: Command,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
val argument = cmd.argument as? Argument.Character ?: return false
|
||||
val argument = cmd.argument ?: return false
|
||||
val reg = argument.character
|
||||
val application = injector.application
|
||||
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
|
||||
injector.macro.lastRegister = reg
|
||||
}
|
||||
} catch (_: ExException) {
|
||||
} catch (e: ExException) {
|
||||
res[0] = false
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class ToggleRecordingAction : VimActionHandler.SingleExecution() {
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
return if (!injector.registerGroup.isRecording) {
|
||||
val argument = cmd.argument as? Argument.Character ?: return false
|
||||
val argument = cmd.argument ?: return false
|
||||
val reg = argument.character
|
||||
injector.registerGroup.startRecording(reg)
|
||||
} else {
|
||||
|
@ -19,21 +19,10 @@ import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.MotionType
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler
|
||||
|
||||
private fun doMotion(
|
||||
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() {
|
||||
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||
class MotionArrowLeftAction : NonShiftedSpecialKeyHandler() {
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
override fun motion(
|
||||
@ -43,38 +32,8 @@ abstract class MotionNonShiftedArrowLeftBaseAction() : NonShiftedSpecialKeyHandl
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
return doMotion(editor, caret, -operatorArguments.count1, "<", allowPastEnd)
|
||||
}
|
||||
|
||||
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)
|
||||
val allowWrap = injector.options(editor).whichwrap.contains("<")
|
||||
val allowEnd = operatorArguments.isOperatorPending // d<Left> deletes \n with wrap enabled
|
||||
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowEnd, allowWrap)
|
||||
}
|
||||
}
|
||||
|
@ -19,23 +19,12 @@ import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.MotionType
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
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.helper.isEndAllowed
|
||||
import com.maddyhome.idea.vim.helper.usesVirtualSpace
|
||||
|
||||
private fun doMotion(
|
||||
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() {
|
||||
@CommandOrMotion(keys = ["<Right>", "<kRight>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||
class MotionArrowRightAction : NonShiftedSpecialKeyHandler() {
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
override fun motion(
|
||||
@ -45,38 +34,9 @@ abstract class MotionNonShiftedArrowRightBaseAction() : NonShiftedSpecialKeyHand
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
return doMotion(editor, caret, operatorArguments.count1, ">", allowPastEnd(editor))
|
||||
}
|
||||
|
||||
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)
|
||||
val allowPastEnd = editor.usesVirtualSpace || editor.isEndAllowed ||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
|
||||
@CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||
open class MotionBackspaceAction(private val allowPastEnd: Boolean = false) : MotionActionHandler.ForEachCaret() {
|
||||
@CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||
class MotionBackspaceAction : MotionActionHandler.ForEachCaret() {
|
||||
override fun getOffset(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
@ -30,15 +30,24 @@ open class MotionBackspaceAction(private val allowPastEnd: Boolean = false) : Mo
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
val allowWrap = injector.options(editor).whichwrap.contains("b")
|
||||
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowPastEnd, allowWrap)
|
||||
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowPastEnd = false, allowWrap)
|
||||
}
|
||||
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
}
|
||||
|
||||
// When the motion is used with an operator, the EOL character is counted.
|
||||
// This allows e.g., `d<BS>` to delete the end of line character on the previous line when wrap is active
|
||||
// ('whichwrap' contains "b")
|
||||
// See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
|
||||
@CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.OP_PENDING])
|
||||
class MotionBackspaceOpPendingModeAction : MotionBackspaceAction(allowPastEnd = true)
|
||||
@CommandOrMotion(keys = ["<Space>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||
class MotionSpaceAction : MotionActionHandler.ForEachCaret() {
|
||||
override fun getOffset(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
context: ExecutionContext,
|
||||
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
|
||||
}
|
||||
|
@ -27,9 +27,13 @@ import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||
import com.maddyhome.idea.vim.helper.isEndAllowed
|
||||
import java.util.*
|
||||
|
||||
abstract class MotionLastColumnBaseAction(private val isMotionForOperator: Boolean = false)
|
||||
: MotionActionHandler.ForEachCaret() {
|
||||
@CommandOrMotion(keys = ["<End>"], modes = [Mode.INSERT])
|
||||
class MotionLastColumnInsertAction : MotionLastColumnAction() {
|
||||
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 fun getOffset(
|
||||
@ -39,26 +43,13 @@ abstract class MotionLastColumnBaseAction(private val isMotionForOperator: Boole
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
val allowPastEnd = if (editor.inVisualMode) {
|
||||
val allow = if (editor.inVisualMode) {
|
||||
injector.options(editor).selection != "old"
|
||||
} else {
|
||||
// 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
|
||||
if (operatorArguments.isOperatorPending) false else editor.isEndAllowed
|
||||
}
|
||||
|
||||
val offset = injector.motion.moveCaretToRelativeLineEnd(editor, caret, operatorArguments.count1 - 1, allowPastEnd)
|
||||
val offset = injector.motion.moveCaretToRelativeLineEnd(editor, caret, operatorArguments.count1 - 1, allow)
|
||||
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)
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
|
||||
abstract class MotionLeftBaseAction(private val allowPastEnd: Boolean) : MotionActionHandler.ForEachCaret() {
|
||||
@CommandOrMotion(keys = ["h"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||
class MotionLeftAction : MotionActionHandler.ForEachCaret() {
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
override fun getOffset(
|
||||
@ -32,16 +33,23 @@ abstract class MotionLeftBaseAction(private val allowPastEnd: Boolean) : MotionA
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
val allowWrap = injector.options(editor).whichwrap.contains("h")
|
||||
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowPastEnd, allowWrap)
|
||||
val allowEnd = operatorArguments.isOperatorPending // dh deletes \n with wrap enabled
|
||||
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowEnd, allowWrap)
|
||||
}
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["h"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||
class MotionLeftAction : MotionLeftBaseAction(allowPastEnd = false)
|
||||
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.INSERT])
|
||||
class MotionLeftInsertModeAction : MotionActionHandler.ForEachCaret() {
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
// When the motion is used with an operator, the EOL character is counted.
|
||||
// This allows e.g., `dh` to delete the end of line character on the previous line when wrap is active
|
||||
// ('whichwrap' contains "h")
|
||||
// See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
|
||||
@CommandOrMotion(keys = ["h"], modes = [Mode.OP_PENDING])
|
||||
class MotionLeftOpPendingModeAction : MotionLeftBaseAction(allowPastEnd = true)
|
||||
override fun getOffset(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
context: ExecutionContext,
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
val allowWrap = injector.options(editor).whichwrap.contains("[")
|
||||
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, true, allowWrap)
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,8 @@ import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.helper.isEndAllowed
|
||||
import com.maddyhome.idea.vim.helper.usesVirtualSpace
|
||||
|
||||
abstract class MotionRightBaseAction() : MotionActionHandler.ForEachCaret() {
|
||||
@CommandOrMotion(keys = ["l"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||
class MotionRightAction : MotionActionHandler.ForEachCaret() {
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
override fun getOffset(
|
||||
@ -34,20 +35,24 @@ abstract class MotionRightBaseAction() : MotionActionHandler.ForEachCaret() {
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
val allowWrap = injector.options(editor).whichwrap.contains("l")
|
||||
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd(editor), allowWrap)
|
||||
val allowEnd = editor.usesVirtualSpace || editor.isEndAllowed ||
|
||||
operatorArguments.isOperatorPending // because of `dl` removing the last character
|
||||
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd = allowEnd, allowWrap)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun allowPastEnd(editor: VimEditor) = editor.usesVirtualSpace || editor.isEndAllowed
|
||||
}
|
||||
@CommandOrMotion(keys = ["<Right>", "<kRight>"], modes = [Mode.INSERT])
|
||||
class MotionRightInsertAction : MotionActionHandler.ForEachCaret() {
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
@CommandOrMotion(keys = ["l"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||
class MotionRightAction : MotionRightBaseAction()
|
||||
|
||||
@CommandOrMotion(keys = ["l"], modes = [Mode.OP_PENDING])
|
||||
class MotionRightOpPendingAction : MotionRightBaseAction() {
|
||||
// When the motion is used with an operator, the EOL character is counted.
|
||||
// This allows e.g., `dl` 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
|
||||
override fun getOffset(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
context: ExecutionContext,
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
val allowWrap = injector.options(editor).whichwrap.contains("]")
|
||||
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd = true, allowWrap)
|
||||
}
|
||||
}
|
||||
|
@ -18,19 +18,23 @@ import com.maddyhome.idea.vim.api.moveToMotion
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.handler.ShiftedArrowKeyHandler
|
||||
|
||||
/**
|
||||
* @author Alex Plate
|
||||
*/
|
||||
|
||||
@CommandOrMotion(keys = ["<S-Left>"], modes = [Mode.INSERT, Mode.NORMAL, Mode.VISUAL, Mode.SELECT])
|
||||
class MotionShiftArrowLeftAction : ShiftedArrowKeyHandler(true) {
|
||||
class MotionShiftLeftAction : ShiftedArrowKeyHandler(true) {
|
||||
|
||||
override val type: Command.Type = Command.Type.OTHER_READONLY
|
||||
|
||||
override fun motionWithKeyModel(editor: VimEditor, caret: VimCaret, context: ExecutionContext, cmd: Command) {
|
||||
val motion = injector.motion.getHorizontalMotion(editor, caret, -cmd.count, true)
|
||||
caret.moveToMotion(motion)
|
||||
val vertical = injector.motion.getHorizontalMotion(editor, caret, -cmd.count, true)
|
||||
caret.moveToMotion(vertical)
|
||||
}
|
||||
|
||||
override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||
val caret = editor.currentCaret()
|
||||
val motion = injector.motion.findOffsetOfNextWord(editor, caret.offset, -cmd.count, false)
|
||||
caret.moveToMotion(motion)
|
||||
val newOffset = injector.motion.findOffsetOfNextWord(editor, caret.offset, -cmd.count, false)
|
||||
caret.moveToMotion(newOffset)
|
||||
}
|
||||
}
|
@ -16,21 +16,28 @@ import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.moveToMotion
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.ShiftedArrowKeyHandler
|
||||
|
||||
/**
|
||||
* @author Alex Plate
|
||||
*/
|
||||
|
||||
@CommandOrMotion(keys = ["<S-Right>"], modes = [Mode.INSERT, Mode.NORMAL, Mode.VISUAL, Mode.SELECT])
|
||||
class MotionShiftArrowRightAction : ShiftedArrowKeyHandler(true) {
|
||||
class MotionShiftRightAction : ShiftedArrowKeyHandler(true) {
|
||||
|
||||
override val type: Command.Type = Command.Type.OTHER_READONLY
|
||||
|
||||
override fun motionWithKeyModel(editor: VimEditor, caret: VimCaret, context: ExecutionContext, cmd: Command) {
|
||||
val motion = injector.motion.getHorizontalMotion(editor, caret, cmd.count, true)
|
||||
caret.moveToMotion(motion)
|
||||
val vertical = injector.motion.getHorizontalMotion(editor, caret, cmd.count, true)
|
||||
caret.moveToMotion(vertical)
|
||||
}
|
||||
|
||||
override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||
val caret = editor.currentCaret()
|
||||
val motion = injector.motion.findOffsetOfNextWord(editor, caret.offset, cmd.count, false)
|
||||
caret.moveToMotion(motion)
|
||||
val newOffset = injector.motion.findOffsetOfNextWord(editor, caret.offset, cmd.count, false)
|
||||
if (newOffset is Motion.AbsoluteOffset) {
|
||||
caret.moveToOffset(newOffset.offset)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.action.motion.leftright
|
||||
|
||||
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.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.options
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.MotionType
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
|
||||
@CommandOrMotion(keys = ["<Space>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||
open class MotionSpaceAction(private val allowPastEnd: Boolean = false) : MotionActionHandler.ForEachCaret() {
|
||||
override fun getOffset(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
context: ExecutionContext,
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
val allowWrap = injector.options(editor).whichwrap.contains("s")
|
||||
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd, allowWrap)
|
||||
}
|
||||
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["<Space>"], modes = [Mode.OP_PENDING])
|
||||
class MotionSpaceOpPendingModeAction : MotionSpaceAction(allowPastEnd = true)
|
@ -15,12 +15,15 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.MotionType
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.handler.toMotionOrError
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import java.util.*
|
||||
|
||||
enum class TillCharacterMotionType {
|
||||
LAST_F,
|
||||
@ -48,6 +51,9 @@ sealed class TillCharacterMotion(
|
||||
private val finishBeforeCharacter: Boolean,
|
||||
) : MotionActionHandler.ForEachCaret() {
|
||||
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
|
||||
|
||||
override val motionType: MotionType =
|
||||
if (direction == Direction.BACKWARDS) MotionType.EXCLUSIVE else MotionType.INCLUSIVE
|
||||
|
||||
@ -58,7 +64,7 @@ sealed class TillCharacterMotion(
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
val res = if (finishBeforeCharacter) {
|
||||
injector.motion
|
||||
.moveCaretToBeforeNextCharacterOnLine(
|
||||
|
@ -37,7 +37,7 @@ class MotionGotoFileMarkAction : MotionActionHandler.ForEachCaret() {
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
val mark = argument.character
|
||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||
@ -57,7 +57,7 @@ class MotionGotoFileMarkNoSaveJumpAction : MotionActionHandler.ForEachCaret() {
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
val mark = argument.character
|
||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||
|
@ -37,7 +37,7 @@ class MotionGotoFileMarkLineAction : MotionActionHandler.ForEachCaret() {
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
val mark = argument.character
|
||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||
@ -57,7 +57,7 @@ class MotionGotoFileMarkLineNoSaveJumpAction : MotionActionHandler.ForEachCaret(
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
val mark = argument.character
|
||||
return injector.motion.moveCaretToMark(caret, mark, true)
|
||||
|
@ -37,7 +37,7 @@ class MotionGotoMarkAction : MotionActionHandler.ForEachCaret() {
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
val mark = argument.character
|
||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||
@ -57,7 +57,7 @@ class MotionGotoMarkNoSaveJumpAction : MotionActionHandler.ForEachCaret() {
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
val mark = argument.character
|
||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||
|
@ -37,7 +37,7 @@ class MotionGotoMarkLineAction : MotionActionHandler.ForEachCaret() {
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
val mark = argument.character
|
||||
return injector.motion.moveCaretToMark(caret, mark, true)
|
||||
@ -57,7 +57,7 @@ class MotionGotoMarkLineNoSaveJumpAction : MotionActionHandler.ForEachCaret() {
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Motion {
|
||||
if (argument !is Argument.Character) return Motion.Error
|
||||
if (argument == null) return Motion.Error
|
||||
|
||||
val mark = argument.character
|
||||
return injector.motion.moveCaretToMark(caret, mark, true)
|
||||
|
@ -24,6 +24,7 @@ class MotionMarkAction : VimActionHandler.SingleExecution() {
|
||||
override val argumentType: Argument.Type = Argument.Type.CHARACTER
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
return cmd.argument.let { it is Argument.Character && injector.markService.setMark(editor, it.character) }
|
||||
val argument = cmd.argument
|
||||
return argument != null && injector.markService.setMark(editor, argument.character)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.MotionType
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.handler.toMotion
|
||||
@ -29,7 +28,7 @@ import com.maddyhome.idea.vim.options.OptionConstants
|
||||
*/
|
||||
|
||||
@CommandOrMotion(keys = ["<Left>"], modes = [Mode.SELECT])
|
||||
class SelectMotionArrowLeftAction : MotionActionHandler.ForEachCaret() {
|
||||
class SelectMotionLeftAction : MotionActionHandler.ForEachCaret() {
|
||||
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
@ -59,6 +58,6 @@ class SelectMotionArrowLeftAction : MotionActionHandler.ForEachCaret() {
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val logger = vimLogger<SelectMotionArrowLeftAction>()
|
||||
private val logger = injector.getLogger(SelectMotionLeftAction::class.java)
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.MotionType
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.handler.toMotion
|
||||
@ -29,7 +28,7 @@ import com.maddyhome.idea.vim.options.OptionConstants
|
||||
*/
|
||||
|
||||
@CommandOrMotion(keys = ["<Right>"], modes = [Mode.SELECT])
|
||||
class SelectMotionArrowRightAction : MotionActionHandler.ForEachCaret() {
|
||||
class SelectMotionRightAction : MotionActionHandler.ForEachCaret() {
|
||||
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
|
||||
@ -59,6 +58,6 @@ class SelectMotionArrowRightAction : MotionActionHandler.ForEachCaret() {
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val logger = vimLogger<SelectMotionArrowRightAction>()
|
||||
private val logger = injector.getLogger(SelectMotionRightAction::class.java)
|
||||
}
|
||||
}
|
@ -37,26 +37,10 @@ interface VimChangeGroup {
|
||||
|
||||
fun initInsert(editor: VimEditor, context: ExecutionContext, mode: Mode)
|
||||
|
||||
/**
|
||||
* Enter Insert mode for block selection.
|
||||
*
|
||||
* Given a [TextRange] representing a block selection, position the primary caret either at the start column of the
|
||||
* selection for insert, or the end of the first line for append. Then set the insert repeat counts for the extent of
|
||||
* the block selection and start Insert mode.
|
||||
*
|
||||
* @param editor The Vim editor instance.
|
||||
* @param context The execution context.
|
||||
* @param range The range of text representing the block selection.
|
||||
* @param append Whether to insert before the range, or append after it.
|
||||
* @return True if the block was successfully inserted, false otherwise.
|
||||
*/
|
||||
fun initBlockInsert(editor: VimEditor, context: ExecutionContext, range: TextRange, append: Boolean): Boolean
|
||||
|
||||
fun processEscape(editor: VimEditor, context: ExecutionContext?)
|
||||
fun processEscape(editor: VimEditor, context: ExecutionContext?, operatorArguments: OperatorArguments)
|
||||
|
||||
fun processEnter(editor: VimEditor, caret: VimCaret, context: ExecutionContext)
|
||||
fun processEnter(editor: VimEditor, context: ExecutionContext)
|
||||
fun processBackspace(editor: VimEditor, context: ExecutionContext)
|
||||
|
||||
fun processPostChangeModeSwitch(editor: VimEditor, context: ExecutionContext, toSwitch: Mode)
|
||||
|
||||
@ -74,7 +58,13 @@ interface VimChangeGroup {
|
||||
|
||||
fun deleteEndOfLine(editor: VimEditor, caret: VimCaret, count: Int, operatorArguments: OperatorArguments): Boolean
|
||||
|
||||
fun deleteJoinLines(editor: VimEditor, caret: VimCaret, count: Int, spaces: Boolean): Boolean
|
||||
fun deleteJoinLines(
|
||||
editor: VimEditor,
|
||||
caret: VimCaret,
|
||||
count: Int,
|
||||
spaces: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean
|
||||
|
||||
fun processKey(editor: VimEditor, key: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
|
||||
|
||||
@ -102,6 +92,7 @@ interface VimChangeGroup {
|
||||
range: TextRange,
|
||||
type: SelectionType?,
|
||||
isChange: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
saveToRegister: Boolean = true,
|
||||
): Boolean
|
||||
fun changeCharacters(editor: VimEditor, caret: VimCaret, operatorArguments: OperatorArguments): Boolean
|
||||
@ -121,6 +112,8 @@ interface VimChangeGroup {
|
||||
|
||||
fun changeCaseToggleCharacter(editor: VimEditor, caret: VimCaret, count: Int): Boolean
|
||||
|
||||
fun blockInsert(editor: VimEditor, context: ExecutionContext, range: TextRange, append: Boolean, operatorArguments: OperatorArguments): Boolean
|
||||
|
||||
fun changeCaseRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: ChangeCaseType): Boolean
|
||||
|
||||
fun changeRange(
|
||||
@ -129,6 +122,7 @@ interface VimChangeGroup {
|
||||
range: TextRange,
|
||||
type: SelectionType,
|
||||
context: ExecutionContext,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean
|
||||
|
||||
fun changeCaseMotion(editor: VimEditor, caret: VimCaret, context: ExecutionContext?, type: ChangeCaseType, argument: Argument, operatorArguments: OperatorArguments): Boolean
|
||||
@ -193,6 +187,7 @@ interface VimChangeGroup {
|
||||
context: ExecutionContext,
|
||||
count: Int,
|
||||
started: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
)
|
||||
|
||||
fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char)
|
||||
|
@ -15,6 +15,7 @@ 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.common.ChangesListener
|
||||
import com.maddyhome.idea.vim.common.OperatedRange
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
@ -23,7 +24,6 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||
import com.maddyhome.idea.vim.helper.NumberType
|
||||
@ -111,6 +111,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
TextRange(caret.offset, endOffset.offset),
|
||||
SelectionType.CHARACTER_WISE,
|
||||
caret,
|
||||
operatorArguments,
|
||||
)
|
||||
val pos = caret.offset
|
||||
val norm = editor.normalizeOffset(caret.getBufferPosition().line, pos, isChange)
|
||||
@ -161,21 +162,21 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
range: TextRange,
|
||||
type: SelectionType?,
|
||||
caret: VimCaret,
|
||||
operatorArguments: OperatorArguments,
|
||||
saveToRegister: Boolean = true,
|
||||
): Boolean {
|
||||
var updatedRange = range
|
||||
|
||||
// Fix for https://youtrack.jetbrains.net/issue/VIM-35
|
||||
if (!range.normalize(editor.fileSize().toInt())) {
|
||||
updatedRange = if (range.startOffset == range.endOffset
|
||||
&& range.startOffset == editor.fileSize().toInt()
|
||||
&& range.startOffset != 0) {
|
||||
updatedRange = if (range.startOffset == range.endOffset && range.startOffset == editor.fileSize()
|
||||
.toInt() && range.startOffset != 0
|
||||
) {
|
||||
TextRange(range.startOffset - 1, range.endOffset)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
val mode = editor.mode
|
||||
val mode = operatorArguments.mode
|
||||
if (type == null ||
|
||||
(mode == Mode.INSERT || mode == Mode.REPLACE) ||
|
||||
!saveToRegister ||
|
||||
@ -238,10 +239,11 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
count: Int,
|
||||
operatorArguments: OperatorArguments,
|
||||
) {
|
||||
val myLastStrokes = lastStrokes ?: return
|
||||
for (caret in editor.nativeCarets()) {
|
||||
repeat(count) {
|
||||
for (i in 0 until count) {
|
||||
for (lastStroke in myLastStrokes) {
|
||||
when (lastStroke) {
|
||||
is NativeAction -> {
|
||||
@ -250,7 +252,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
}
|
||||
|
||||
is EditorActionHandlerBase -> {
|
||||
injector.actionExecutor.executeVimAction(editor, lastStroke, context, OperatorArguments(0, editor.mode))
|
||||
injector.actionExecutor.executeVimAction(editor, lastStroke, context, operatorArguments)
|
||||
strokes.add(lastStroke)
|
||||
}
|
||||
|
||||
@ -279,6 +281,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
context: ExecutionContext,
|
||||
count: Int,
|
||||
started: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
) {
|
||||
for (caret in editor.nativeCarets()) {
|
||||
if (repeatLines > 0) {
|
||||
@ -299,17 +302,17 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
val updatedCount = if (started) (if (i == 0) count else count + 1) else count
|
||||
if (repeatColumn >= VimMotionGroupBase.LAST_COLUMN) {
|
||||
caret.moveToOffset(injector.motion.moveCaretToLineEnd(editor, bufferLine + i, true))
|
||||
repeatInsertText(editor, context, updatedCount)
|
||||
repeatInsertText(editor, context, updatedCount, operatorArguments)
|
||||
} else if (editor.getVisualLineLength(visualLine + i) >= repeatColumn) {
|
||||
val visualPosition = VimVisualPosition(visualLine + i, repeatColumn, false)
|
||||
val inlaysCount = injector.engineEditorHelper.amountOfInlaysBeforeVisualPosition(editor, visualPosition)
|
||||
caret.moveToVisualPosition(VimVisualPosition(visualLine + i, repeatColumn + inlaysCount, false))
|
||||
repeatInsertText(editor, context, updatedCount)
|
||||
repeatInsertText(editor, context, updatedCount, operatorArguments)
|
||||
}
|
||||
}
|
||||
caret.moveToOffset(position)
|
||||
} else {
|
||||
repeatInsertText(editor, context, count)
|
||||
repeatInsertText(editor, context, count, operatorArguments)
|
||||
val position = injector.motion.getHorizontalMotion(editor, caret, -1, false)
|
||||
caret.moveToMotion(position)
|
||||
}
|
||||
@ -327,7 +330,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
val oldFragmentLength = oldFragment.length
|
||||
|
||||
// Repeat buffer limits
|
||||
if (repeatCharsCount > MAX_REPEAT_CHARS_COUNT) {
|
||||
if (repeatCharsCount > Companion.MAX_REPEAT_CHARS_COUNT) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -348,7 +351,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
if (oldFragmentLength > 0) {
|
||||
val editorDelete = injector.nativeActionManager.deleteAction
|
||||
if (editorDelete != null) {
|
||||
repeat(oldFragmentLength) {
|
||||
for (i in 0 until oldFragmentLength) {
|
||||
strokes.add(editorDelete)
|
||||
}
|
||||
}
|
||||
@ -367,7 +370,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
val motionName = if (delta < 0) "VimMotionLeftAction" else "VimMotionRightAction"
|
||||
val action = injector.actionExecutor.findVimAction(motionName)!!
|
||||
val count = abs(delta)
|
||||
repeat(count) {
|
||||
for (i in 0 until count) {
|
||||
positionCaretActions.add(action)
|
||||
}
|
||||
return positionCaretActions
|
||||
@ -446,8 +449,25 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
if (mode == Mode.REPLACE) {
|
||||
editor.insertMode = false
|
||||
}
|
||||
val count = if (cmd.flags.contains(CommandFlags.FLAG_NO_REPEAT_INSERT)) 1 else cmd.count
|
||||
repeatInsert(editor, context, count, false)
|
||||
if (cmd.flags.contains(CommandFlags.FLAG_NO_REPEAT_INSERT)) {
|
||||
val commandState = injector.vimState
|
||||
repeatInsert(
|
||||
editor,
|
||||
context,
|
||||
1,
|
||||
false,
|
||||
OperatorArguments(false, 1, commandState.mode),
|
||||
)
|
||||
} else {
|
||||
val commandState = injector.vimState
|
||||
repeatInsert(
|
||||
editor,
|
||||
context,
|
||||
cmd.count,
|
||||
false,
|
||||
OperatorArguments(false, cmd.count, commandState.mode),
|
||||
)
|
||||
}
|
||||
if (mode == Mode.REPLACE) {
|
||||
editor.insertMode = true
|
||||
}
|
||||
@ -512,9 +532,9 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
exit: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
) {
|
||||
repeatInsertText(editor, context, 1)
|
||||
repeatInsertText(editor, context, 1, operatorArguments)
|
||||
if (exit) {
|
||||
editor.exitInsertMode(context)
|
||||
editor.exitInsertMode(context, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
||||
@ -524,7 +544,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
*
|
||||
* DEPRECATED. Please, don't use this function directly. Use ModeHelper.exitInsertMode in file ModeExtensions.kt
|
||||
*/
|
||||
override fun processEscape(editor: VimEditor, context: ExecutionContext?) {
|
||||
override fun processEscape(editor: VimEditor, context: ExecutionContext?, operatorArguments: OperatorArguments) {
|
||||
// Get the offset for marks before we exit insert mode - switching from insert to overtype subtracts one from the
|
||||
// column offset.
|
||||
val markGroup = injector.markService
|
||||
@ -533,24 +553,17 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
if (editor.mode is Mode.REPLACE) {
|
||||
editor.insertMode = true
|
||||
}
|
||||
val repeatCount0 = lastInsert?.let {
|
||||
// How many times do we want to *repeat* the insert? For a simple insert or change action, this is count-1. But if
|
||||
// the command is an operator+motion, then the count applies to the motion, not the insert/change. I.e., `2cw`
|
||||
// changes two words, rather than inserting the change twice. This is the only place where we need to know who the
|
||||
// count applies to
|
||||
if (CommandFlags.FLAG_NO_REPEAT_INSERT in it.flags || it.action.argumentType == Argument.Type.MOTION) {
|
||||
0
|
||||
} else {
|
||||
it.count - 1
|
||||
var cnt = if (lastInsert != null) lastInsert!!.count else 0
|
||||
if (lastInsert != null && lastInsert!!.flags.contains(CommandFlags.FLAG_NO_REPEAT_INSERT)) {
|
||||
cnt = 1
|
||||
}
|
||||
} ?: 0
|
||||
if (vimDocument != null && vimDocumentListener != null) {
|
||||
vimDocument!!.removeChangeListener(vimDocumentListener!!)
|
||||
vimDocumentListener = null
|
||||
}
|
||||
lastStrokes = ArrayList(strokes)
|
||||
if (context != null) {
|
||||
injector.changeGroup.repeatInsert(editor, context, repeatCount0, true)
|
||||
injector.changeGroup.repeatInsert(editor, context, if (cnt == 0) 0 else cnt - 1, true, operatorArguments)
|
||||
}
|
||||
if (editor.mode is Mode.INSERT) {
|
||||
updateLastInsertedTextRegister()
|
||||
@ -664,7 +677,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
val rangeToDelete = TextRange(startOffset, offset)
|
||||
editor.nativeCarets().filter { it != caret && rangeToDelete.contains(it.offset) }
|
||||
.forEach { editor.removeCaret(it) }
|
||||
val res = deleteText(editor, rangeToDelete, SelectionType.CHARACTER_WISE, caret)
|
||||
val res = deleteText(editor, rangeToDelete, SelectionType.CHARACTER_WISE, caret, operatorArguments)
|
||||
if (editor.usesVirtualSpace) {
|
||||
caret.moveToOffset(startOffset)
|
||||
} else {
|
||||
@ -691,6 +704,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
caret: VimCaret,
|
||||
count: Int,
|
||||
spaces: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
var myCount = count
|
||||
if (myCount < 2) myCount = 2
|
||||
@ -699,7 +713,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
return if (lline + myCount > total) {
|
||||
false
|
||||
} else {
|
||||
deleteJoinNLines(editor, caret, lline, myCount, spaces)
|
||||
deleteJoinNLines(editor, caret, lline, myCount, spaces, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
||||
@ -717,14 +731,12 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
): Boolean {
|
||||
logger.debug { "processKey($key)" }
|
||||
if (key.keyChar != KeyEvent.CHAR_UNDEFINED) {
|
||||
editor.replaceMask?.recordChangeAtCaret(editor)
|
||||
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, key.keyChar) }
|
||||
return true
|
||||
}
|
||||
|
||||
// Shift-space
|
||||
if (key.keyCode == 32 && key.modifiers and KeyEvent.SHIFT_DOWN_MASK != 0) {
|
||||
editor.replaceMask?.recordChangeAtCaret(editor)
|
||||
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, ' ') }
|
||||
return true
|
||||
}
|
||||
@ -772,7 +784,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
logger.debug("offset=$offset")
|
||||
}
|
||||
if (offset != -1) {
|
||||
val res = deleteText(editor, TextRange(start, offset), SelectionType.LINE_WISE, caret)
|
||||
val res = deleteText(editor, TextRange(start, offset), SelectionType.LINE_WISE, caret, operatorArguments)
|
||||
if (res && caret.offset >= editor.fileSize() && caret.offset != 0) {
|
||||
caret.moveToOffset(
|
||||
injector.motion.moveCaretToRelativeLineStartSkipLeading(
|
||||
@ -795,7 +807,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
lline + count <= total
|
||||
}
|
||||
if (!allowedExecution) return false
|
||||
repeat(executions) {
|
||||
for (i in 0 until executions) {
|
||||
val joinLinesAction = injector.nativeActionManager.joinLines
|
||||
if (joinLinesAction != null) {
|
||||
injector.actionExecutor.executeAction(editor, joinLinesAction, context)
|
||||
@ -825,7 +837,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
val endLine = editor.offsetToBufferPosition(range.endOffset).line
|
||||
var count = endLine - startLine + 1
|
||||
if (count < 2) count = 2
|
||||
return deleteJoinNLines(editor, caret, startLine, count, spaces)
|
||||
return deleteJoinNLines(editor, caret, startLine, count, spaces, operatorArguments)
|
||||
}
|
||||
|
||||
override fun joinViaIdeaBySelections(
|
||||
@ -862,25 +874,28 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
isChange: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Pair<TextRange, SelectionType>? {
|
||||
check(argument is Argument.Motion) { "Unexpected argument: $argument" }
|
||||
|
||||
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments) ?: return null
|
||||
var motionType = argument.getMotionType()
|
||||
|
||||
// Delete motion commands that are not linewise become linewise if all the following are true:
|
||||
// 1) The range is across multiple lines
|
||||
// 2) There is only whitespace before the start of the range
|
||||
// 3) There is only whitespace after the end of the range
|
||||
if (!isChange && motionType != SelectionType.LINE_WISE) {
|
||||
var type: SelectionType = if (argument.motion.isLinewiseMotion()) {
|
||||
SelectionType.LINE_WISE
|
||||
} else {
|
||||
SelectionType.CHARACTER_WISE
|
||||
}
|
||||
val motion = argument.motion
|
||||
if (!isChange && !motion.isLinewiseMotion()) {
|
||||
val start = editor.offsetToBufferPosition(range.startOffset)
|
||||
val end = editor.offsetToBufferPosition(range.endOffset)
|
||||
if (start.line != end.line
|
||||
&& !editor.anyNonWhitespace(range.startOffset, -1)
|
||||
&& !editor.anyNonWhitespace(range.endOffset, 1)) {
|
||||
motionType = SelectionType.LINE_WISE
|
||||
if (start.line != end.line) {
|
||||
if (!editor.anyNonWhitespace(range.startOffset, -1) && !editor.anyNonWhitespace(range.endOffset, 1)) {
|
||||
type = SelectionType.LINE_WISE
|
||||
}
|
||||
}
|
||||
return Pair(range, motionType)
|
||||
}
|
||||
return Pair(range, type)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -900,12 +915,13 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
range: TextRange,
|
||||
type: SelectionType?,
|
||||
isChange: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
saveToRegister: Boolean,
|
||||
): Boolean {
|
||||
val intendedColumn = caret.vimLastColumn
|
||||
|
||||
val removeLastNewLine = removeLastNewLine(editor, range, type)
|
||||
val res = deleteText(editor, range, type, caret, saveToRegister)
|
||||
val res = deleteText(editor, range, type, caret, operatorArguments, saveToRegister)
|
||||
var processedCaret = editor.findLastVersionOfCaret(caret) ?: caret
|
||||
if (removeLastNewLine) {
|
||||
val textLength = editor.fileSize().toInt()
|
||||
@ -1024,6 +1040,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
startLine: Int,
|
||||
count: Int,
|
||||
spaces: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
// Don't move the caret until we've successfully deleted text. If we're on the last line, we don't want to move the
|
||||
// caret and then be unable to delete
|
||||
@ -1040,7 +1057,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
return i > 1
|
||||
}
|
||||
// Note that caret isn't moved here; it's only used for register + mark storage
|
||||
deleteText(editor, TextRange(startOffset, endOffset), null, caret)
|
||||
deleteText(editor, TextRange(startOffset, endOffset), null, caret, operatorArguments)
|
||||
if (spaces && !hasTrailingWhitespace) {
|
||||
insertText(editor, caret, startOffset, " ")
|
||||
}
|
||||
@ -1127,8 +1144,8 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
): Boolean {
|
||||
var count0 = operatorArguments.count0
|
||||
// Vim treats cw as ce and cW as cE if cursor is on a non-blank character
|
||||
var motionArgument = argument as? Argument.Motion ?: return false
|
||||
val id = motionArgument.motion.id
|
||||
val motion = argument.motion
|
||||
val id = motion.action.id
|
||||
var kludge = false
|
||||
val bigWord = id == VIM_MOTION_BIG_WORD_RIGHT
|
||||
val chars = editor.text()
|
||||
@ -1138,7 +1155,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
val charType = charType(editor, chars[offset], bigWord)
|
||||
if (charType !== CharacterHelper.CharacterType.WHITESPACE) {
|
||||
val lastWordChar = offset >= fileSize - 1 || charType(editor, chars[offset + 1], bigWord) !== charType
|
||||
if (wordMotions.contains(id) && lastWordChar && operatorArguments.count1 == 1) {
|
||||
if (wordMotions.contains(id) && lastWordChar && motion.count == 1) {
|
||||
val res = deleteCharacter(editor, caret, 1, true, operatorArguments)
|
||||
if (res) {
|
||||
editor.vimChangeActionSwitchMode = Mode.INSERT
|
||||
@ -1148,50 +1165,49 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
when (id) {
|
||||
VIM_MOTION_WORD_RIGHT -> {
|
||||
kludge = true
|
||||
motionArgument = Argument.Motion(
|
||||
injector.actionExecutor.findVimActionOrDie(VIM_MOTION_WORD_END_RIGHT) as MotionActionHandler,
|
||||
motionArgument.argument
|
||||
)
|
||||
motion.action = injector.actionExecutor.findVimActionOrDie(VIM_MOTION_WORD_END_RIGHT)
|
||||
}
|
||||
|
||||
VIM_MOTION_BIG_WORD_RIGHT -> {
|
||||
kludge = true
|
||||
motionArgument = Argument.Motion(
|
||||
injector.actionExecutor.findVimActionOrDie(VIM_MOTION_BIG_WORD_END_RIGHT) as MotionActionHandler,
|
||||
motionArgument.argument
|
||||
)
|
||||
motion.action = injector.actionExecutor.findVimActionOrDie(VIM_MOTION_BIG_WORD_END_RIGHT)
|
||||
}
|
||||
|
||||
VIM_MOTION_CAMEL_RIGHT -> {
|
||||
kludge = true
|
||||
motionArgument = Argument.Motion(
|
||||
injector.actionExecutor.findVimActionOrDie(VIM_MOTION_CAMEL_END_RIGHT) as MotionActionHandler,
|
||||
motionArgument.argument
|
||||
)
|
||||
motion.action = injector.actionExecutor.findVimActionOrDie(VIM_MOTION_CAMEL_END_RIGHT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kludge) {
|
||||
val pos1 = injector.searchHelper.findNextWordEnd(editor, offset, operatorArguments.count1, bigWord, false)
|
||||
val pos2 = injector.searchHelper.findNextWordEnd(editor, pos1, -operatorArguments.count1, bigWord, false)
|
||||
val cnt = operatorArguments.count1 * motion.count
|
||||
val pos1 = injector.searchHelper.findNextWordEnd(editor, offset, cnt, bigWord, false)
|
||||
val pos2 = injector.searchHelper.findNextWordEnd(editor, pos1, -cnt, bigWord, false)
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("pos=$offset")
|
||||
logger.debug("pos1=$pos1")
|
||||
logger.debug("pos2=$pos2")
|
||||
logger.debug("count=" + operatorArguments.count1)
|
||||
logger.debug("arg.count=" + motion.count)
|
||||
}
|
||||
if (pos2 == offset && operatorArguments.count1 > 1) {
|
||||
if (pos2 == offset) {
|
||||
if (operatorArguments.count1 > 1) {
|
||||
count0--
|
||||
} else if (motion.count > 1) {
|
||||
motion.rawCount = motion.count - 1
|
||||
} else {
|
||||
motion.flags = EnumSet.noneOf(CommandFlags::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
val (first, second) = getDeleteRangeAndType(
|
||||
editor,
|
||||
caret,
|
||||
context,
|
||||
motionArgument,
|
||||
argument,
|
||||
true,
|
||||
operatorArguments.copy(count0 = count0),
|
||||
operatorArguments.withCount0(count0),
|
||||
) ?: return false
|
||||
return changeRange(
|
||||
editor,
|
||||
@ -1199,6 +1215,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
first,
|
||||
second,
|
||||
context,
|
||||
operatorArguments,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1223,6 +1240,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
* @param caret The caret to be moved after range deletion
|
||||
* @param range The range to change
|
||||
* @param type The type of the range
|
||||
* @param operatorArguments
|
||||
* @return true if able to delete the range, false if not
|
||||
*/
|
||||
override fun changeRange(
|
||||
@ -1231,6 +1249,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
range: TextRange,
|
||||
type: SelectionType,
|
||||
context: ExecutionContext,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
var col = 0
|
||||
var lines = 0
|
||||
@ -1243,7 +1262,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
}
|
||||
val after = range.endOffset >= editor.fileSize()
|
||||
val lp = editor.offsetToBufferPosition(injector.motion.moveCaretToCurrentLineStartSkipLeading(editor, caret))
|
||||
val res = deleteRange(editor, caret, range, type, true)
|
||||
val res = deleteRange(editor, caret, range, type, true, operatorArguments)
|
||||
val updatedCaret = editor.findLastVersionOfCaret(caret) ?: caret
|
||||
if (res) {
|
||||
if (type === SelectionType.LINE_WISE) {
|
||||
@ -1905,7 +1924,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
pos++
|
||||
}
|
||||
if (pos > wsoff) {
|
||||
deleteText(editor, TextRange(wsoff, pos), null, caret, true)
|
||||
deleteText(editor, TextRange(wsoff, pos), null, caret, operatorArguments, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1937,21 +1956,23 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
}
|
||||
}
|
||||
|
||||
override fun initBlockInsert(
|
||||
override fun blockInsert(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
range: TextRange,
|
||||
append: Boolean,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
val lines = getLinesCountInVisualBlock(editor, range)
|
||||
val startPosition = editor.offsetToBufferPosition(range.startOffset)
|
||||
// Note that when called, we're likely to have moved from Visual (block) to Normal, which means all secondary carets
|
||||
// will have been removed. Even if not, this would move them all to the same location, which would remove them and
|
||||
// leave only the primary caret.
|
||||
val mode = operatorArguments.mode
|
||||
val visualBlockMode = mode is Mode.VISUAL && mode.selectionType === SelectionType.BLOCK_WISE
|
||||
for (caret in editor.carets()) {
|
||||
val line = startPosition.line
|
||||
var column = startPosition.column
|
||||
if (append) {
|
||||
if (!visualBlockMode) {
|
||||
column = 0
|
||||
} else if (append) {
|
||||
column += range.maxLength
|
||||
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
|
||||
column = VimMotionGroupBase.LAST_COLUMN
|
||||
@ -1963,10 +1984,18 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
val offset = editor.getLineEndOffset(line)
|
||||
insertText(editor, caret, offset, pad)
|
||||
}
|
||||
if (visualBlockMode || !append) {
|
||||
caret.moveToInlayAwareOffset(editor.bufferPositionToOffset(BufferPosition(line, column)))
|
||||
}
|
||||
if (visualBlockMode) {
|
||||
setInsertRepeat(lines, column, append)
|
||||
}
|
||||
}
|
||||
if (visualBlockMode || !append) {
|
||||
insertBeforeCursor(editor, context)
|
||||
} else {
|
||||
insertAfterCursor(editor, context)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -2039,3 +2068,9 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
||||
VimChangeGroup.ChangeCaseType.UPPER -> Character.toUpperCase(ch)
|
||||
}
|
||||
}
|
||||
|
||||
fun OperatedRange.toType(): SelectionType = when (this) {
|
||||
is OperatedRange.Characters -> SelectionType.CHARACTER_WISE
|
||||
is OperatedRange.Lines -> SelectionType.LINE_WISE
|
||||
is OperatedRange.Block -> SelectionType.BLOCK_WISE
|
||||
}
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
package com.maddyhome.idea.vim.api
|
||||
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
|
||||
interface VimCommandLineService {
|
||||
fun isCommandLineSupported(editor: VimEditor): Boolean
|
||||
|
||||
@ -24,7 +26,7 @@ interface VimCommandLineService {
|
||||
* @param initialText The initial text for the entry
|
||||
*/
|
||||
fun createSearchPrompt(editor: VimEditor, context: ExecutionContext, label: String, initialText: String): VimCommandLine
|
||||
fun createCommandPrompt(editor: VimEditor, context: ExecutionContext, count0: Int, initialText: String): VimCommandLine
|
||||
fun createCommandPrompt(editor: VimEditor, context: ExecutionContext, command: Command, initialText: String): VimCommandLine
|
||||
|
||||
@Deprecated("Please use ModalInputService.create()")
|
||||
fun createWithoutShortcuts(editor: VimEditor, context: ExecutionContext, label: String, initText: String): VimCommandLine
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
package com.maddyhome.idea.vim.api
|
||||
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.ex.ExException
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
||||
@ -49,15 +50,15 @@ abstract class VimCommandLineServiceBase : VimCommandLineService {
|
||||
return createCommandLinePrompt(editor, context, removeSelections = false, label, initialText)
|
||||
}
|
||||
|
||||
override fun createCommandPrompt(editor: VimEditor, context: ExecutionContext, count0: Int, initialText: String): VimCommandLine {
|
||||
val rangeText = getRange(editor, count0)
|
||||
override fun createCommandPrompt(editor: VimEditor, context: ExecutionContext, command: Command, initialText: String): VimCommandLine {
|
||||
val rangeText = getRange(editor, command)
|
||||
return createCommandLinePrompt(editor, context, removeSelections = true, label = ":", rangeText + initialText)
|
||||
}
|
||||
|
||||
protected fun getRange(editor: VimEditor, count0: Int) = when {
|
||||
protected fun getRange(editor: VimEditor, cmd: Command) = when {
|
||||
editor.inVisualMode -> "'<,'>"
|
||||
count0 == 1 -> "."
|
||||
count0 > 1 -> ".,.+" + (count0 - 1)
|
||||
cmd.rawCount == 1 -> "."
|
||||
cmd.rawCount > 1 -> ".,.+" + (cmd.count - 1)
|
||||
else -> ""
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.api
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
@ -129,7 +128,6 @@ interface VimEditor {
|
||||
val lfMakesNewLine: Boolean
|
||||
var vimChangeActionSwitchMode: Mode?
|
||||
val indentConfig: VimIndentConfig
|
||||
var replaceMask: VimEditorReplaceMask?
|
||||
|
||||
fun fileSize(): Long
|
||||
|
||||
@ -246,9 +244,7 @@ interface VimEditor {
|
||||
// Can be used as a key to store something for specific project
|
||||
val projectId: String
|
||||
|
||||
@Deprecated("Use overload without OperatorArguments", replaceWith = ReplaceWith("exitInsertMode(context)"))
|
||||
fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) { exitInsertMode(context) }
|
||||
fun exitInsertMode(context: ExecutionContext)
|
||||
fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments)
|
||||
fun exitSelectModeNative(adjustCaret: Boolean)
|
||||
|
||||
var vimLastSelectionType: SelectionType?
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
package com.maddyhome.idea.vim.api
|
||||
|
||||
import com.maddyhome.idea.vim.common.forgetAllReplaceMasks
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
|
||||
abstract class VimEditorBase : VimEditor {
|
||||
@ -19,9 +18,6 @@ abstract class VimEditorBase : VimEditor {
|
||||
if (vimState.mode == value) return
|
||||
|
||||
val oldValue = vimState.mode
|
||||
if (oldValue == Mode.REPLACE) {
|
||||
forgetAllReplaceMasks()
|
||||
}
|
||||
updateMode(value)
|
||||
injector.listenersNotifier.notifyModeChanged(this, oldValue)
|
||||
}
|
||||
|
@ -10,17 +10,17 @@ package com.maddyhome.idea.vim.api
|
||||
import com.maddyhome.idea.vim.action.change.LazyVimCommand
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.key.KeyMapping
|
||||
import com.maddyhome.idea.vim.key.KeyMappingLayer
|
||||
import com.maddyhome.idea.vim.key.MappingInfo
|
||||
import com.maddyhome.idea.vim.key.MappingOwner
|
||||
import com.maddyhome.idea.vim.key.RootNode
|
||||
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
interface VimKeyGroup {
|
||||
fun getKeyRoot(mappingMode: MappingMode): RootNode<LazyVimCommand>
|
||||
fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand>
|
||||
fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer
|
||||
fun getActions(editor: VimEditor, keyStroke: KeyStroke): List<NativeAction>
|
||||
fun getKeymapConflicts(keyStroke: KeyStroke): List<NativeAction>
|
||||
|
@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.action.change.LazyVimCommand
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.key.KeyMapping
|
||||
import com.maddyhome.idea.vim.key.KeyMappingLayer
|
||||
import com.maddyhome.idea.vim.key.MappingInfo
|
||||
@ -29,7 +30,7 @@ abstract class VimKeyGroupBase : VimKeyGroup {
|
||||
@JvmField
|
||||
val myShortcutConflicts: MutableMap<KeyStroke, ShortcutOwnerInfo> = LinkedHashMap()
|
||||
val requiredShortcutKeys: MutableSet<RequiredShortcut> = HashSet(300)
|
||||
val keyRoots: MutableMap<MappingMode, RootNode<LazyVimCommand>> = EnumMap(MappingMode::class.java)
|
||||
val keyRoots: MutableMap<MappingMode, CommandPartNode<LazyVimCommand>> = EnumMap(MappingMode::class.java)
|
||||
val keyMappings: MutableMap<MappingMode, KeyMapping> = EnumMap(MappingMode::class.java)
|
||||
|
||||
override fun removeKeyMapping(modes: Set<MappingMode>, keys: List<KeyStroke>) {
|
||||
@ -62,7 +63,7 @@ abstract class VimKeyGroupBase : VimKeyGroup {
|
||||
* @param mappingMode The mapping mode
|
||||
* @return The key mapping tree root
|
||||
*/
|
||||
override fun getKeyRoot(mappingMode: MappingMode): RootNode<LazyVimCommand> = keyRoots.getOrPut(mappingMode) { RootNode(mappingMode.name.get(0).lowercase()) }
|
||||
override fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand> = keyRoots.getOrPut(mappingMode) { RootNode() }
|
||||
|
||||
override fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer = getKeyMapping(mode)
|
||||
|
||||
|
@ -15,7 +15,6 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.Graphemes
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.group.findMatchingPairOnCurrentLine
|
||||
import com.maddyhome.idea.vim.handler.ExternalActionHandler
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
@ -117,9 +116,9 @@ abstract class VimMotionGroupBase : VimMotionGroup {
|
||||
val text = editor.text()
|
||||
val oldOffset = caret.offset
|
||||
var current = oldOffset
|
||||
repeat(count.absoluteValue) {
|
||||
for (i in 0 until count.absoluteValue) {
|
||||
val newOffset = if (count > 0) Graphemes.next(text, current) else Graphemes.prev(text, current)
|
||||
current = newOffset ?: return@repeat
|
||||
current = newOffset ?: break
|
||||
}
|
||||
|
||||
val offset = if (allowWrap) {
|
||||
@ -314,51 +313,50 @@ abstract class VimMotionGroupBase : VimMotionGroup {
|
||||
argument: Argument,
|
||||
operatorArguments: OperatorArguments,
|
||||
): TextRange? {
|
||||
if (argument !is Argument.Motion) {
|
||||
throw RuntimeException("Unexpected argument passed to getMotionRange2: $argument")
|
||||
}
|
||||
|
||||
var start: Int
|
||||
var end: Int
|
||||
|
||||
val action = argument.motion
|
||||
when (action) {
|
||||
is MotionActionHandler -> {
|
||||
if (argument.type === Argument.Type.OFFSETS) {
|
||||
val offsets = argument.offsets[caret] ?: 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
|
||||
val cmdAction = cmd.action
|
||||
if (cmdAction is MotionActionHandler) {
|
||||
// This is where we are now
|
||||
start = caret.offset
|
||||
|
||||
// Execute the motion (without moving the cursor) and get where we end
|
||||
val motion = action.getHandlerOffset(editor, caret, context, argument.argument, operatorArguments)
|
||||
if (Motion.Error == motion || Motion.NoMotion == motion) return null
|
||||
val motion =
|
||||
cmdAction.getHandlerOffset(editor, caret, context, cmd.argument, operatorArguments.withCount0(raw))
|
||||
|
||||
// Invalid motion
|
||||
if (Motion.Error == motion) return null
|
||||
if (Motion.NoMotion == motion) return null
|
||||
end = (motion as AbsoluteOffset).offset
|
||||
|
||||
// If inclusive, add the last character to the range
|
||||
if (action.motionType === MotionType.INCLUSIVE) {
|
||||
if (cmdAction.motionType === MotionType.INCLUSIVE) {
|
||||
if (start > end) {
|
||||
if (start < editor.fileSize()) start++
|
||||
} else {
|
||||
if (end < editor.fileSize()) end++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is TextObjectActionHandler -> {
|
||||
val range: TextRange = action.getRange(editor, caret, context, operatorArguments.count1, operatorArguments.count0)
|
||||
} else if (cmdAction is TextObjectActionHandler) {
|
||||
val range: TextRange = cmdAction.getRange(editor, caret, context, cnt, raw)
|
||||
?: return null
|
||||
start = range.startOffset
|
||||
end = range.endOffset
|
||||
if (argument.isLinewiseMotion()) end--
|
||||
}
|
||||
|
||||
is ExternalActionHandler -> {
|
||||
val range: TextRange = action.getRange(caret) ?: return null
|
||||
start = range.startOffset
|
||||
end = range.endOffset
|
||||
if (argument.isLinewiseMotion()) end--
|
||||
}
|
||||
|
||||
else -> throw RuntimeException("Commands doesn't take " + action.javaClass.simpleName + " as an operator")
|
||||
if (cmd.isLinewiseMotion()) end--
|
||||
} else {
|
||||
throw RuntimeException(
|
||||
"Commands doesn't take " + cmdAction.javaClass.simpleName + " as an operator",
|
||||
)
|
||||
}
|
||||
|
||||
// Normalize the range
|
||||
@ -370,7 +368,7 @@ abstract class VimMotionGroupBase : VimMotionGroup {
|
||||
|
||||
// If we are a linewise motion we need to normalize the start and stop then move the start to the beginning
|
||||
// of the line and move the end to the end of the line.
|
||||
if (argument.isLinewiseMotion()) {
|
||||
if (cmd.isLinewiseMotion()) {
|
||||
if (caret.getBufferPosition().line != editor.lineCount() - 1) {
|
||||
start = editor.getLineStartForOffset(start)
|
||||
end = min((editor.getLineEndForOffset(end) + 1).toLong(), editor.fileSize()).toInt()
|
||||
@ -379,19 +377,19 @@ abstract class VimMotionGroupBase : VimMotionGroup {
|
||||
end = editor.getLineEndForOffset(end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a kludge for dw, dW, and d[w. Without this kludge, an extra newline is operated when it shouldn't be.
|
||||
val text = editor.text().subSequence(start, end).toString()
|
||||
val lastNewLine = text.lastIndexOf('\n')
|
||||
if (lastNewLine > 0) {
|
||||
val id = action.id
|
||||
val id = argument.motion.action.id
|
||||
if (id == "VimMotionWordRightAction" || id == "VimMotionBigWordRightAction" || id == "VimMotionCamelRightAction") {
|
||||
if (!editor.anyNonWhitespace(end, -1)) {
|
||||
end = start + lastNewLine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TextRange(start, end)
|
||||
}
|
||||
|
||||
|
@ -8,88 +8,51 @@
|
||||
|
||||
package com.maddyhome.idea.vim.command
|
||||
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.handler.ExternalActionHandler
|
||||
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.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Represents an argument to a command's action
|
||||
*
|
||||
* A [Command] is made up of an optional register and count, and an action. That action might be a simple command such
|
||||
* as `i` to start Insert mode, or a motion `w` to move to the next word. Or it might require an argument, such as a
|
||||
* character like in the motion `fx` or an ex-string in the command `d/foo`. Or it might be another action, representing
|
||||
* a motion, such as `dw`. That motion argument's action might itself have an action (`dfx`).
|
||||
* This represents a command argument.
|
||||
* TODO please make it a sealed class and not a giant collection of fields with default values, it's not safe
|
||||
*/
|
||||
sealed class Argument {
|
||||
/** A simple character argument */
|
||||
class Character(val character: Char) : Argument()
|
||||
class Argument private constructor(
|
||||
val character: Char = 0.toChar(),
|
||||
val motion: Command = EMPTY_COMMAND,
|
||||
val offsets: Map<ImmutableVimCaret, VimSelection> = emptyMap(),
|
||||
val string: String = "",
|
||||
val processing: ((String) -> Unit)? = null,
|
||||
val type: Type,
|
||||
) {
|
||||
constructor(motionArg: Command) : this(motion = motionArg, type = Type.MOTION)
|
||||
constructor(charArg: Char) : this(character = charArg, type = Type.CHARACTER)
|
||||
constructor(label: Char, strArg: String, processing: ((String) -> Unit)?) : this(character = label, string = strArg, processing = processing, type = Type.EX_STRING)
|
||||
constructor(offsets: Map<ImmutableVimCaret, VimSelection>) : this(offsets = offsets, type = Type.OFFSETS)
|
||||
|
||||
/** An argument representing the user's input from the Ex command line, typically a search string */
|
||||
class ExString(val label: Char, val string: String, val processing: ((String) -> Unit)?) : Argument()
|
||||
|
||||
/**
|
||||
* Represents an argument that is a motion. Used by operator commands
|
||||
*
|
||||
* A command is either an action (like `i`), a motion (like `w`) or an operator that takes a motion as an argument
|
||||
* (like `dw`). A motion argument is a motion action handler with its own optional argument. The motion action handler
|
||||
* could be a [MotionActionHandler] or [TextObjectActionHandler], or even the [ExternalActionHandler] that tracks the
|
||||
* caret moves from an external action such as EasyMotion/AceJump. A motion might be a simple motion such as `w` to
|
||||
* move a word, or require a character argument (`f`), or even an ex-string (`/foo`).
|
||||
*
|
||||
* Note that a motion argument does not have a count - that is owned by the fully built command. When executing the
|
||||
* command, the count applies to the motion action, not the operator action. This just means the operator action
|
||||
* does not use the count. (`3i` means insert the following typed text three times. But `3cw` means change the next
|
||||
* three words with the following inserted text, rather than change the next word by inserting the following text
|
||||
* three times.)
|
||||
*
|
||||
* @see Command
|
||||
*/
|
||||
class Motion private constructor(val motion: EditorActionHandlerBase, val argument: Argument? = null) : Argument() {
|
||||
constructor(motion: MotionActionHandler, argument: Argument?) : this(motion as EditorActionHandlerBase, argument)
|
||||
constructor(motion: TextObjectActionHandler) : this(motion as EditorActionHandlerBase)
|
||||
constructor(motion: ExternalActionHandler) : this (motion as EditorActionHandlerBase)
|
||||
|
||||
fun getMotionType() = if (isLinewiseMotion()) SelectionType.LINE_WISE else SelectionType.CHARACTER_WISE
|
||||
|
||||
fun isLinewiseMotion(): Boolean {
|
||||
return motion.let {
|
||||
when (it) {
|
||||
is TextObjectActionHandler -> it.visualType == TextObjectVisualType.LINE_WISE
|
||||
is MotionActionHandler -> it.motionType == MotionType.LINE_WISE
|
||||
is ExternalActionHandler -> it.isLinewiseMotion
|
||||
else -> error("Command is not a motion: $motion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun withArgument(argument: Argument) = Motion(motion, argument)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the type of argument, or the type of an expected argument while entering a command
|
||||
*/
|
||||
enum class Type {
|
||||
MOTION, CHARACTER, DIGRAPH, EX_STRING, OFFSETS
|
||||
}
|
||||
|
||||
/**
|
||||
* A motion argument used to complete an operator, such as `dw` or `diw`
|
||||
*
|
||||
* A motion argument will often have its own argument, such as when deleting up to the next occurrence of a
|
||||
* character, as in `dfx`.
|
||||
*/
|
||||
MOTION,
|
||||
companion object {
|
||||
@JvmField
|
||||
val EMPTY_COMMAND: Command = Command(
|
||||
0,
|
||||
object : MotionActionHandler.SingleExecution() {
|
||||
override fun getOffset(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
argument: Argument?,
|
||||
operatorArguments: OperatorArguments,
|
||||
) = Motion.NoMotion
|
||||
|
||||
/** A character argument, such as the character to move to with the `f` command. */
|
||||
CHARACTER,
|
||||
|
||||
/**
|
||||
* Used to represent an expected argument type rather than an actual argument type
|
||||
*
|
||||
* When building a command, an operator can say that it expects a digraph or literal argument, in which case the key
|
||||
* handler will allow `<C-K>`, `<C-V>` and `<C-Q>`, and start the digraph state machine. The finished digraph is
|
||||
* converted into a character, and a character argument is added to the operator action.
|
||||
*/
|
||||
DIGRAPH
|
||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||
},
|
||||
Command.Type.MOTION,
|
||||
EnumSet.noneOf(CommandFlags::class.java),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -8,47 +8,34 @@
|
||||
|
||||
package com.maddyhome.idea.vim.command
|
||||
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This represents a single Vim command to be executed (action, motion, operator+motion, v_textobject, etc.)
|
||||
*
|
||||
* A command is an action, with a type that determines how it is handled, such as [Type.MOTION], [Type.CHANGE],
|
||||
* [Type.OTHER_SELF_SYNCHRONIZED], etc. It also exposes the action's [CommandFlags] which are also used to help execute
|
||||
* the action.
|
||||
*
|
||||
* A command's action can require an argument, which can be either a character (e.g., `fx`) or the input from the Ex
|
||||
* command line. It can also be a motion, in which case the command is an operator+motion, such as `dw`. The motion
|
||||
* argument is an action that might also have an argument, such as `dfx` or `d/foo`.
|
||||
*
|
||||
* A command can optionally include a count and a register. More than one count can be entered, before an operator and
|
||||
* then before the motion argument, e.g. `2d3w`. This is intuitively "delete the next three words, twice", which is the
|
||||
* same as "delete the next six words". While both the operator and motion have a count while being built, the final
|
||||
* command has a single count that is the product of all count components. In this example, the command would have a
|
||||
* final count of `6`.
|
||||
*
|
||||
* Note that for a command that is an operator+motion command, the count applies to the motion, rather than the
|
||||
* operator. For example, `3i` will insert the following typed text three times, while `3cw` will change the next three
|
||||
* words with the following typed text, rather than changing the next word with the typed text three times. The command
|
||||
* still has a single count, and to handle this, the operator action should ignore the count, while the motion action
|
||||
* should use it when calculating the movement.
|
||||
*
|
||||
* As an additional interesting pathological edge case, it's possible to enter a count when selecting a register, and
|
||||
* it's possible to select multiple registers while building a command; the last register wins. This means that
|
||||
* `2"a3"b4"c5d6w` will delete 720 words and store the text in register `c`.
|
||||
*
|
||||
* @see OperatorArguments
|
||||
* This represents a single Vim command to be executed (operator, motion, text object, etc.). It may optionally include
|
||||
* an argument if appropriate for the command. The command has a count and a type.
|
||||
*/
|
||||
data class Command(
|
||||
val register: Char?,
|
||||
val rawCount: Int,
|
||||
val action: EditorActionHandlerBase,
|
||||
val argument: Argument?,
|
||||
var rawCount: Int,
|
||||
var action: EditorActionHandlerBase,
|
||||
val type: Type,
|
||||
val flags: EnumSet<CommandFlags>,
|
||||
var flags: EnumSet<CommandFlags>,
|
||||
) {
|
||||
|
||||
constructor(rawCount: Int, register: Char) : this(
|
||||
rawCount,
|
||||
NonExecutableActionHandler,
|
||||
Type.SELECT_REGISTER,
|
||||
EnumSet.of(CommandFlags.FLAG_EXPECT_MORE),
|
||||
) {
|
||||
this.register = register
|
||||
}
|
||||
|
||||
init {
|
||||
action.process(this)
|
||||
}
|
||||
@ -56,7 +43,20 @@ data class Command(
|
||||
val count: Int
|
||||
get() = rawCount.coerceAtLeast(1)
|
||||
|
||||
override fun toString() = "Action = ${action.id}"
|
||||
var argument: Argument? = null
|
||||
var register: Char? = null
|
||||
|
||||
fun isLinewiseMotion(): Boolean {
|
||||
return when (action) {
|
||||
is TextObjectActionHandler -> (action as TextObjectActionHandler).visualType == TextObjectVisualType.LINE_WISE
|
||||
is MotionActionHandler -> (action as MotionActionHandler).motionType == MotionType.LINE_WISE
|
||||
else -> error("Command is not a motion: $action")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Action = ${action.id}"
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
/**
|
||||
@ -85,6 +85,10 @@ data class Command(
|
||||
COPY,
|
||||
PASTE,
|
||||
|
||||
/**
|
||||
* Represents commands that select the register.
|
||||
*/
|
||||
SELECT_REGISTER,
|
||||
OTHER_READONLY,
|
||||
OTHER_WRITABLE,
|
||||
|
||||
@ -108,3 +112,18 @@ data class Command(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object NonExecutableActionHandler : EditorActionHandlerBase(false) {
|
||||
override val type: Command.Type
|
||||
get() = error("This action should not be executed")
|
||||
|
||||
override fun baseExecute(
|
||||
editor: VimEditor,
|
||||
caret: VimCaret,
|
||||
context: ExecutionContext,
|
||||
cmd: Command,
|
||||
operatorArguments: OperatorArguments,
|
||||
): Boolean {
|
||||
error("This action should not be executed")
|
||||
}
|
||||
}
|
||||
|
@ -15,78 +15,66 @@ import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.trace
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.handler.ExternalActionHandler
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
import com.maddyhome.idea.vim.helper.noneOfEnum
|
||||
import com.maddyhome.idea.vim.key.CommandNode
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.key.Node
|
||||
import com.maddyhome.idea.vim.key.RootNode
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
class CommandBuilder private constructor(
|
||||
class CommandBuilder(
|
||||
private var currentCommandPartNode: CommandPartNode<LazyVimCommand>,
|
||||
private val counts: MutableList<Int>,
|
||||
private val commandParts: ArrayDeque<Command>,
|
||||
private val keyList: MutableList<KeyStroke>,
|
||||
initialUncommittedRawCount: Int,
|
||||
) : Cloneable {
|
||||
|
||||
constructor(rootNode: RootNode<LazyVimCommand>, initialUncommittedRawCount: Int = 0)
|
||||
: this(rootNode, mutableListOf(initialUncommittedRawCount), mutableListOf())
|
||||
constructor(
|
||||
currentCommandPartNode: CommandPartNode<LazyVimCommand>,
|
||||
initialUncommittedRawCount: Int = 0
|
||||
) : this(
|
||||
currentCommandPartNode,
|
||||
ArrayDeque(),
|
||||
mutableListOf(),
|
||||
initialUncommittedRawCount
|
||||
)
|
||||
|
||||
private var commandState: CurrentCommandState = CurrentCommandState.NEW_COMMAND
|
||||
private var selectedRegister: Char? = null
|
||||
private var action: EditorActionHandlerBase? = null
|
||||
private var argument: Argument? = null
|
||||
private var fallbackArgumentType: Argument.Type? = null
|
||||
|
||||
private val motionArgument
|
||||
get() = argument as? Argument.Motion
|
||||
|
||||
private var currentCount: Int
|
||||
get() = counts.last()
|
||||
set(value) {
|
||||
counts[counts.size - 1] = value
|
||||
}
|
||||
|
||||
/** Provide the typed keys for `'showcmd'` */
|
||||
val keys: Iterable<KeyStroke> get() = keyList
|
||||
|
||||
/** Returns true if the command builder is clean and ready to start building */
|
||||
val isEmpty
|
||||
get() = commandState == CurrentCommandState.NEW_COMMAND
|
||||
&& selectedRegister == null
|
||||
&& counts.size == 1
|
||||
&& action == null
|
||||
&& argument == null
|
||||
&& fallbackArgumentType == null
|
||||
|
||||
/** Returns true if the command is ready to be built and executed */
|
||||
val isReady
|
||||
get() = commandState == CurrentCommandState.READY
|
||||
var commandState: CurrentCommandState = CurrentCommandState.NEW_COMMAND
|
||||
|
||||
/**
|
||||
* Returns the current total count, as the product of all entered count components. The value is not coerced.
|
||||
* The current uncommitted count for the currently in-progress command part
|
||||
*
|
||||
* This value is not reliable! Please use [Command.rawCount] or [Command.count] instead of this function.
|
||||
* TODO: Investigate usages. This value cannot be trusted
|
||||
* TODO: Rename to uncommittedRawCount
|
||||
*
|
||||
* This value is a snapshot of the count for a currently in-progress command, and should not be used for anything
|
||||
* other than reporting on the state of the command. This value is likely to change as the user continues entering the
|
||||
* command. There are very few expected uses of this value. Examples include calculating `'incsearch'` highlighting
|
||||
* for an in-progress search command, or the `v:count` and `v:count1` variables used during an expression mapping.
|
||||
* This value is not coerced, and can be 0.
|
||||
*
|
||||
* The returned value is the product of all count components. In other words, given a command that is an
|
||||
* operator+motion, both the operator and motion can have a count, such as `2d3w`, which means delete the next six
|
||||
* words. Furthermore, Vim allows a count when selecting register, and it is valid to select register multiple times.
|
||||
* E.g., `2"a3"b4"c5d6w` will delete the next 720 words and save the text to the register `c`.
|
||||
* There are very few reasons for using this value. It is incomplete (the user could type another digit), and there
|
||||
* can be other committed command parts, such as operator and multiple register selections, each of which will can a
|
||||
* count (e.g., `2"a3"b4"c5d6` waiting for a motion). The count is only final after [buildCommand], and then only via
|
||||
* [Command.count] or [Command.rawCount].
|
||||
*
|
||||
* The returned value is not coerced. If no count components are specified, the returned value is 0. If any components
|
||||
* are specified, the value will naturally be greater than 0.
|
||||
* The [aggregatedUncommittedCount] property can be used to get the current total count across all command parts,
|
||||
* although this value is also not guaranteed to be final.
|
||||
*/
|
||||
fun calculateCount0Snapshot(): Int {
|
||||
return if (counts.all { it == 0 }) 0 else counts.map { it.coerceAtLeast(1) }.reduce { acc, i -> acc * i }
|
||||
}
|
||||
var count: Int = initialUncommittedRawCount
|
||||
private set
|
||||
|
||||
/**
|
||||
* The current aggregated, but uncommitted count for all command parts in the command builder, coerced to 1
|
||||
*
|
||||
* This value multiplies together the count for command parts currently committed, such as operator and multiple
|
||||
* register selections, as well as the current uncommitted count for the next command part. E.g., `2"a3"b4"c5d6` will
|
||||
* multiply each count together to get what would be the final count. All counts are coerced to at least 1 before
|
||||
* multiplying, which means the result will also be at least 1.
|
||||
*
|
||||
* Note that there are very few uses for this value. The final value should be retrieved from [Command.count] or
|
||||
* [Command.rawCount] after a call to [buildCommand]. This value is expected to be used for `'incsearch'`
|
||||
* highlighting.
|
||||
*/
|
||||
val aggregatedUncommittedCount: Int
|
||||
get() = (commandParts.map { it.count }.reduceOrNull { acc, i -> acc * i } ?: 1) * count.coerceAtLeast(1)
|
||||
|
||||
val keys: Iterable<KeyStroke> get() = keyList
|
||||
|
||||
// TODO: Try to remove this. We shouldn't be looking at the unbuilt command
|
||||
// This is used by the extension mapping handler, to select the current register before invoking the extension. We
|
||||
@ -96,18 +84,7 @@ class CommandBuilder private constructor(
|
||||
// still change, if more keys are processed. E.g., it's perfectly valid to select register multiple times `"a"b`.
|
||||
// This doesn't cause any issues with existing extensions
|
||||
val register: Char?
|
||||
get() = selectedRegister
|
||||
|
||||
// TODO: Try to remove this too. Also used by extension handling
|
||||
fun hasCurrentCommandPartArgument() = motionArgument != null || argument != null
|
||||
|
||||
// TODO: And remove this too. More extension special case code
|
||||
// It's used by the Matchit extension to incorrectly reset the command builder. Extensions need a way to properly
|
||||
// handle the command builder. I.e., they should act like expression mappings, which return keys to evaluate, or an
|
||||
// empty string to leave state as it is - either way, it's an explicit choice. Currently, extensions mostly ignore it
|
||||
fun resetCount() {
|
||||
counts[counts.size - 1] = 0
|
||||
}
|
||||
get() = commandParts.lastOrNull { it.register != null }?.register
|
||||
|
||||
/**
|
||||
* The argument type for the current in-progress command part's action
|
||||
@ -115,202 +92,79 @@ class CommandBuilder private constructor(
|
||||
* For digraph arguments, this can fall back to [Argument.Type.CHARACTER] if there isn't a digraph match.
|
||||
*/
|
||||
val expectedArgumentType: Argument.Type?
|
||||
get() = fallbackArgumentType
|
||||
?: motionArgument?.let { return it.motion.argumentType }
|
||||
?: action?.argumentType
|
||||
get() = fallbackArgumentType ?: commandParts.lastOrNull()?.action?.argumentType
|
||||
|
||||
/**
|
||||
* Returns true if the command builder is waiting for an argument
|
||||
*
|
||||
* The command builder might be waiting for the argument to a simple motion action such as `f`, waiting for a
|
||||
* character to move to, or it might be waiting for the argument to a motion that is itself an argument to an operator
|
||||
* argument. For example, the character argument to `f` in `df{character}`.
|
||||
*/
|
||||
val isAwaitingArgument: Boolean
|
||||
get() = expectedArgumentType != null && (motionArgument?.let { it.argument == null } ?: (argument == null))
|
||||
private var fallbackArgumentType: Argument.Type? = null
|
||||
|
||||
val isReady: Boolean get() = commandState == CurrentCommandState.READY
|
||||
val isEmpty: Boolean get() = commandParts.isEmpty()
|
||||
val isAtDefaultState: Boolean get() = isEmpty && count == 0 && expectedArgumentType == null
|
||||
|
||||
val isExpectingCount: Boolean
|
||||
get() {
|
||||
return commandState == CurrentCommandState.NEW_COMMAND &&
|
||||
expectedArgumentType != Argument.Type.CHARACTER &&
|
||||
expectedArgumentType != Argument.Type.DIGRAPH
|
||||
}
|
||||
|
||||
fun pushCommandPart(action: EditorActionHandlerBase) {
|
||||
logger.trace { "pushCommandPart is executed. action = $action" }
|
||||
commandParts.add(Command(count, action, action.type, action.flags))
|
||||
fallbackArgumentType = null
|
||||
count = 0
|
||||
}
|
||||
|
||||
fun pushCommandPart(register: Char) {
|
||||
logger.trace { "pushCommandPart is executed. register = $register" }
|
||||
// We will never execute this command, but we need to push something to correctly handle counts on either side of a
|
||||
// select register command part. e.g. 2"a2d2w or even crazier 2"a2"a2"a2"a2"a2d2w
|
||||
commandParts.add(Command(count, register))
|
||||
fallbackArgumentType = null
|
||||
count = 0
|
||||
}
|
||||
|
||||
fun fallbackToCharacterArgument() {
|
||||
logger.trace("fallbackToCharacterArgument is executed")
|
||||
logger.trace { "fallbackToCharacterArgument is executed" }
|
||||
// Finished handling DIGRAPH. We either succeeded, in which case handle the converted character, or failed to parse,
|
||||
// in which case try to handle input as a character argument.
|
||||
assert(expectedArgumentType == Argument.Type.DIGRAPH) { "Cannot move state from $expectedArgumentType to CHARACTER" }
|
||||
fallbackArgumentType = Argument.Type.CHARACTER
|
||||
}
|
||||
|
||||
fun isAwaitingCharOrDigraphArgument(): Boolean {
|
||||
val awaiting = expectedArgumentType == Argument.Type.CHARACTER || expectedArgumentType == Argument.Type.DIGRAPH
|
||||
logger.debug { "Awaiting char or digraph: $awaiting" }
|
||||
return awaiting
|
||||
fun addKey(key: KeyStroke) {
|
||||
logger.trace { "added key to command builder" }
|
||||
keyList.add(key)
|
||||
}
|
||||
|
||||
val isExpectingCount: Boolean
|
||||
get() {
|
||||
return commandState == CurrentCommandState.NEW_COMMAND &&
|
||||
!isRegisterPending &&
|
||||
expectedArgumentType != Argument.Type.CHARACTER &&
|
||||
expectedArgumentType != Argument.Type.DIGRAPH
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user has typed some count characters
|
||||
*
|
||||
* Used to know if `0` should be mapped or not. Vim allows "0" to be mapped, but not while entering a count. Also used
|
||||
* to know if there are count characters available to delete.
|
||||
*/
|
||||
fun hasCountCharacters() = currentCount > 0
|
||||
|
||||
fun addCountCharacter(key: KeyStroke) {
|
||||
currentCount = (currentCount * 10) + (key.keyChar - '0')
|
||||
count = (count * 10) + (key.keyChar - '0')
|
||||
// If count overflows and flips negative, reset to 999999999L. In Vim, count is a long, which is *usually* 32 bits,
|
||||
// so will flip at 2147483648. We store count as an Int, which is also 32 bit.
|
||||
// See https://github.com/vim/vim/blob/b376ace1aeaa7614debc725487d75c8f756dd773/src/normal.c#L631
|
||||
if (currentCount < 0) {
|
||||
currentCount = 999999999
|
||||
if (count < 0) {
|
||||
count = 999999999
|
||||
}
|
||||
addKey(key)
|
||||
}
|
||||
|
||||
fun deleteCountCharacter() {
|
||||
currentCount /= 10
|
||||
count /= 10
|
||||
keyList.removeAt(keyList.size - 1)
|
||||
}
|
||||
|
||||
var isRegisterPending: Boolean = false
|
||||
private set
|
||||
|
||||
fun startWaitingForRegister(key: KeyStroke) {
|
||||
isRegisterPending = true
|
||||
addKey(key)
|
||||
fun setCurrentCommandPartNode(newNode: CommandPartNode<LazyVimCommand>) {
|
||||
logger.trace { "setCurrentCommandPartNode is executed" }
|
||||
currentCommandPartNode = newNode
|
||||
}
|
||||
|
||||
fun selectRegister(register: Char) {
|
||||
logger.trace { "Selected register '$register'" }
|
||||
selectedRegister = register
|
||||
isRegisterPending = false
|
||||
fallbackArgumentType = null
|
||||
counts.add(0)
|
||||
fun getChildNode(key: KeyStroke): Node<LazyVimCommand>? {
|
||||
return currentCommandPartNode[key]
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a keystroke to the command builder
|
||||
*
|
||||
* Only public use is when entering a digraph/literal, where each key isn't handled by [CommandBuilder], but should
|
||||
* be added to the `'showcmd'` output.
|
||||
*/
|
||||
fun addKey(key: KeyStroke) {
|
||||
logger.trace { "added key to command builder: $key" }
|
||||
keyList.add(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an action to the command
|
||||
*
|
||||
* This can be an action such as delete the current character - `x`, a motion like `w`, an operator like `d` or a
|
||||
* motion that will be used as the argument of an operator - the `w` in `dw`.
|
||||
*/
|
||||
fun addAction(action: EditorActionHandlerBase) {
|
||||
logger.trace { "addAction is executed. action = $action" }
|
||||
|
||||
if (this.action == null) {
|
||||
this.action = action
|
||||
}
|
||||
else {
|
||||
StrictMode.assert(argument == null, "Command builder already has an action and a fully populated argument")
|
||||
argument = when (action) {
|
||||
is MotionActionHandler -> Argument.Motion(action, null)
|
||||
is TextObjectActionHandler -> Argument.Motion(action)
|
||||
is ExternalActionHandler -> Argument.Motion(action)
|
||||
else -> throw RuntimeException("Unexpected action type: $action")
|
||||
}
|
||||
}
|
||||
|
||||
// Push a new count component, so we get an extra count for e.g. an operator's motion
|
||||
counts.add(0)
|
||||
fallbackArgumentType = null
|
||||
|
||||
if (!isAwaitingArgument) {
|
||||
logger.trace("Action does not require an argument. Setting command state to READY")
|
||||
commandState = CurrentCommandState.READY
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an argument to the command
|
||||
*
|
||||
* This might be a simple character argument, such as `x` in `fx`, or an ex-string argument to a search motion, like
|
||||
* `d/foo`. If the command is an operator+motion, the motion is both an action and an argument. While it is simpler
|
||||
* to use [addAction], it will still work if the motion action can also be wrapped in an [Argument.Motion] and passed
|
||||
* to [addArgument].
|
||||
*/
|
||||
fun addArgument(argument: Argument) {
|
||||
logger.trace("addArgument is executed")
|
||||
|
||||
// If the command's action is an operator, the argument will be a motion, which might be waiting for its argument.
|
||||
// If so, update the motion argument to include the given argument
|
||||
this.argument = motionArgument?.withArgument(argument) ?: argument
|
||||
|
||||
fallbackArgumentType = null
|
||||
|
||||
if (!isAwaitingArgument) {
|
||||
logger.trace("Argument is simple type, or motion with own argument. No further argument required. Setting command state to READY")
|
||||
commandState = CurrentCommandState.READY
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a keystroke, matching an action if available
|
||||
*
|
||||
* If the given keystroke matches an action, the [processor] is invoked with the action instance. Typically, the
|
||||
* caller will end up passing the action back to [addAction], but there are more housekeeping steps that stop us
|
||||
* encapsulating it completely.
|
||||
*
|
||||
* If the given keystroke does not yet match an action, the internal state is updated to track the current command
|
||||
* part node.
|
||||
*/
|
||||
fun processKey(key: KeyStroke, processor: (EditorActionHandlerBase) -> Unit): Boolean {
|
||||
val node = currentCommandPartNode[key]
|
||||
when (node) {
|
||||
is CommandNode -> {
|
||||
logger.trace { "Found full command node ($key) - ${node.debugString}" }
|
||||
addKey(key)
|
||||
processor(node.actionHolder.instance)
|
||||
return true
|
||||
}
|
||||
is CommandPartNode -> {
|
||||
logger.trace { "Found command part node ($key) - ${node.debugString}" }
|
||||
currentCommandPartNode = node
|
||||
addKey(key)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace { "No command/command part node found for key: $key" }
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a keystroke that duplicates an operator into the `_` "current line" motion
|
||||
*
|
||||
* Some commands like `dd` or `yy` or `cc` are treated as special cases by Vim. There is no `d`, `y` or `c` motion,
|
||||
* so for convenience, Vim maps the repeated operator keystroke as meaning "operate on the current line", and replaces
|
||||
* the second keystroke with the `_` motion. I.e. `dd` becomes `d_`, `yy` becomes `y_`, `cc` becomes `c_`, etc.
|
||||
*
|
||||
* @see DuplicableOperatorAction
|
||||
*/
|
||||
fun convertDuplicateOperatorKeyStrokeToMotion(key: KeyStroke): KeyStroke {
|
||||
logger.trace { "convertDuplicateOperatorKeyStrokeToMotion is executed. key = $key" }
|
||||
|
||||
// Simple check to ensure that we're in OP_PENDING. If we don't have an action, we don't have an operator. If we
|
||||
// have an argument, we can't be in OP_PENDING
|
||||
if (action != null && argument == null) {
|
||||
(action as? DuplicableOperatorAction)?.let {
|
||||
logger.trace { "action = $action" }
|
||||
if (it.duplicateWith == key.keyChar) {
|
||||
return KeyStroke.getKeyStroke('_')
|
||||
}
|
||||
}
|
||||
}
|
||||
return key
|
||||
fun isAwaitingCharOrDigraphArgument(): Boolean {
|
||||
val awaiting = expectedArgumentType == Argument.Type.CHARACTER || expectedArgumentType == Argument.Type.DIGRAPH
|
||||
logger.debug { "Awaiting char or digraph: $awaiting" }
|
||||
return awaiting
|
||||
}
|
||||
|
||||
fun isBuildingMultiKeyCommand(): Boolean {
|
||||
@ -324,47 +178,67 @@ class CommandBuilder private constructor(
|
||||
return isMultikey
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the command with the current counts, register, actions and arguments
|
||||
*
|
||||
* The command builder is reset after the command is built.
|
||||
*/
|
||||
fun isDone(): Boolean {
|
||||
return commandParts.isEmpty()
|
||||
}
|
||||
|
||||
fun completeCommandPart(argument: Argument) {
|
||||
logger.trace { "completeCommandPart is executed" }
|
||||
commandParts.last().argument = argument
|
||||
commandState = CurrentCommandState.READY
|
||||
}
|
||||
|
||||
fun isDuplicateOperatorKeyStroke(key: KeyStroke): Boolean {
|
||||
logger.trace { "entered isDuplicateOperatorKeyStroke" }
|
||||
val action = commandParts.last().action as? DuplicableOperatorAction
|
||||
logger.trace { "action = $action" }
|
||||
return action?.duplicateWith == key.keyChar
|
||||
}
|
||||
|
||||
fun hasCurrentCommandPartArgument(): Boolean {
|
||||
return commandParts.lastOrNull()?.argument != null
|
||||
}
|
||||
|
||||
fun buildCommand(): Command {
|
||||
val rawCount = calculateCount0Snapshot()
|
||||
val command = Command(selectedRegister, rawCount, action!!, argument, action!!.type, action?.flags ?: noneOfEnum())
|
||||
resetAll(currentCommandPartNode.root as RootNode<LazyVimCommand>)
|
||||
var command: Command = commandParts.removeFirst()
|
||||
while (commandParts.size > 0) {
|
||||
val next = commandParts.removeFirst()
|
||||
next.rawCount = if (command.rawCount == 0 && next.rawCount == 0) 0 else command.count * next.count
|
||||
command.rawCount = 0
|
||||
if (command.type == Command.Type.SELECT_REGISTER) {
|
||||
next.register = command.register
|
||||
command.register = null
|
||||
command = next
|
||||
} else {
|
||||
command.argument = Argument(next)
|
||||
assert(commandParts.size == 0)
|
||||
}
|
||||
}
|
||||
fallbackArgumentType = null
|
||||
return command
|
||||
}
|
||||
|
||||
fun resetAll(rootNode: RootNode<LazyVimCommand>) {
|
||||
logger.trace("resetAll is executed")
|
||||
currentCommandPartNode = rootNode
|
||||
fun resetAll(commandPartNode: CommandPartNode<LazyVimCommand>) {
|
||||
logger.trace { "resetAll is executed" }
|
||||
resetInProgressCommandPart(commandPartNode)
|
||||
commandState = CurrentCommandState.NEW_COMMAND
|
||||
counts.clear()
|
||||
counts.add(0)
|
||||
isRegisterPending = false
|
||||
selectedRegister = null
|
||||
action = null
|
||||
argument = null
|
||||
commandParts.clear()
|
||||
keyList.clear()
|
||||
fallbackArgumentType = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the command trie root node used to find commands for the current mode
|
||||
*
|
||||
* Typically, we reset the command trie root node after a command is executed, using the root node of the current
|
||||
* mode - this is handled by [resetAll]. This function allows us to change the root node without executing a command
|
||||
* or fully resetting the command builder, such as when switching to Op-pending while entering an operator+motion.
|
||||
*/
|
||||
fun resetCommandTrieRootNode(rootNode: RootNode<LazyVimCommand>) {
|
||||
logger.trace("resetCommandTrieRootNode is executed")
|
||||
currentCommandPartNode = rootNode
|
||||
fun resetCount() {
|
||||
count = 0
|
||||
}
|
||||
|
||||
fun resetInProgressCommandPart(commandPartNode: CommandPartNode<LazyVimCommand>) {
|
||||
logger.trace { "resetInProgressCommandPart is executed" }
|
||||
count = 0
|
||||
setCurrentCommandPartNode(commandPartNode)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun getCurrentTrie(): CommandPartNode<LazyVimCommand> = currentCommandPartNode
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
@ -372,12 +246,10 @@ class CommandBuilder private constructor(
|
||||
other as CommandBuilder
|
||||
|
||||
if (currentCommandPartNode != other.currentCommandPartNode) return false
|
||||
if (counts != other.counts) return false
|
||||
if (selectedRegister != other.selectedRegister) return false
|
||||
if (action != other.action) return false
|
||||
if (argument != other.argument) return false
|
||||
if (commandParts != other.commandParts) return false
|
||||
if (keyList != other.keyList) return false
|
||||
if (commandState != other.commandState) return false
|
||||
if (count != other.count) return false
|
||||
if (expectedArgumentType != other.expectedArgumentType) return false
|
||||
if (fallbackArgumentType != other.fallbackArgumentType) return false
|
||||
|
||||
@ -386,38 +258,24 @@ class CommandBuilder private constructor(
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = currentCommandPartNode.hashCode()
|
||||
result = 31 * result + counts.hashCode()
|
||||
result = 31 * result + selectedRegister.hashCode()
|
||||
result = 31 * result + action.hashCode()
|
||||
result = 31 * result + argument.hashCode()
|
||||
result = 31 * result + commandParts.hashCode()
|
||||
result = 31 * result + keyList.hashCode()
|
||||
result = 31 * result + commandState.hashCode()
|
||||
result = 31 * result + expectedArgumentType.hashCode()
|
||||
result = 31 * result + fallbackArgumentType.hashCode()
|
||||
result = 31 * result + count
|
||||
result = 31 * result + (expectedArgumentType?.hashCode() ?: 0)
|
||||
result = 31 * result + (fallbackArgumentType?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
public override fun clone(): CommandBuilder {
|
||||
val result = CommandBuilder(
|
||||
currentCommandPartNode,
|
||||
counts.toMutableList(),
|
||||
keyList.toMutableList()
|
||||
)
|
||||
result.selectedRegister = selectedRegister
|
||||
result.action = action
|
||||
result.argument = argument
|
||||
val result = CommandBuilder(currentCommandPartNode, ArrayDeque(commandParts), keyList.toMutableList(), count)
|
||||
result.commandState = commandState
|
||||
result.fallbackArgumentType = fallbackArgumentType
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Command state = $commandState, " +
|
||||
"key list = ${ injector.parser.toKeyNotation(keyList) }, " +
|
||||
"selected register = $selectedRegister, " +
|
||||
"counts = $counts, " +
|
||||
"action = $action, " +
|
||||
"argument = $argument, " +
|
||||
return "Command state = $commandState, key list = ${ injector.parser.toKeyNotation(keyList) }, command parts = ${ commandParts }, count = $count\n" +
|
||||
"command part node - $currentCommandPartNode"
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,11 @@ enum class CommandFlags {
|
||||
*/
|
||||
FLAG_EXPECT_MORE,
|
||||
|
||||
/**
|
||||
* Indicate that the character argument may come from a digraph
|
||||
*/
|
||||
FLAG_ALLOW_DIGRAPH,
|
||||
|
||||
FLAG_START_EX,
|
||||
FLAG_END_EX,
|
||||
|
||||
|
@ -42,12 +42,12 @@ object MappingProcessor: KeyConsumer {
|
||||
val keyState = keyProcessResultBuilder.state
|
||||
val mappingState = keyState.mappingState
|
||||
val commandBuilder = keyState.commandBuilder
|
||||
if (commandBuilder.isAwaitingCharOrDigraphArgument()
|
||||
|| commandBuilder.isBuildingMultiKeyCommand()
|
||||
|| commandBuilder.isRegisterPending
|
||||
|| isMappingDisabledForKey(key, keyState)
|
||||
if (commandBuilder.isAwaitingCharOrDigraphArgument() ||
|
||||
commandBuilder.isBuildingMultiKeyCommand() ||
|
||||
isMappingDisabledForKey(key, keyState) ||
|
||||
injector.vimState.isRegisterPending
|
||||
) {
|
||||
log.debug("Mapping not applicable. Finish key processing, returning false")
|
||||
log.debug("Finish key processing, returning false")
|
||||
return false
|
||||
}
|
||||
mappingState.stopMappingTimer()
|
||||
@ -74,7 +74,7 @@ object MappingProcessor: KeyConsumer {
|
||||
// "0" can be mapped, but the mapping isn't applied when entering a count. Other digits are always mapped, even when
|
||||
// entering a count.
|
||||
// See `:help :map-modes`
|
||||
val isMappingDisabled = key.keyChar == '0' && keyState.commandBuilder.hasCountCharacters()
|
||||
val isMappingDisabled = key.keyChar == '0' && keyState.commandBuilder.count > 0
|
||||
log.debug { "Mapping disabled for key: $isMappingDisabled" }
|
||||
return isMappingDisabled
|
||||
}
|
||||
|
@ -8,53 +8,21 @@
|
||||
|
||||
package com.maddyhome.idea.vim.command
|
||||
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
|
||||
/**
|
||||
* Represents arguments used when executing a command - either an action, operator or motion
|
||||
*
|
||||
* TODO: Remove, rename or otherwise refactor this class
|
||||
*
|
||||
* Problems with this class:
|
||||
* * The name is misleading, as it is used when executing motions that do not have an operator, as well as when
|
||||
* executing the operator itself. Or even when executing actions that are neither operators nor motions
|
||||
* * It is not clear if this represents the arguments to an operator (while the operator's [Command] also has arguments)
|
||||
* * [mode] is the mode _before_ the command is completed, which is not guaranteed to be the same as the mode once the
|
||||
* command completes. There is no indication of this difference, which could lead to confusion
|
||||
* * The count is (correctly) the count for the whole command, rather than the operator, or the operator's arguments
|
||||
* (the in-progress motion)
|
||||
*
|
||||
* @param isOperatorPending Deprecated. The value is used to indicate that a command is operator+motion and was
|
||||
* previously used to change the behaviour of the motion (the EOL character is counted in this scenario - see
|
||||
* `:help whichwrap`). It is better to register a separate action for [Mode.OP_PENDING] rather than expect a runtime
|
||||
* flag for something that can be handled statically.
|
||||
* @param count0 The raw count of the entire command. E.g., if the command is `2d3w`, then this count will be `6`, even
|
||||
* when this class is passed to the `d` operator action (the count applies to the motion).
|
||||
* @param mode Deprecated. The mode of the editor at the time that the [OperatorArguments] is created, which is _before_
|
||||
* the command is completed. This was previously used to check for [Mode.OP_PENDING], but is no longer required. Prefer
|
||||
* [VimEditor.mode] instead.
|
||||
* [count0] is a raw count entered by user. May be zero.
|
||||
* [count1] is the same count, but 1-based. If [count0] is zero, [count1] is one.
|
||||
* The terminology is taken directly from vim.
|
||||
* If no count is provided, [count0] defaults to zero.
|
||||
*/
|
||||
data class OperatorArguments
|
||||
|
||||
@Deprecated(
|
||||
"Use overload without isOperatorPending. Value can be calculated from mode",
|
||||
replaceWith = ReplaceWith("OperatorArguments(count0, mode)"),
|
||||
) constructor(
|
||||
// This is used by EasyMotion
|
||||
@Deprecated("It is better to register a separate OP_PENDING action than switch on a runtime flag") val isOperatorPending: Boolean,
|
||||
data class OperatorArguments(
|
||||
val isOperatorPending: Boolean,
|
||||
val count0: Int,
|
||||
@Deprecated("Represents the mode when the OperatorArguments was created, not the current mode. Prefer editor.mode") val mode: Mode,
|
||||
|
||||
val mode: Mode,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a new instance of [OperatorArguments]
|
||||
*
|
||||
* @param count0 The 0-based count for the whole command
|
||||
* @param mode Only used for the deprecated [OperatorArguments.mode] property
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
constructor(count0: Int, mode: Mode) : this(mode is Mode.OP_PENDING, count0, mode)
|
||||
|
||||
val count1: Int = count0.coerceAtLeast(1)
|
||||
|
||||
fun withCount0(count0: Int): OperatorArguments = this.copy(count0 = count0)
|
||||
}
|
||||
|
@ -11,4 +11,5 @@ package com.maddyhome.idea.vim.common
|
||||
enum class CurrentCommandState {
|
||||
NEW_COMMAND,
|
||||
READY,
|
||||
BAD_COMMAND,
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.common
|
||||
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
|
||||
class VimEditorReplaceMask {
|
||||
private val changedChars = mutableMapOf<LiveRange, Char>()
|
||||
|
||||
fun recordChangeAtCaret(editor: VimEditor) {
|
||||
for (caret in editor.carets()) {
|
||||
val offset = caret.offset
|
||||
val marker = editor.createLiveMarker(offset, offset)
|
||||
changedChars[marker] = editor.charAt(offset)
|
||||
}
|
||||
}
|
||||
|
||||
fun popChange(editor: VimEditor, offset: Int): Char? {
|
||||
val marker = editor.createLiveMarker(offset, offset)
|
||||
val change = changedChars[marker]
|
||||
changedChars.remove(marker)
|
||||
return change
|
||||
}
|
||||
}
|
||||
|
||||
fun forgetAllReplaceMasks() {
|
||||
injector.editorGroup.getEditors().forEach { it.replaceMask = null }
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user