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
|
// Active tests
|
||||||
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||||
buildType(TestingBuildType("2024.1.1", "<default>"))
|
buildType(TestingBuildType("2024.1.1", "<default>"))
|
||||||
buildType(TestingBuildType("2024.2", "<default>"))
|
|
||||||
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||||
|
|
||||||
buildType(PropertyBased)
|
buildType(PropertyBased)
|
||||||
|
@ -535,10 +535,6 @@ Contributors:
|
|||||||
[![icon][github]](https://github.com/igorbabko)
|
[![icon][github]](https://github.com/igorbabko)
|
||||||
|
|
||||||
Igor Babko
|
Igor Babko
|
||||||
* [![icon][mail]](mailto:533601+felixwiemuth@users.noreply.github.com)
|
|
||||||
[![icon][github]](https://github.com/felixwiemuth)
|
|
||||||
|
|
||||||
Felix Wiemuth
|
|
||||||
|
|
||||||
Previous contributors:
|
Previous contributors:
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ Every effort is made to make these options compatible with Vim behaviour.
|
|||||||
However, some differences are inevitable.
|
However, some differences are inevitable.
|
||||||
|
|
||||||
```
|
```
|
||||||
'clipboard' 'cb' Defines clipboard behavior
|
'clipboard' 'cb' Defines clipboard behavioue
|
||||||
A comma-separated list of words to control clipboard behaviour:
|
A comma-separated list of words to control clipboard behaviour:
|
||||||
unnamed The clipboard register '*' is used instead of the
|
unnamed The clipboard register '*' is used instead of the
|
||||||
unnamed register
|
unnamed register
|
||||||
|
@ -20,7 +20,7 @@ ideaVersion=2024.2
|
|||||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||||
ideaType=IC
|
ideaType=IC
|
||||||
instrumentPluginCode=true
|
instrumentPluginCode=true
|
||||||
version=chylex-41
|
version=chylex-40
|
||||||
javaVersion=17
|
javaVersion=17
|
||||||
remoteRobotVersion=0.11.23
|
remoteRobotVersion=0.11.23
|
||||||
antlrVersion=4.10.1
|
antlrVersion=4.10.1
|
||||||
|
@ -8,10 +8,6 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim
|
package com.maddyhome.idea.vim
|
||||||
|
|
||||||
import com.intellij.ide.BrowserUtil
|
|
||||||
import com.intellij.ide.plugins.IdeaPluginDescriptor
|
|
||||||
import com.intellij.ide.plugins.PluginStateListener
|
|
||||||
import com.intellij.ide.plugins.PluginStateManager
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
@ -44,18 +40,6 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
|
|||||||
|
|
||||||
// This code should be executed once
|
// This code should be executed once
|
||||||
VimPlugin.getInstance().initialize()
|
VimPlugin.getInstance().initialize()
|
||||||
|
|
||||||
// Uninstall survey. Should be registered once for all projects
|
|
||||||
PluginStateManager.addStateListener(object : PluginStateListener {
|
|
||||||
override fun install(p0: IdeaPluginDescriptor) {/*Nothing*/
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun uninstall(descriptor: IdeaPluginDescriptor) {
|
|
||||||
if (descriptor.pluginId == VimPlugin.getPluginId()) {
|
|
||||||
BrowserUtil.open("https://surveys.jetbrains.com/s3/ideavim-uninstall-feedback")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
|
|||||||
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
|
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
|
||||||
|
|
||||||
// todo make it multicaret
|
// todo make it multicaret
|
||||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, motionType: SelectionType): Boolean {
|
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
|
||||||
val func = injector.globalOptions().operatorfunc
|
val func = injector.globalOptions().operatorfunc
|
||||||
if (func.isEmpty()) {
|
if (func.isEmpty()) {
|
||||||
VimPlugin.showMessage(MessageHelper.message("E774"))
|
VimPlugin.showMessage(MessageHelper.message("E774"))
|
||||||
@ -57,9 +57,9 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
|
|||||||
if (value is VimFuncref) {
|
if (value is VimFuncref) {
|
||||||
handler = value.handler
|
handler = value.handler
|
||||||
}
|
}
|
||||||
} catch (_: ExException) {
|
} catch (ex: ExException) {
|
||||||
// Get the argument for function('...') or funcref('...') for the error message
|
// Get the argument for function('...') or funcref('...') for the error message
|
||||||
val functionName = if (expression is FunctionCallExpression && expression.arguments.isNotEmpty()) {
|
val functionName = if (expression is FunctionCallExpression && expression.arguments.size > 0) {
|
||||||
expression.arguments[0].evaluate(editor, context, scriptContext).toString()
|
expression.arguments[0].evaluate(editor, context, scriptContext).toString()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -77,7 +77,7 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val arg = when (motionType) {
|
val arg = when (selectionType) {
|
||||||
SelectionType.LINE_WISE -> "line"
|
SelectionType.LINE_WISE -> "line"
|
||||||
SelectionType.CHARACTER_WISE -> "char"
|
SelectionType.CHARACTER_WISE -> "char"
|
||||||
SelectionType.BLOCK_WISE -> "block"
|
SelectionType.BLOCK_WISE -> "block"
|
||||||
@ -101,13 +101,19 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
|
|||||||
override val argumentType: Argument.Type = Argument.Type.MOTION
|
override val argumentType: Argument.Type = Argument.Type.MOTION
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
val argument = cmd.argument as? Argument.Motion ?: return false
|
val argument = cmd.argument ?: return false
|
||||||
if (!editor.inRepeatMode) {
|
if (!editor.inRepeatMode) {
|
||||||
argumentCaptured = argument
|
argumentCaptured = argument
|
||||||
}
|
}
|
||||||
val range = getMotionRange(editor, context, argument, operatorArguments)
|
val range = getMotionRange(editor, context, argument, operatorArguments)
|
||||||
|
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
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
|
return false
|
||||||
}
|
}
|
||||||
@ -115,7 +121,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
|
|||||||
private fun getMotionRange(
|
private fun getMotionRange(
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
argument: Argument.Motion,
|
argument: Argument,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): TextRange? {
|
): TextRange? {
|
||||||
// Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start
|
// Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start
|
||||||
@ -130,7 +136,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
|
|||||||
operatorArguments,
|
operatorArguments,
|
||||||
)?.normalize()?.let {
|
)?.normalize()?.let {
|
||||||
// If we're linewise, make sure the end offset isn't just the EOL char
|
// If we're linewise, make sure the end offset isn't just the EOL char
|
||||||
if (argument.getMotionType() == SelectionType.LINE_WISE && it.endOffset < editor.fileSize()) {
|
if (argument.motion.isLinewiseMotion() && it.endOffset < editor.fileSize()) {
|
||||||
TextRange(it.startOffset, it.endOffset + 1)
|
TextRange(it.startOffset, it.endOffset + 1)
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
|
@ -25,7 +25,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
|
|||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
val state = injector.vimState
|
val state = injector.vimState
|
||||||
var lastCommand = VimRepeater.lastChangeCommand
|
val lastCommand = VimRepeater.lastChangeCommand
|
||||||
|
|
||||||
if (lastCommand == null && Extension.lastExtensionHandler == null) return false
|
if (lastCommand == null && Extension.lastExtensionHandler == null) return false
|
||||||
|
|
||||||
@ -57,7 +57,12 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
|
|||||||
)
|
)
|
||||||
} else if (!repeatHandler && lastCommand != null) {
|
} else if (!repeatHandler && lastCommand != null) {
|
||||||
if (cmd.rawCount > 0) {
|
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
|
state.executingCommand = lastCommand
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecuti
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
injector.editorGroup.notifyIdeaJoin(editor)
|
injector.editorGroup.notifyIdeaJoin(editor)
|
||||||
|
|
||||||
return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false)
|
return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false, operatorArguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun execute(
|
override fun execute(
|
||||||
|
@ -35,7 +35,7 @@ class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution()
|
|||||||
injector.editorGroup.notifyIdeaJoin(editor)
|
injector.editorGroup.notifyIdeaJoin(editor)
|
||||||
var res = true
|
var res = true
|
||||||
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
|
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
|
||||||
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true)) {
|
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) {
|
||||||
res = false
|
res = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,11 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
|
|||||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||||
import java.util.*
|
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])
|
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
|
||||||
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE) {
|
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE) {
|
||||||
override val type: Command.Type = Command.Type.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.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
||||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
|
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
|
||||||
@ -63,8 +64,8 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
*/
|
*/
|
||||||
private static class BracketPairs {
|
private static class BracketPairs {
|
||||||
// NOTE: brackets must match by the position, and ordered by rank (highest to lowest).
|
// NOTE: brackets must match by the position, and ordered by rank (highest to lowest).
|
||||||
private final @NotNull String openBrackets;
|
@NotNull private final String openBrackets;
|
||||||
private final @NotNull String closeBrackets;
|
@NotNull private final String closeBrackets;
|
||||||
|
|
||||||
static class ParseException extends Exception {
|
static class ParseException extends Exception {
|
||||||
public ParseException(@NotNull String message) {
|
public ParseException(@NotNull String message) {
|
||||||
@ -86,7 +87,8 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
* @param bracketPairs comma-separated list of colon-separated bracket pairs.
|
* @param bracketPairs comma-separated list of colon-separated bracket pairs.
|
||||||
* @throws ParseException if a syntax error is detected.
|
* @throws ParseException if a syntax error is detected.
|
||||||
*/
|
*/
|
||||||
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 openBrackets = new StringBuilder();
|
||||||
StringBuilder closeBrackets = new StringBuilder();
|
StringBuilder closeBrackets = new StringBuilder();
|
||||||
ParseState state = ParseState.OPEN;
|
ParseState state = ParseState.OPEN;
|
||||||
@ -126,7 +128,7 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
return new BracketPairs(openBrackets.toString(), closeBrackets.toString());
|
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();
|
assert openBrackets.length() == closeBrackets.length();
|
||||||
this.openBrackets = openBrackets;
|
this.openBrackets = openBrackets;
|
||||||
this.closeBrackets = closeBrackets;
|
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");
|
final Object value = VimPlugin.getVariableService().getGlobalVariableValue("argtextobj_pairs");
|
||||||
if (value instanceof VimString vimValue) {
|
if (value instanceof VimString vimValue) {
|
||||||
return vimValue.getValue();
|
return vimValue.getValue();
|
||||||
@ -189,8 +192,9 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
this.isInner = isInner;
|
this.isInner = isInner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public @Nullable TextRange getRange(@NotNull VimEditor editor,
|
public TextRange getRange(@NotNull VimEditor editor,
|
||||||
@NotNull ImmutableVimCaret caret,
|
@NotNull ImmutableVimCaret caret,
|
||||||
@NotNull ExecutionContext context,
|
@NotNull ExecutionContext context,
|
||||||
int count,
|
int count,
|
||||||
@ -232,22 +236,24 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
return new TextRange(finder.getLeftBound(), finder.getRightBound());
|
return new TextRange(finder.getLeftBound(), finder.getRightBound());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public @NotNull TextObjectVisualType getVisualType() {
|
public TextObjectVisualType getVisualType() {
|
||||||
return TextObjectVisualType.CHARACTER_WISE;
|
return TextObjectVisualType.CHARACTER_WISE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||||
|
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||||
|
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||||
|
|
||||||
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
||||||
//noinspection DuplicatedCode
|
//noinspection DuplicatedCode
|
||||||
if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
|
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||||
int count0 = operatorArguments.getCount0();
|
|
||||||
editor.nativeCarets().forEach((VimCaret caret) -> {
|
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) {
|
if (range != null) {
|
||||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||||
@ -259,7 +265,8 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} 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
|
* position
|
||||||
*/
|
*/
|
||||||
private static class ArgBoundsFinder {
|
private static class ArgBoundsFinder {
|
||||||
private final @NotNull CharSequence text;
|
@NotNull private final CharSequence text;
|
||||||
private final @NotNull Document document;
|
@NotNull private final Document document;
|
||||||
private final @NotNull BracketPairs brackets;
|
@NotNull private final BracketPairs brackets;
|
||||||
private int leftBound = Integer.MAX_VALUE;
|
private int leftBound = Integer.MAX_VALUE;
|
||||||
private int rightBound = Integer.MIN_VALUE;
|
private int rightBound = Integer.MIN_VALUE;
|
||||||
private int leftBracket;
|
private int leftBracket;
|
||||||
@ -298,7 +305,7 @@ public class VimArgTextObjExtension implements VimExtension {
|
|||||||
* @param position starting position.
|
* @param position starting position.
|
||||||
*/
|
*/
|
||||||
boolean findBoundsAt(int position) throws IllegalStateException {
|
boolean findBoundsAt(int position) throws IllegalStateException {
|
||||||
if (text.isEmpty()) {
|
if (text.length() == 0) {
|
||||||
error = "empty document";
|
error = "empty document";
|
||||||
return false;
|
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.getLineEndOffset
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
|
import com.maddyhome.idea.vim.command.Command
|
||||||
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
import com.maddyhome.idea.vim.command.MappingMode
|
import com.maddyhome.idea.vim.command.MappingMode
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.command.TextObjectVisualType
|
import com.maddyhome.idea.vim.command.TextObjectVisualType
|
||||||
@ -49,6 +52,7 @@ import com.maddyhome.idea.vim.newapi.ij
|
|||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
internal class CommentaryExtension : VimExtension {
|
internal class CommentaryExtension : VimExtension {
|
||||||
|
|
||||||
@ -180,8 +184,10 @@ internal class CommentaryExtension : VimExtension {
|
|||||||
override val isRepeatable = true
|
override val isRepeatable = true
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
|
val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
|
||||||
|
|
||||||
val keyState = KeyHandler.getInstance().keyHandlerState
|
val keyState = KeyHandler.getInstance().keyHandlerState
|
||||||
keyState.commandBuilder.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.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
@ -94,29 +93,34 @@ internal class Matchit : VimExtension {
|
|||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
val keyHandler = KeyHandler.getInstance()
|
val keyHandler = KeyHandler.getInstance()
|
||||||
val keyState = keyHandler.keyHandlerState
|
val keyState = keyHandler.keyHandlerState
|
||||||
|
val count = keyState.commandBuilder.count
|
||||||
|
|
||||||
// Reset the command count so it doesn't transfer onto subsequent commands.
|
// Reset the command count so it doesn't transfer onto subsequent commands.
|
||||||
keyState.commandBuilder.resetCount()
|
keyState.commandBuilder.resetCount()
|
||||||
|
|
||||||
// Normally we want to jump to the start of the matching pair. But when moving forward in operator
|
// Normally we want to jump to the start of the matching pair. But when moving forward in operator
|
||||||
// pending mode, we want to include the entire match. isInOpPending makes that distinction.
|
// pending mode, we want to include the entire match. isInOpPending makes that distinction.
|
||||||
if (editor.mode is Mode.OP_PENDING) {
|
val isInOpPending = keyHandler.isOperatorPending(editor.mode, keyState)
|
||||||
|
|
||||||
|
if (isInOpPending) {
|
||||||
val matchitAction = MatchitAction()
|
val matchitAction = MatchitAction()
|
||||||
matchitAction.reverse = reverse
|
matchitAction.reverse = reverse
|
||||||
matchitAction.isInOpPending = true
|
matchitAction.isInOpPending = true
|
||||||
|
|
||||||
keyState.commandBuilder.addAction(matchitAction)
|
keyState.commandBuilder.completeCommandPart(
|
||||||
|
Argument(
|
||||||
|
Command(
|
||||||
|
count,
|
||||||
|
matchitAction,
|
||||||
|
Command.Type.MOTION,
|
||||||
|
EnumSet.noneOf(CommandFlags::class.java),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
editor.sortedCarets().forEach { caret ->
|
editor.sortedCarets().forEach { caret ->
|
||||||
injector.jumpService.saveJumpLocation(editor)
|
injector.jumpService.saveJumpLocation(editor)
|
||||||
caret.moveToOffset(
|
caret.moveToOffset(getMatchitOffset(editor.ij, caret.ij, count, isInOpPending, reverse))
|
||||||
getMatchitOffset(
|
|
||||||
editor.ij,
|
|
||||||
caret.ij,
|
|
||||||
operatorArguments.count0,
|
|
||||||
isInOpPending = false,
|
|
||||||
reverse
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,7 +354,7 @@ private object FileTypePatterns {
|
|||||||
|
|
||||||
private val DEFAULT_PAIRS = setOf('(', ')', '[', ']', '{', '}')
|
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)
|
val virtualFile = EditorHelper.getVirtualFile(editor)
|
||||||
var caretOffset = caret.offset
|
var caretOffset = caret.offset
|
||||||
|
|
||||||
@ -363,9 +367,9 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPe
|
|||||||
val currentChar = editor.document.charsSequence[caretOffset]
|
val currentChar = editor.document.charsSequence[caretOffset]
|
||||||
var motionOffset: Int? = null
|
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.
|
// 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 {
|
} else {
|
||||||
// Check the simplest case first.
|
// Check the simplest case first.
|
||||||
if (DEFAULT_PAIRS.contains(currentChar)) {
|
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? {
|
private fun getMotionOffset(motion: Motion): Int? {
|
||||||
return when (motion) {
|
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
|
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 var currentNode: CommandPartNode<NerdAction> = actionsRoot
|
||||||
|
|
||||||
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
|
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
|
||||||
return if (node is CommandPartNode<NerdAction>) {
|
return if (node is CommandPartNode<NerdAction>) {
|
||||||
val res = node.children.keys.toMutableSet()
|
val res = node.keys.toMutableSet()
|
||||||
res += node.children.values.map { collectShortcuts(it) }.flatten()
|
res += node.values.map { collectShortcuts(it) }.flatten()
|
||||||
res
|
res
|
||||||
} else {
|
} else {
|
||||||
emptySet()
|
emptySet()
|
||||||
|
@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.extension.replacewithregister
|
|||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||||
@ -165,11 +166,17 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
|
|||||||
putToLine = -1,
|
putToLine = -1,
|
||||||
)
|
)
|
||||||
val vimEditor = editor.vim
|
val vimEditor = editor.vim
|
||||||
|
val keyHandler = KeyHandler.getInstance()
|
||||||
ClipboardOptionHelper.IdeaputDisabler().use {
|
ClipboardOptionHelper.IdeaputDisabler().use {
|
||||||
VimPlugin.getPut().putText(
|
VimPlugin.getPut().putText(
|
||||||
vimEditor,
|
vimEditor,
|
||||||
context.vim,
|
context.vim,
|
||||||
putData,
|
putData,
|
||||||
|
operatorArguments = OperatorArguments(
|
||||||
|
keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState),
|
||||||
|
0,
|
||||||
|
editor.vim.mode,
|
||||||
|
),
|
||||||
saveToRegister = false
|
saveToRegister = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,14 @@ import com.maddyhome.idea.vim.state.mode.Mode;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
||||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
|
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port of vim-entire:
|
* Port of vim-entire:
|
||||||
* <a href="https://github.com/kana/vim-textobj-entire">vim-textobj-entire</a>
|
* https://github.com/kana/vim-textobj-entire
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* vim-textobj-entire provides two text objects:
|
* vim-textobj-entire provides two text objects:
|
||||||
@ -49,7 +51,7 @@ import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingI
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* See also the reference manual for more details at:
|
* See also the reference manual for more details at:
|
||||||
* <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)
|
* @author Alexandre Grison (@agrison)
|
||||||
*/
|
*/
|
||||||
@ -92,8 +94,9 @@ public class VimTextObjEntireExtension implements VimExtension {
|
|||||||
this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing;
|
this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public @Nullable TextRange getRange(@NotNull VimEditor editor,
|
public TextRange getRange(@NotNull VimEditor editor,
|
||||||
@NotNull ImmutableVimCaret caret,
|
@NotNull ImmutableVimCaret caret,
|
||||||
@NotNull ExecutionContext context,
|
@NotNull ExecutionContext context,
|
||||||
int count,
|
int count,
|
||||||
@ -122,22 +125,24 @@ public class VimTextObjEntireExtension implements VimExtension {
|
|||||||
return new TextRange(start, end);
|
return new TextRange(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public @NotNull TextObjectVisualType getVisualType() {
|
public TextObjectVisualType getVisualType() {
|
||||||
return TextObjectVisualType.CHARACTER_WISE;
|
return TextObjectVisualType.CHARACTER_WISE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||||
|
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||||
|
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||||
|
|
||||||
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
|
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
|
||||||
//noinspection DuplicatedCode
|
//noinspection DuplicatedCode
|
||||||
if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
|
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||||
int count0 = operatorArguments.getCount0();
|
|
||||||
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||||
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, Math.max(1, count0), count0);
|
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||||
@ -150,7 +155,9 @@ public class VimTextObjEntireExtension implements VimExtension {
|
|||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} 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.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
||||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
|
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port of vim-indent-object:
|
* Port of vim-indent-object:
|
||||||
* <a href="https://github.com/michaeljsmith/vim-indent-object">vim-indent-object</a>
|
* https://github.com/michaeljsmith/vim-indent-object
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* vim-indent-object provides these text objects based on the cursor line's indentation:
|
* vim-indent-object provides these text objects based on the cursor line's indentation:
|
||||||
@ -47,7 +49,7 @@ import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* See also the reference manual for more details at:
|
* See also the reference manual for more details at:
|
||||||
* <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)
|
* @author Shrikant Kandula (@sharat87)
|
||||||
*/
|
*/
|
||||||
@ -96,8 +98,9 @@ public class VimIndentObject implements VimExtension {
|
|||||||
this.includeBelow = includeBelow;
|
this.includeBelow = includeBelow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public @Nullable TextRange getRange(@NotNull VimEditor editor,
|
public TextRange getRange(@NotNull VimEditor editor,
|
||||||
@NotNull ImmutableVimCaret caret,
|
@NotNull ImmutableVimCaret caret,
|
||||||
@NotNull ExecutionContext context,
|
@NotNull ExecutionContext context,
|
||||||
int count,
|
int count,
|
||||||
@ -246,8 +249,9 @@ public class VimIndentObject implements VimExtension {
|
|||||||
return new TextRange(upperBoundaryOffset, lowerBoundaryOffset);
|
return new TextRange(upperBoundaryOffset, lowerBoundaryOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public @NotNull TextObjectVisualType getVisualType() {
|
public TextObjectVisualType getVisualType() {
|
||||||
return TextObjectVisualType.LINE_WISE;
|
return TextObjectVisualType.LINE_WISE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,14 +264,15 @@ public class VimIndentObject implements VimExtension {
|
|||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||||
IjVimEditor vimEditor = (IjVimEditor)editor;
|
IjVimEditor vimEditor = (IjVimEditor)editor;
|
||||||
|
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||||
|
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||||
|
|
||||||
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
|
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
|
||||||
|
|
||||||
if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
|
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||||
int count0 = operatorArguments.getCount0();
|
|
||||||
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||||
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, Math.max(1, count0), count0);
|
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||||
@ -280,7 +285,9 @@ public class VimIndentObject implements VimExtension {
|
|||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} 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) {
|
private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) {
|
||||||
if (caret != editor.primaryCaret()) {
|
if (caret != editor.primaryCaret()) {
|
||||||
(editor as IjVimEditor).editor.caretModel.addCaret(
|
(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.MotionType
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.handler.ExternalActionHandler
|
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||||
@ -193,16 +193,21 @@ internal class MotionGroup : VimMotionGroupBase() {
|
|||||||
argument: Argument,
|
argument: Argument,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): TextRange? {
|
): TextRange? {
|
||||||
if (argument !is Argument.Motion) {
|
|
||||||
throw RuntimeException("Unexpected argument passed to getMotionRange2: $argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
var start: Int
|
var start: Int
|
||||||
var end: Int
|
var end: Int
|
||||||
|
if (argument.type === Argument.Type.OFFSETS) {
|
||||||
|
val offsets = argument.offsets[caret.vim] ?: return null
|
||||||
|
val (first, second) = offsets.getNativeStartAndEnd()
|
||||||
|
start = first
|
||||||
|
end = second
|
||||||
|
} else {
|
||||||
|
val cmd = argument.motion
|
||||||
|
// Normalize the counts between the command and the motion argument
|
||||||
|
val cnt = cmd.count * operatorArguments.count1
|
||||||
|
val raw = if (operatorArguments.count0 == 0 && cmd.rawCount == 0) 0 else cnt
|
||||||
|
if (cmd.action is MotionActionHandler) {
|
||||||
|
val action = cmd.action as MotionActionHandler
|
||||||
|
|
||||||
val action = argument.motion
|
|
||||||
when (action) {
|
|
||||||
is MotionActionHandler -> {
|
|
||||||
// This is where we are now
|
// This is where we are now
|
||||||
start = caret.offset
|
start = caret.offset
|
||||||
|
|
||||||
@ -211,8 +216,8 @@ internal class MotionGroup : VimMotionGroupBase() {
|
|||||||
editor.vim,
|
editor.vim,
|
||||||
caret.vim,
|
caret.vim,
|
||||||
IjEditorExecutionContext(context!!),
|
IjEditorExecutionContext(context!!),
|
||||||
argument.argument,
|
cmd.argument,
|
||||||
operatorArguments
|
operatorArguments.withCount0(raw),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Invalid motion
|
// Invalid motion
|
||||||
@ -228,32 +233,22 @@ internal class MotionGroup : VimMotionGroupBase() {
|
|||||||
end++
|
end++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (cmd.action is TextObjectActionHandler) {
|
||||||
|
val action = cmd.action as TextObjectActionHandler
|
||||||
is TextObjectActionHandler -> {
|
val range =
|
||||||
val range = action.getRange(
|
action.getRange(editor.vim, caret.vim, IjEditorExecutionContext(context!!), cnt, raw) ?: return null
|
||||||
editor.vim,
|
|
||||||
caret.vim,
|
|
||||||
IjEditorExecutionContext(context!!),
|
|
||||||
operatorArguments.count1,
|
|
||||||
operatorArguments.count0
|
|
||||||
) ?: return null
|
|
||||||
start = range.startOffset
|
start = range.startOffset
|
||||||
end = range.endOffset
|
end = range.endOffset
|
||||||
if (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.
|
// 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) {
|
if (id == VimChangeGroupBase.VIM_MOTION_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_BIG_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_CAMEL_RIGHT) {
|
||||||
val text = editor.document.charsSequence.subSequence(start, end).toString()
|
val text = editor.document.charsSequence.subSequence(start, end).toString()
|
||||||
val lastNewLine = text.lastIndexOf('\n')
|
val lastNewLine = text.lastIndexOf('\n')
|
||||||
@ -263,7 +258,6 @@ internal class MotionGroup : VimMotionGroupBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextRange(start, end)
|
return TextRange(start, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,6 @@ internal class IjActionExecutor : VimActionExecutor {
|
|||||||
override val ACTION_EXPAND_REGION_RECURSIVELY: String
|
override val ACTION_EXPAND_REGION_RECURSIVELY: String
|
||||||
get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY
|
get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY
|
||||||
override val ACTION_EXPAND_COLLAPSE_TOGGLE: String
|
override val ACTION_EXPAND_COLLAPSE_TOGGLE: String
|
||||||
// [VERSION UPDATE] 2024.3+ Replace raw "ExpandCollapseToggleAction" with IdeActions.ACTION_EXPAND_COLLAPSE_TOGGLE_REGION from the platform.
|
|
||||||
get() = "ExpandCollapseToggleAction"
|
get() = "ExpandCollapseToggleAction"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
|||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.getLineEndForOffset
|
import com.maddyhome.idea.vim.api.getLineEndForOffset
|
||||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||||
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||||
@ -93,6 +94,6 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Editor.exitInsertMode(context: DataContext) {
|
internal fun Editor.exitInsertMode(context: DataContext, operatorArguments: OperatorArguments) {
|
||||||
VimPlugin.getChange().processEscape(IjVimEditor(this), IjEditorExecutionContext(context))
|
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.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
import com.maddyhome.idea.vim.group.visual.VisualChange
|
||||||
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
|
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
|
||||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
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.vimTestInputModel: TestInputModel? by userData()
|
||||||
|
|
||||||
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
|
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
|
||||||
internal var Editor.replaceMask: VimEditorReplaceMask? by userData()
|
|
||||||
|
|
||||||
internal var Caret.currentInsert: InsertSequence? by userData()
|
internal var Caret.currentInsert: InsertSequence? by userData()
|
||||||
internal val Caret.insertHistory: MutableList<InsertSequence> by userDataOr { mutableListOf() }
|
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.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.EditorListener
|
import com.maddyhome.idea.vim.common.EditorListener
|
||||||
import com.maddyhome.idea.vim.helper.inInsertMode
|
import com.maddyhome.idea.vim.helper.inInsertMode
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
@ -64,7 +65,7 @@ class IJEditorFocusListener : EditorListener {
|
|||||||
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
|
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
|
||||||
val mode = injector.vimState.mode
|
val mode = injector.vimState.mode
|
||||||
when (mode) {
|
when (mode) {
|
||||||
is Mode.INSERT -> editor.exitInsertMode(context)
|
is Mode.INSERT -> editor.exitInsertMode(context, OperatorArguments(false, 0, mode))
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,4 +79,3 @@ class IJEditorFocusListener : EditorListener {
|
|||||||
KeyHandler.getInstance().reset(editor)
|
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.Template
|
||||||
import com.intellij.codeInsight.template.TemplateEditingAdapter
|
import com.intellij.codeInsight.template.TemplateEditingAdapter
|
||||||
import com.intellij.codeInsight.template.TemplateManagerListener
|
import com.intellij.codeInsight.template.TemplateManagerListener
|
||||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
|
|
||||||
import com.intellij.codeInsight.template.impl.TemplateState
|
import com.intellij.codeInsight.template.impl.TemplateState
|
||||||
import com.intellij.codeInsight.template.impl.actions.NextVariableAction
|
|
||||||
import com.intellij.find.FindModelListener
|
import com.intellij.find.FindModelListener
|
||||||
import com.intellij.openapi.actionSystem.ActionManager
|
import com.intellij.openapi.actionSystem.ActionManager
|
||||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||||
@ -154,10 +152,6 @@ internal object IdeaSpecifics {
|
|||||||
KeyHandler.getInstance().reset(it.vim)
|
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
|
//endregion
|
||||||
|
|
||||||
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
|
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.getLineEndForOffset
|
||||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.group.EditorGroup
|
import com.maddyhome.idea.vim.group.EditorGroup
|
||||||
import com.maddyhome.idea.vim.group.FileGroup
|
import com.maddyhome.idea.vim.group.FileGroup
|
||||||
import com.maddyhome.idea.vim.group.IjOptions
|
import com.maddyhome.idea.vim.group.IjOptions
|
||||||
@ -394,8 +395,7 @@ internal object VimListenerManager {
|
|||||||
editor.vim.mode = Mode.NORMAL()
|
editor.vim.mode = Mode.NORMAL()
|
||||||
KeyHandler.getInstance().reset(editor.vim)
|
KeyHandler.getInstance().reset(editor.vim)
|
||||||
}
|
}
|
||||||
// Breaks relativenumber for some reason
|
injector.scroll.scrollCaretIntoView(editor.vim)
|
||||||
// injector.scroll.scrollCaretIntoView(editor.vim)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
|
@ -17,24 +17,6 @@ internal class IjLiveRange(val marker: RangeMarker) : LiveRange {
|
|||||||
|
|
||||||
override val endOffset: Int
|
override val endOffset: Int
|
||||||
get() = marker.endOffset
|
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
|
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.VimVisualPosition
|
||||||
import com.maddyhome.idea.vim.api.VirtualFile
|
import com.maddyhome.idea.vim.api.VirtualFile
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.IndentConfig
|
import com.maddyhome.idea.vim.common.IndentConfig
|
||||||
|
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
|
||||||
import com.maddyhome.idea.vim.common.LiveRange
|
import com.maddyhome.idea.vim.common.LiveRange
|
||||||
import com.maddyhome.idea.vim.common.ModeChangeListener
|
import com.maddyhome.idea.vim.common.ModeChangeListener
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
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.group.visual.vimSetSystemBlockSelectionSilently
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.StrictMode
|
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.getTopLevelEditor
|
||||||
import com.maddyhome.idea.vim.helper.inExMode
|
import com.maddyhome.idea.vim.helper.inExMode
|
||||||
import com.maddyhome.idea.vim.helper.isTemplateActive
|
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.vimChangeActionSwitchMode
|
||||||
import com.maddyhome.idea.vim.helper.vimLastSelectionType
|
import com.maddyhome.idea.vim.helper.vimLastSelectionType
|
||||||
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
|
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
|
// TBH, I don't like the names. Need to think a bit more about this
|
||||||
val editor = editor.getTopLevelEditor()
|
val editor = editor.getTopLevelEditor()
|
||||||
val originalEditor = editor
|
val originalEditor = editor
|
||||||
override var replaceMask: VimEditorReplaceMask?
|
|
||||||
get() = editor.replaceMask
|
|
||||||
set(value) {
|
|
||||||
editor.replaceMask = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateMode(mode: Mode) {
|
override fun updateMode(mode: Mode) {
|
||||||
(injector.vimState as VimStateMachineImpl).mode = mode
|
(injector.vimState as VimStateMachineImpl).mode = mode
|
||||||
@ -97,7 +91,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
|
|||||||
editor.vimChangeActionSwitchMode = value
|
editor.vimChangeActionSwitchMode = value
|
||||||
}
|
}
|
||||||
override val indentConfig: VimIndentConfig
|
override val indentConfig: VimIndentConfig
|
||||||
get() = IndentConfig.create(editor)
|
get() = create(editor)
|
||||||
|
|
||||||
override fun fileSize(): Long = editor.fileSize.toLong()
|
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))
|
return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exitInsertMode(context: ExecutionContext) {
|
override fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
editor.exitInsertMode(context.ij)
|
editor.exitInsertMode(context.ij, operatorArguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exitSelectModeNative(adjustCaret: Boolean) {
|
override fun exitSelectModeNative(adjustCaret: Boolean) {
|
||||||
@ -477,7 +471,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
|
|||||||
get() = (editor as? EditorEx)?.isInsertMode ?: false
|
get() = (editor as? EditorEx)?.isInsertMode ?: false
|
||||||
set(value) {
|
set(value) {
|
||||||
(editor as? EditorEx)?.isInsertMode = value
|
(editor as? EditorEx)?.isInsertMode = value
|
||||||
forgetAllReplaceMasks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val document: VimDocument
|
override val document: VimDocument
|
||||||
|
@ -192,13 +192,6 @@ private object VimActionsPopup {
|
|||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
actionGroup.add(
|
|
||||||
HelpLink(
|
|
||||||
"Take Survey ↗",
|
|
||||||
"https://surveys.jetbrains.com/s3/ideavim-usage-survey",
|
|
||||||
AllIcons.Actions.IntentionBulb,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
actionGroup.addSeparator(MessageHelper.message("action.eap.choice.active.text"))
|
actionGroup.addSeparator(MessageHelper.message("action.eap.choice.active.text"))
|
||||||
|
|
||||||
actionGroup.add(JoinEap)
|
actionGroup.add(JoinEap)
|
||||||
|
@ -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
|
// Get the current count from the command builder. This value is coerced to at least 1, so will always be valid.
|
||||||
// count components - selecting register(s), operator and motions. E.g. `2"a3"b4"c5d6/` will return 720.
|
// The aggregated value includes any counts for operator and register selections, and the uncommitted count for
|
||||||
// If we're showing highlights for an Ex command like `:s`, the command builder will be empty, but we'll still
|
// the search command (`/` or `?`). E.g., `2"a3"b4"c5d6/` would return 720.
|
||||||
// get a valid value.
|
// If we're showing highlights for an ex command like `:s`, there won't be a command, but the value is already
|
||||||
int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder()
|
// coerced to at least 1.
|
||||||
.calculateCount0Snapshot());
|
int count1 = KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getAggregatedUncommittedCount();
|
||||||
|
|
||||||
if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
|
if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
|
||||||
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
||||||
@ -531,8 +531,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
|
|
||||||
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public @NotNull VimCommandLineCaret getCaret() {
|
public VimCommandLineCaret getCaret() {
|
||||||
return (VimCommandLineCaret) entry.getCaret();
|
return (VimCommandLineCaret) entry.getCaret();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,8 +551,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
entry.clearCurrentAction();
|
entry.clearCurrentAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Integer getPromptCharacterOffset() {
|
public Integer getPromptCharacterOffset() {
|
||||||
int offset = entry.currentActionPromptCharacterOffset;
|
int offset = entry.currentActionPromptCharacterOffset;
|
||||||
return offset == -1 ? null : offset;
|
return offset == -1 ? null : offset;
|
||||||
}
|
}
|
||||||
@ -571,7 +573,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
IdeFocusManager.findInstance().requestFocus(entry, true);
|
IdeFocusManager.findInstance().requestFocus(entry, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable VimInputInterceptor<?> getInputInterceptor() {
|
@Nullable
|
||||||
|
public VimInputInterceptor<?> getInputInterceptor() {
|
||||||
return myInputInterceptor;
|
return myInputInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,13 +587,15 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
|||||||
myInputInterceptor = vimInputInterceptor;
|
myInputInterceptor = vimInputInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Function1<String, Unit> getInputProcessing() {
|
public Function1<String, Unit> getInputProcessing() {
|
||||||
return inputProcessing;
|
return inputProcessing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Character getFinishOn() {
|
public Character getFinishOn() {
|
||||||
return finishOn;
|
return finishOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,6 @@ viminfo
|
|||||||
virtualedit
|
virtualedit
|
||||||
visualbell
|
visualbell
|
||||||
visualdelay
|
visualdelay
|
||||||
whichwrap
|
|
||||||
wrapscan
|
wrapscan
|
||||||
|
|
||||||
nobomb
|
nobomb
|
||||||
|
@ -4,6 +4,16 @@
|
|||||||
"class": "com.maddyhome.idea.vim.action.change.RepeatChangeAction",
|
"class": "com.maddyhome.idea.vim.action.change.RepeatChangeAction",
|
||||||
"modes": "N"
|
"modes": "N"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"keys": "<BS>",
|
||||||
|
"class": "com.maddyhome.idea.vim.action.editor.VimEditorBackSpace",
|
||||||
|
"modes": "I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": "<C-H>",
|
||||||
|
"class": "com.maddyhome.idea.vim.action.editor.VimEditorBackSpace",
|
||||||
|
"modes": "I"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"keys": "<C-I>",
|
"keys": "<C-I>",
|
||||||
"class": "com.maddyhome.idea.vim.action.editor.VimEditorTab",
|
"class": "com.maddyhome.idea.vim.action.editor.VimEditorTab",
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jetbrains.plugins.ideavim.action
|
package org.jetbrains.plugins.ideavim.action
|
||||||
|
|
||||||
import com.intellij.idea.TestFor
|
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||||
@ -1069,14 +1068,4 @@ foobaz
|
|||||||
Mode.NORMAL(),
|
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)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
fun testYankRegisterUsesLastEnteredRegister() {
|
fun testYankRegisterUsesLastEnteredRegister() {
|
||||||
typeTextInFile("\"a\"byl" + "\"bp", "hel<caret>lo world\n")
|
typeTextInFile("\"a\"byl" + "\"ap", "hel<caret>lo world\n")
|
||||||
assertState("helllo world\n")
|
assertState("helllo world\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,33 +85,4 @@ class MotionBackspaceActionTest : VimTestCase() {
|
|||||||
enterCommand("set whichwrap=b")
|
enterCommand("set whichwrap=b")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestWithoutNeovim(SkipNeovimReason.OPTION)
|
|
||||||
@Test
|
|
||||||
fun `test backspace motion with operator`() {
|
|
||||||
doTest(
|
|
||||||
"d<BS>",
|
|
||||||
"""
|
|
||||||
lorem ${c}ipsum dolor sit amet
|
|
||||||
""".trimIndent(),
|
|
||||||
"""
|
|
||||||
lorem${c}ipsum dolor sit amet
|
|
||||||
""".trimIndent(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestWithoutNeovim(SkipNeovimReason.OPTION)
|
|
||||||
@Test
|
|
||||||
fun `test backspace motion with operator at start of line`() {
|
|
||||||
doTest(
|
|
||||||
"d<BS>",
|
|
||||||
"""
|
|
||||||
lorem ipsum dolor sit amet
|
|
||||||
${c}lorem ipsum dolor sit amet
|
|
||||||
""".trimIndent(),
|
|
||||||
"""
|
|
||||||
lorem ipsum dolor sit amet${c}lorem ipsum dolor sit amet
|
|
||||||
""".trimIndent(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -85,35 +85,4 @@ class MotionSpaceActionTest : VimTestCase() {
|
|||||||
enterCommand("set whichwrap=s")
|
enterCommand("set whichwrap=s")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("SpellCheckingInspection")
|
|
||||||
@TestWithoutNeovim(SkipNeovimReason.OPTION)
|
|
||||||
@Test
|
|
||||||
fun `test space motion with operator`() {
|
|
||||||
doTest(
|
|
||||||
"d<Space>",
|
|
||||||
"""
|
|
||||||
lorem ${c}ipsum dolor sit amet
|
|
||||||
""".trimIndent(),
|
|
||||||
"""
|
|
||||||
lorem ${c}psum dolor sit amet
|
|
||||||
""".trimIndent(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestWithoutNeovim(SkipNeovimReason.OPTION)
|
|
||||||
@Test
|
|
||||||
fun `test space motion with operator at end of line`() {
|
|
||||||
doTest(
|
|
||||||
"d<Space>",
|
|
||||||
"""
|
|
||||||
lorem ipsum dolor sit ame${c}t
|
|
||||||
lorem ipsum dolor sit amet
|
|
||||||
""".trimIndent(),
|
|
||||||
"""
|
|
||||||
lorem ipsum dolor sit am${c}e
|
|
||||||
lorem ipsum dolor sit amet
|
|
||||||
""".trimIndent(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ class VimVariableServiceTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test v count variable without count specified`() {
|
fun `test v count variable without count specified`() {
|
||||||
configureByText("\n")
|
configureByText("\n")
|
||||||
enterCommand("""nnoremap <expr> n ':echo ' .. v:count .. "\<CR>"""")
|
enterCommand("nnoremap <expr> n ':echo ' .. v:count .. \"\\<CR>\"")
|
||||||
typeText("n")
|
typeText("n")
|
||||||
assertExOutput("0")
|
assertExOutput("0")
|
||||||
}
|
}
|
||||||
@ -23,31 +23,15 @@ class VimVariableServiceTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test v count variable`() {
|
fun `test v count variable`() {
|
||||||
configureByText("\n")
|
configureByText("\n")
|
||||||
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count .. "\<CR>"""")
|
enterCommand("nnoremap <expr> n ':' .. \"\\<C-u>\" .. 'echo ' .. v:count .. \"\\<CR>\"")
|
||||||
typeText("5n")
|
typeText("5n")
|
||||||
assertExOutput("5")
|
assertExOutput("5")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test v count variable with additional count during select register`() {
|
|
||||||
configureByText("\n")
|
|
||||||
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count .. "\<CR>"""")
|
|
||||||
typeText("2\"a5n")
|
|
||||||
assertExOutput("10")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test v count variable with additional pathological count during select register`() {
|
|
||||||
configureByText("\n")
|
|
||||||
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count .. "\<CR>"""")
|
|
||||||
typeText("2\"a3\"b4\"c5n")
|
|
||||||
assertExOutput("120")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test v count1 variable without count specified`() {
|
fun `test v count1 variable without count specified`() {
|
||||||
configureByText("\n")
|
configureByText("\n")
|
||||||
enterCommand("""nnoremap <expr> n ':echo ' .. v:count1 .. "\<CR>"""")
|
enterCommand("nnoremap <expr> n ':echo ' .. v:count1 .. \"\\<CR>\"")
|
||||||
typeText("n")
|
typeText("n")
|
||||||
assertExOutput("1")
|
assertExOutput("1")
|
||||||
}
|
}
|
||||||
@ -55,27 +39,11 @@ class VimVariableServiceTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test v count1 variable`() {
|
fun `test v count1 variable`() {
|
||||||
configureByText("\n")
|
configureByText("\n")
|
||||||
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count1 .. "\<CR>"""")
|
enterCommand("nnoremap <expr> n ':' .. \"\\<C-u>\" .. 'echo ' .. v:count1 .. \"\\<CR>\"")
|
||||||
typeText("5n")
|
typeText("5n")
|
||||||
assertExOutput("5")
|
assertExOutput("5")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test v count1 variable with additional count during select register`() {
|
|
||||||
configureByText("\n")
|
|
||||||
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count1 .. "\<CR>"""")
|
|
||||||
typeText("2\"a5n")
|
|
||||||
assertExOutput("10")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test v count1 variable with additional pathological count during select register`() {
|
|
||||||
configureByText("\n")
|
|
||||||
enterCommand("""nnoremap <expr> n ':' .. "\<C-u>" .. 'echo ' .. v:count1 .. "\<CR>"""")
|
|
||||||
typeText("2\"a3\"b4\"c5n")
|
|
||||||
assertExOutput("120")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test mapping with updating jumplist`() {
|
fun `test mapping with updating jumplist`() {
|
||||||
configureByText("${c}1\n2\n3\n4\n5\n6\n7\n8\n9\n")
|
configureByText("${c}1\n2\n3\n4\n5\n6\n7\n8\n9\n")
|
||||||
|
@ -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
|
@Test
|
||||||
fun testSelectTwoArguments() {
|
fun testSelectTwoArguments() {
|
||||||
doTest(
|
doTest(
|
||||||
|
@ -95,7 +95,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
|
|||||||
val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie()
|
val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie()
|
||||||
|
|
||||||
// Note: esc is always an option
|
// Note: esc is always an option
|
||||||
val possibleKeys = (currentNode.children.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) }
|
val possibleKeys = (currentNode.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) }
|
||||||
println("Keys: ${possibleKeys.joinToString(", ")}")
|
println("Keys: ${possibleKeys.joinToString(", ")}")
|
||||||
val keyGenerator = Generator.integers(0, possibleKeys.lastIndex)
|
val keyGenerator = Generator.integers(0, possibleKeys.lastIndex)
|
||||||
.suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList }
|
.suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList }
|
||||||
|
@ -17,13 +17,14 @@ import com.maddyhome.idea.vim.command.CommandFlags
|
|||||||
import com.maddyhome.idea.vim.command.MappingMode
|
import com.maddyhome.idea.vim.command.MappingMode
|
||||||
import com.maddyhome.idea.vim.command.MappingProcessor
|
import com.maddyhome.idea.vim.command.MappingProcessor
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
|
import com.maddyhome.idea.vim.common.CurrentCommandState
|
||||||
import com.maddyhome.idea.vim.diagnostic.VimLogger
|
import com.maddyhome.idea.vim.diagnostic.VimLogger
|
||||||
import com.maddyhome.idea.vim.diagnostic.trace
|
import com.maddyhome.idea.vim.diagnostic.trace
|
||||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||||
import com.maddyhome.idea.vim.impl.state.toMappingMode
|
import com.maddyhome.idea.vim.impl.state.toMappingMode
|
||||||
|
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||||
import com.maddyhome.idea.vim.key.KeyConsumer
|
import com.maddyhome.idea.vim.key.KeyConsumer
|
||||||
import com.maddyhome.idea.vim.key.KeyStack
|
import com.maddyhome.idea.vim.key.KeyStack
|
||||||
import com.maddyhome.idea.vim.key.RootNode
|
|
||||||
import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer
|
import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer
|
||||||
import com.maddyhome.idea.vim.key.consumers.CommandConsumer
|
import com.maddyhome.idea.vim.key.consumers.CommandConsumer
|
||||||
import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer
|
import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer
|
||||||
@ -196,9 +197,11 @@ class KeyHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onUnknownKey(editor: VimEditor, keyState: KeyHandlerState) {
|
private fun onUnknownKey(editor: VimEditor, keyState: KeyHandlerState) {
|
||||||
|
logger.trace("Command builder is set to BAD")
|
||||||
|
keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
|
||||||
editor.resetOpPending()
|
editor.resetOpPending()
|
||||||
|
injector.vimState.resetRegisterPending()
|
||||||
editor.isReplaceCharacter = false
|
editor.isReplaceCharacter = false
|
||||||
// Note that this will also reset the CommandBuilder to NEW_COMMAND
|
|
||||||
reset(keyState, editor.mode)
|
reset(keyState, editor.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +210,14 @@ class KeyHandler {
|
|||||||
injector.messages.indicateError()
|
injector.messages.indicateError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode, keyState: KeyHandlerState): Boolean {
|
||||||
|
return isOperatorPending(mode, keyState) && keyState.commandBuilder.isDuplicateOperatorKeyStroke(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isOperatorPending(mode: Mode, keyState: KeyHandlerState): Boolean {
|
||||||
|
return mode is Mode.OP_PENDING && !keyState.commandBuilder.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
private fun executeCommand(
|
private fun executeCommand(
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
@ -215,7 +226,11 @@ class KeyHandler {
|
|||||||
) {
|
) {
|
||||||
logger.trace("Command execution")
|
logger.trace("Command execution")
|
||||||
val command = keyState.commandBuilder.buildCommand()
|
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.
|
// If we were in "operator pending" mode, reset back to normal mode.
|
||||||
// But opening command line should not reset operator pending mode (e.g. `d/foo`
|
// But opening command line should not reset operator pending mode (e.g. `d/foo`
|
||||||
@ -280,7 +295,7 @@ class KeyHandler {
|
|||||||
keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode()))
|
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)
|
return injector.keyGroup.getKeyRoot(mappingMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,7 +341,7 @@ class KeyHandler {
|
|||||||
) : Runnable {
|
) : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val editorState = injector.vimState
|
val editorState = injector.vimState
|
||||||
|
keyState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
|
||||||
val register = cmd.register
|
val register = cmd.register
|
||||||
if (register != null) {
|
if (register != null) {
|
||||||
injector.registerGroup.selectRegister(register)
|
injector.registerGroup.selectRegister(register)
|
||||||
@ -346,15 +361,22 @@ class KeyHandler {
|
|||||||
// mode we were in. This handles commands in those modes that temporarily allow us to execute normal
|
// mode we were in. This handles commands in those modes that temporarily allow us to execute normal
|
||||||
// mode commands. An exception is if this command should leave us in the temporary mode such as
|
// mode commands. An exception is if this command should leave us in the temporary mode such as
|
||||||
// "select register"
|
// "select register"
|
||||||
if (editorState.mode is Mode.NORMAL && !cmd.flags.contains(CommandFlags.FLAG_EXPECT_MORE)) {
|
val myMode = editorState.mode
|
||||||
when (editorState.mode.returnTo) {
|
val returnTo = myMode.returnTo
|
||||||
ReturnTo.INSERT -> editor.mode = Mode.INSERT
|
if (myMode is Mode.NORMAL && returnTo != null && !cmd.flags.contains(CommandFlags.FLAG_EXPECT_MORE)) {
|
||||||
ReturnTo.REPLACE -> editor.mode = Mode.REPLACE
|
when (returnTo) {
|
||||||
null -> {}
|
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.api.lineLength
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.diagnostic.debug
|
import com.maddyhome.idea.vim.diagnostic.debug
|
||||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||||
|
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["r"], modes = [Mode.NORMAL])
|
@CommandOrMotion(keys = ["r"], modes = [Mode.NORMAL])
|
||||||
class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() {
|
class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() {
|
||||||
override val type: Command.Type = Command.Type.CHANGE
|
override val type: Command.Type = Command.Type.CHANGE
|
||||||
|
|
||||||
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
||||||
|
|
||||||
|
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
|
||||||
|
|
||||||
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||||
editor.isReplaceCharacter = true
|
editor.isReplaceCharacter = true
|
||||||
}
|
}
|
||||||
@ -39,7 +45,7 @@ class ChangeCharacterAction : ChangeEditorActionHandler.ForEachCaret() {
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): 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 {
|
): Boolean {
|
||||||
// `S` command is a synonym of `cc`
|
// `S` command is a synonym of `cc`
|
||||||
val motion = MotionDownLess1FirstNonSpaceAction()
|
val motion = MotionDownLess1FirstNonSpaceAction()
|
||||||
|
val command = Command(1, motion, motion.type, motion.flags)
|
||||||
return injector.changeGroup.changeMotion(
|
return injector.changeGroup.changeMotion(
|
||||||
editor,
|
editor,
|
||||||
caret,
|
caret,
|
||||||
context,
|
context,
|
||||||
Argument.Motion(motion, null),
|
Argument(command),
|
||||||
operatorArguments,
|
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.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
|
||||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["R"], modes = [Mode.NORMAL])
|
@CommandOrMotion(keys = ["R"], modes = [Mode.NORMAL])
|
||||||
@ -40,5 +39,4 @@ class ChangeReplaceAction : ChangeEditorActionHandler.SingleExecution() {
|
|||||||
*/
|
*/
|
||||||
private fun changeReplace(editor: VimEditor, context: ExecutionContext) {
|
private fun changeReplace(editor: VimEditor, context: ExecutionContext) {
|
||||||
injector.changeGroup.initInsert(editor, context, com.maddyhome.idea.vim.state.mode.Mode.REPLACE)
|
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.toVimTextRange(false),
|
||||||
range.type,
|
range.type,
|
||||||
context,
|
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.api.injector
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.diagnostic.debug
|
import com.maddyhome.idea.vim.diagnostic.debug
|
||||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||||
|
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author vlan
|
* @author vlan
|
||||||
@ -29,8 +32,11 @@ import com.maddyhome.idea.vim.state.KeyHandlerState
|
|||||||
@CommandOrMotion(keys = ["r"], modes = [Mode.VISUAL])
|
@CommandOrMotion(keys = ["r"], modes = [Mode.VISUAL])
|
||||||
class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() {
|
class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||||
override val type: Command.Type = Command.Type.CHANGE
|
override val type: Command.Type = Command.Type.CHANGE
|
||||||
|
|
||||||
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
||||||
|
|
||||||
|
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
|
||||||
|
|
||||||
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
override fun onStartWaitingForArgument(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
|
||||||
editor.isReplaceCharacter = true
|
editor.isReplaceCharacter = true
|
||||||
}
|
}
|
||||||
@ -44,7 +50,7 @@ class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCaret() {
|
|||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val argument = cmd.argument
|
val argument = cmd.argument
|
||||||
return argument is Argument.Character &&
|
return argument != null &&
|
||||||
changeCharacterRange(editor, caret, range.toVimTextRange(false), argument.character)
|
changeCharacterRange(editor, caret, range.toVimTextRange(false), argument.character)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ class ChangeVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
|
|||||||
lineRange,
|
lineRange,
|
||||||
SelectionType.LINE_WISE,
|
SelectionType.LINE_WISE,
|
||||||
context,
|
context,
|
||||||
|
operatorArguments,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ class ChangeVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val blockRange = TextRange(starts, ends)
|
val blockRange = TextRange(starts, ends)
|
||||||
injector.changeGroup.changeRange(editor, caret, blockRange, SelectionType.BLOCK_WISE, context)
|
injector.changeGroup.changeRange(editor, caret, blockRange, SelectionType.BLOCK_WISE, context, operatorArguments)
|
||||||
} else {
|
} else {
|
||||||
val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset)
|
val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset)
|
||||||
val endsWithNewLine = if (lineEndForOffset.toLong() == editor.fileSize()) 0 else 1
|
val endsWithNewLine = if (lineEndForOffset.toLong() == editor.fileSize()) 0 else 1
|
||||||
@ -61,7 +61,7 @@ class ChangeVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
|
|||||||
editor.getLineStartForOffset(vimTextRange.startOffset),
|
editor.getLineStartForOffset(vimTextRange.startOffset),
|
||||||
lineEndForOffset + endsWithNewLine,
|
lineEndForOffset + endsWithNewLine,
|
||||||
)
|
)
|
||||||
injector.changeGroup.changeRange(editor, caret, lineRange, SelectionType.LINE_WISE, context)
|
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 {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
// Start ex entry with the initial text set to the calculated range and `!`
|
// Start ex entry with the initial text set to the calculated range and `!`
|
||||||
startFilterCommand(editor, context, cmd.rawCount)
|
startFilterCommand(editor, context, cmd)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,13 +63,13 @@ class FilterMotionAction : VimActionHandler.SingleExecution(), FilterCommand, Du
|
|||||||
|
|
||||||
// Start ex entry with the initial text set to the calculated range and `!`
|
// Start ex entry with the initial text set to the calculated range and `!`
|
||||||
val count = if (start.line < end.line) end.line - start.line + 1 else 1
|
val count = if (start.line < end.line) end.line - start.line + 1 else 1
|
||||||
startFilterCommand(editor, context, count)
|
startFilterCommand(editor, context, Argument.EMPTY_COMMAND.copy(rawCount = count))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterCommand {
|
interface FilterCommand {
|
||||||
fun startFilterCommand(editor: VimEditor, context: ExecutionContext, count0: Int) {
|
fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||||
injector.commandLine.createCommandPrompt(editor, context, count0, initialText = "!")
|
injector.commandLine.createCommandPrompt(editor, context, cmd, initialText = "!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,6 @@ class DeleteMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO
|
|||||||
val (range, selectionType) = injector.changeGroup
|
val (range, selectionType) = injector.changeGroup
|
||||||
.getDeleteRangeAndType(editor, caret, context, argument, false, operatorArguments)
|
.getDeleteRangeAndType(editor, caret, context, argument, false, operatorArguments)
|
||||||
?: return false
|
?: return false
|
||||||
return injector.changeGroup.deleteRange(editor, caret, range, selectionType, false)
|
return injector.changeGroup.deleteRange(editor, caret, range, selectionType, false, operatorArguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ class DeleteVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
|||||||
range.toVimTextRange(false),
|
range.toVimTextRange(false),
|
||||||
selectionType,
|
selectionType,
|
||||||
false,
|
false,
|
||||||
|
operatorArguments,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,6 @@ class DeleteVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
|
|||||||
Triple(caret, lineRange, SelectionType.LINE_WISE)
|
Triple(caret, lineRange, SelectionType.LINE_WISE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return injector.changeGroup.deleteRange(editor, usedCaret, usedRange, usedType, false)
|
return injector.changeGroup.deleteRange(editor, usedCaret, usedRange, usedType, false, operatorArguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@ class DeleteVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
|
|||||||
blockRange,
|
blockRange,
|
||||||
SelectionType.BLOCK_WISE,
|
SelectionType.BLOCK_WISE,
|
||||||
false,
|
false,
|
||||||
|
operatorArguments,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset)
|
val lineEndForOffset = editor.getLineEndForOffset(vimTextRange.endOffset)
|
||||||
@ -66,7 +67,7 @@ class DeleteVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
|
|||||||
editor.getLineStartForOffset(vimTextRange.startOffset),
|
editor.getLineStartForOffset(vimTextRange.startOffset),
|
||||||
lineEndForOffset + endsWithNewLine,
|
lineEndForOffset + endsWithNewLine,
|
||||||
)
|
)
|
||||||
injector.changeGroup.deleteRange(editor, caret, lineRange, SelectionType.LINE_WISE, false)
|
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 {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
// The converted digraph character has been captured as an argument, push it back through key handler
|
// The converted digraph character has been captured as an argument, push it back through key handler
|
||||||
val argument = cmd.argument as? Argument.Character ?: return false
|
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
|
||||||
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
|
||||||
val keyHandler = KeyHandler.getInstance()
|
val keyHandler = KeyHandler.getInstance()
|
||||||
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
|
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
|
||||||
return true
|
return true
|
||||||
|
@ -52,8 +52,7 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
|
|||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
// The converted literal character has been captured as an argument, push it back through key handler
|
// The converted literal character has been captured as an argument, push it back through key handler
|
||||||
val argument = cmd.argument as? Argument.Character ?: return false
|
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
|
||||||
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
|
||||||
val keyHandler = KeyHandler.getInstance()
|
val keyHandler = KeyHandler.getInstance()
|
||||||
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
|
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
|
||||||
return true
|
return true
|
||||||
|
@ -36,7 +36,7 @@ class InsertDeleteInsertedTextAction : ChangeEditorActionHandler.ForEachCaret()
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): 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
|
* @param caret The caret on which the action is performed
|
||||||
* @return true if able to delete the text, false if not
|
* @return true if able to delete the text, false if not
|
||||||
*/
|
*/
|
||||||
private fun insertDeleteInsertedText(editor: VimEditor, caret: VimCaret): Boolean {
|
private fun insertDeleteInsertedText(
|
||||||
|
editor: VimEditor,
|
||||||
|
caret: VimCaret,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
|
): Boolean {
|
||||||
var deleteTo = caret.vimInsertStart.startOffset
|
var deleteTo = caret.vimInsertStart.startOffset
|
||||||
val offset = caret.offset
|
val offset = caret.offset
|
||||||
if (offset == deleteTo) {
|
if (offset == deleteTo) {
|
||||||
@ -61,6 +65,7 @@ private fun insertDeleteInsertedText(editor: VimEditor, caret: VimCaret): Boolea
|
|||||||
TextRange(deleteTo, offset),
|
TextRange(deleteTo, offset),
|
||||||
SelectionType.CHARACTER_WISE,
|
SelectionType.CHARACTER_WISE,
|
||||||
false,
|
false,
|
||||||
|
operatorArguments,
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ class InsertDeletePreviousWordAction : ChangeEditorActionHandler.ForEachCaret()
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): 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
|
* @param editor The editor to delete the text from
|
||||||
* @return true if able to delete text, false if not
|
* @return true if able to delete text, false if not
|
||||||
*/
|
*/
|
||||||
private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret): Boolean {
|
private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret, operatorArguments: OperatorArguments): Boolean {
|
||||||
val deleteTo: Int = if (caret.getBufferPosition().column == 0) {
|
val deleteTo: Int = if (caret.getBufferPosition().column == 0) {
|
||||||
caret.offset - 1
|
caret.offset - 1
|
||||||
} else {
|
} else {
|
||||||
@ -74,6 +74,6 @@ private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret): Boolea
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
val range = TextRange(deleteTo, caret.offset)
|
val range = TextRange(deleteTo, caret.offset)
|
||||||
injector.changeGroup.deleteRange(editor, caret, range, SelectionType.CHARACTER_WISE, true)
|
injector.changeGroup.deleteRange(editor, caret, range, SelectionType.CHARACTER_WISE, true, operatorArguments)
|
||||||
return true
|
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.ex.ExException
|
||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||||
import com.maddyhome.idea.vim.helper.RWLockLabel
|
import com.maddyhome.idea.vim.helper.RWLockLabel
|
||||||
|
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||||
|
import com.maddyhome.idea.vim.key.interceptors.VimInputInterceptorBase
|
||||||
import com.maddyhome.idea.vim.put.PutData
|
import com.maddyhome.idea.vim.put.PutData
|
||||||
import com.maddyhome.idea.vim.register.Register
|
import com.maddyhome.idea.vim.register.Register
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
import com.maddyhome.idea.vim.vimscript.model.Script
|
import com.maddyhome.idea.vim.vimscript.model.Script
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.INSERT])
|
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.INSERT])
|
||||||
class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
||||||
@ -35,8 +39,9 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
|||||||
cmd: Command,
|
cmd: Command,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val argument = cmd.argument as? Argument.Character ?: return false
|
val argument = cmd.argument
|
||||||
if (argument.character == '=') {
|
|
||||||
|
if (argument?.character == '=') {
|
||||||
injector.commandLine.readInputAndProcess(editor, context, "=", finishOn = null) { input ->
|
injector.commandLine.readInputAndProcess(editor, context, "=", finishOn = null) { input ->
|
||||||
try {
|
try {
|
||||||
if (input.isNotEmpty()) {
|
if (input.isNotEmpty()) {
|
||||||
@ -45,7 +50,7 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
|||||||
val textToStore = expression.toInsertableString()
|
val textToStore = expression.toInsertableString()
|
||||||
injector.registerGroup.storeTextSpecial('=', textToStore)
|
injector.registerGroup.storeTextSpecial('=', textToStore)
|
||||||
}
|
}
|
||||||
insertRegister(editor, context, '=')
|
insertRegister(editor, context, '=', operatorArguments)
|
||||||
} catch (e: ExException) {
|
} catch (e: ExException) {
|
||||||
injector.messages.indicateError()
|
injector.messages.indicateError()
|
||||||
injector.messages.showStatusBarMessage(editor, e.message)
|
injector.messages.showStatusBarMessage(editor, e.message)
|
||||||
@ -53,7 +58,7 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} 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
|
* @return true if able to insert the register contents, false if not
|
||||||
*/
|
*/
|
||||||
@RWLockLabel.SelfSynchronized
|
@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)
|
val register: Register? = injector.registerGroup.getRegister(key)
|
||||||
if (register != null) {
|
if (register != null) {
|
||||||
val text = register.rawText ?: injector.parser.toPrintableString(register.keys)
|
val text = register.rawText ?: injector.parser.toPrintableString(register.keys)
|
||||||
val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), register.name)
|
val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), register.name)
|
||||||
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
|
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
|
||||||
injector.put.putText(editor, context, putData)
|
injector.put.putText(editor, context, putData, operatorArguments = operatorArguments)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -36,7 +36,7 @@ class VisualBlockAppendAction : VisualOperatorActionHandler.SingleExecution() {
|
|||||||
if (editor.isOneLineMode()) return false
|
if (editor.isOneLineMode()) return false
|
||||||
val range = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false
|
val range = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false
|
||||||
return if (range.type == SelectionType.BLOCK_WISE) {
|
return if (range.type == SelectionType.BLOCK_WISE) {
|
||||||
injector.changeGroup.initBlockInsert(editor, context, range.toVimTextRange(false), true)
|
injector.changeGroup.blockInsert(editor, context, range.toVimTextRange(false), true, operatorArguments)
|
||||||
} else {
|
} else {
|
||||||
injector.changeGroup.insertAfterLineEnd(editor, context)
|
injector.changeGroup.insertAfterLineEnd(editor, context)
|
||||||
true
|
true
|
||||||
|
@ -36,7 +36,7 @@ class VisualBlockInsertAction : VisualOperatorActionHandler.SingleExecution() {
|
|||||||
if (editor.isOneLineMode()) return false
|
if (editor.isOneLineMode()) return false
|
||||||
val vimSelection = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false
|
val vimSelection = caretsAndSelections.values.stream().findFirst().orElse(null) ?: return false
|
||||||
return if (vimSelection.type == SelectionType.BLOCK_WISE) {
|
return if (vimSelection.type == SelectionType.BLOCK_WISE) {
|
||||||
injector.changeGroup.initBlockInsert(editor, context, vimSelection.toVimTextRange(false), false)
|
injector.changeGroup.blockInsert(editor, context, vimSelection.toVimTextRange(false), false, operatorArguments)
|
||||||
} else {
|
} else {
|
||||||
injector.changeGroup.insertBeforeFirstNonBlank(editor, context)
|
injector.changeGroup.insertBeforeFirstNonBlank(editor, context)
|
||||||
true
|
true
|
||||||
|
@ -52,7 +52,7 @@ sealed class PutTextBaseAction(
|
|||||||
}
|
}
|
||||||
result
|
result
|
||||||
} else {
|
} 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 {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
if (editor.isOneLineMode()) return false
|
if (editor.isOneLineMode()) return false
|
||||||
injector.commandLine.createCommandPrompt(editor, context, cmd.rawCount, initialText = "")
|
injector.commandLine.createCommandPrompt(editor, context, cmd, initialText = "")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,7 @@ class InsertRegisterAction: VimActionHandler.SingleExecution() {
|
|||||||
|
|
||||||
val caretOffset = cmdLine.caret.offset
|
val caretOffset = cmdLine.caret.offset
|
||||||
|
|
||||||
val argument = cmd.argument as? Argument.Character ?: return false
|
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
|
||||||
val keyStroke = KeyStroke.getKeyStroke(argument.character)
|
|
||||||
val pasteContent = if ((keyStroke.modifiers and KeyEvent.CTRL_DOWN_MASK) == 0) {
|
val pasteContent = if ((keyStroke.modifiers and KeyEvent.CTRL_DOWN_MASK) == 0) {
|
||||||
injector.registerGroup.getRegister(keyStroke.keyChar)?.text
|
injector.registerGroup.getRegister(keyStroke.keyChar)?.text
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,7 +13,6 @@ import com.intellij.vim.annotations.Mode
|
|||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
@ -28,8 +27,8 @@ class LeaveCommandLineAction : VimActionHandler.SingleExecution() {
|
|||||||
override val type: Command.Type = Command.Type.OTHER_READONLY
|
override val type: Command.Type = Command.Type.OTHER_READONLY
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
val argument = cmd.argument as? Argument.ExString ?: return true
|
val argument = cmd.argument ?: return true
|
||||||
val historyType = VimHistory.Type.getTypeByLabel(argument.label.toString())
|
val historyType = VimHistory.Type.getTypeByLabel(argument.character.toString())
|
||||||
injector.historyGroup.addEntry(historyType, argument.string)
|
injector.historyGroup.addEntry(historyType, argument.string)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,8 @@ class ProcessExEntryAction : MotionActionHandler.AmbiguousExecution() {
|
|||||||
override var motionType: MotionType = MotionType.EXCLUSIVE
|
override var motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
|
|
||||||
override fun getMotionActionHandler(argument: Argument?): MotionActionHandler {
|
override fun getMotionActionHandler(argument: Argument?): MotionActionHandler {
|
||||||
check(argument is Argument.ExString)
|
if (argument?.processing != null) return ExecuteDefinedInputProcessingAction()
|
||||||
if (argument.processing != null) return ExecuteDefinedInputProcessingAction()
|
return if (argument?.character == ':') ProcessExCommandEntryAction() else ProcessSearchEntryAction(this)
|
||||||
return if (argument.label == ':') ProcessExCommandEntryAction() else ProcessSearchEntryAction(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ class ExecuteDefinedInputProcessingAction : MotionActionHandler.SingleExecution(
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.ExString) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
val input = argument.string
|
val input = argument.string
|
||||||
val processing = argument.processing!!
|
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")
|
get() = throw RuntimeException("Parent motion type should be used, as only it is accessed by other code")
|
||||||
|
|
||||||
override fun getOffset(editor: VimEditor, caret: ImmutableVimCaret, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion {
|
override fun getOffset(editor: VimEditor, caret: ImmutableVimCaret, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion {
|
||||||
if (argument !is Argument.ExString) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
val offsetAndMotion = when (argument.label) {
|
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.FORWARDS)
|
||||||
'?' -> injector.searchGroup.processSearchCommand(editor, argument.string, caret.offset, operatorArguments.count1, Direction.BACKWARDS)
|
'?' -> injector.searchGroup.processSearchCommand(editor, argument.string, caret.offset, operatorArguments.count1, Direction.BACKWARDS)
|
||||||
else -> throw ExException("Unexpected search label ${argument.label}")
|
else -> throw ExException("Unexpected search label ${argument.character}")
|
||||||
}
|
}
|
||||||
if (offsetAndMotion == null) return Motion.Error
|
if (offsetAndMotion == null) return Motion.Error
|
||||||
parentAction.motionType = offsetAndMotion.second
|
parentAction.motionType = offsetAndMotion.second
|
||||||
@ -79,7 +78,7 @@ class ProcessExCommandEntryAction : MotionActionHandler.SingleExecution() {
|
|||||||
override val motionType: MotionType = MotionType.LINE_WISE
|
override val motionType: MotionType = MotionType.LINE_WISE
|
||||||
|
|
||||||
override fun getOffset(editor: VimEditor, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion {
|
override fun getOffset(editor: VimEditor, context: ExecutionContext, argument: Argument?, operatorArguments: OperatorArguments): Motion {
|
||||||
if (argument !is Argument.ExString) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Exit Command-line mode and return to the previous mode before executing the command (this is set to Normal in
|
// Exit Command-line mode and return to the previous mode before executing the command (this is set to Normal in
|
||||||
|
@ -31,7 +31,7 @@ class PlaybackRegisterAction : VimActionHandler.SingleExecution() {
|
|||||||
cmd: Command,
|
cmd: Command,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val argument = cmd.argument as? Argument.Character ?: return false
|
val argument = cmd.argument ?: return false
|
||||||
val reg = argument.character
|
val reg = argument.character
|
||||||
val application = injector.application
|
val application = injector.application
|
||||||
val res = arrayOf(false)
|
val res = arrayOf(false)
|
||||||
@ -49,7 +49,7 @@ class PlaybackRegisterAction : VimActionHandler.SingleExecution() {
|
|||||||
if (reg != '@') { // @ is not a register itself, it just tells vim to use the last register
|
if (reg != '@') { // @ is not a register itself, it just tells vim to use the last register
|
||||||
injector.macro.lastRegister = reg
|
injector.macro.lastRegister = reg
|
||||||
}
|
}
|
||||||
} catch (_: ExException) {
|
} catch (e: ExException) {
|
||||||
res[0] = false
|
res[0] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ class ToggleRecordingAction : VimActionHandler.SingleExecution() {
|
|||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
return if (!injector.registerGroup.isRecording) {
|
return if (!injector.registerGroup.isRecording) {
|
||||||
val argument = cmd.argument as? Argument.Character ?: return false
|
val argument = cmd.argument ?: return false
|
||||||
val reg = argument.character
|
val reg = argument.character
|
||||||
injector.registerGroup.startRecording(reg)
|
injector.registerGroup.startRecording(reg)
|
||||||
} else {
|
} 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.MotionType
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
|
||||||
import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler
|
import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler
|
||||||
|
|
||||||
private fun doMotion(
|
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||||
editor: VimEditor,
|
class MotionArrowLeftAction : NonShiftedSpecialKeyHandler() {
|
||||||
caret: ImmutableVimCaret,
|
|
||||||
count1: Int,
|
|
||||||
whichwrapKey: String,
|
|
||||||
allowPastEnd: Boolean,
|
|
||||||
): Motion {
|
|
||||||
val allowWrap = injector.options(editor).whichwrap.contains(whichwrapKey)
|
|
||||||
return injector.motion.getHorizontalMotion(editor, caret, count1, allowPastEnd, allowWrap)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class MotionNonShiftedArrowLeftBaseAction() : NonShiftedSpecialKeyHandler() {
|
|
||||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
|
|
||||||
override fun motion(
|
override fun motion(
|
||||||
@ -43,38 +32,8 @@ abstract class MotionNonShiftedArrowLeftBaseAction() : NonShiftedSpecialKeyHandl
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
return doMotion(editor, caret, -operatorArguments.count1, "<", allowPastEnd)
|
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)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,23 +19,12 @@ import com.maddyhome.idea.vim.command.Argument
|
|||||||
import com.maddyhome.idea.vim.command.MotionType
|
import com.maddyhome.idea.vim.command.MotionType
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
|
||||||
import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler
|
import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler
|
||||||
import com.maddyhome.idea.vim.helper.isEndAllowed
|
import com.maddyhome.idea.vim.helper.isEndAllowed
|
||||||
import com.maddyhome.idea.vim.helper.usesVirtualSpace
|
import com.maddyhome.idea.vim.helper.usesVirtualSpace
|
||||||
|
|
||||||
private fun doMotion(
|
@CommandOrMotion(keys = ["<Right>", "<kRight>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||||
editor: VimEditor,
|
class MotionArrowRightAction : NonShiftedSpecialKeyHandler() {
|
||||||
caret: ImmutableVimCaret,
|
|
||||||
count1: Int,
|
|
||||||
whichwrapKey: String,
|
|
||||||
allowPastEnd: Boolean,
|
|
||||||
): Motion {
|
|
||||||
val allowWrap = injector.options(editor).whichwrap.contains(whichwrapKey)
|
|
||||||
return injector.motion.getHorizontalMotion(editor, caret, count1, allowPastEnd, allowWrap)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class MotionNonShiftedArrowRightBaseAction() : NonShiftedSpecialKeyHandler() {
|
|
||||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
|
|
||||||
override fun motion(
|
override fun motion(
|
||||||
@ -45,38 +34,9 @@ abstract class MotionNonShiftedArrowRightBaseAction() : NonShiftedSpecialKeyHand
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
return doMotion(editor, caret, operatorArguments.count1, ">", allowPastEnd(editor))
|
val allowPastEnd = editor.usesVirtualSpace || editor.isEndAllowed ||
|
||||||
}
|
operatorArguments.isOperatorPending // because of `d<Right>` removing the last character
|
||||||
|
val allowWrap = injector.options(editor).whichwrap.contains(">")
|
||||||
protected open fun allowPastEnd(editor: VimEditor) = editor.usesVirtualSpace || editor.isEndAllowed
|
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd, allowWrap)
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
|||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
@CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||||
open class MotionBackspaceAction(private val allowPastEnd: Boolean = false) : MotionActionHandler.ForEachCaret() {
|
class MotionBackspaceAction : MotionActionHandler.ForEachCaret() {
|
||||||
override fun getOffset(
|
override fun getOffset(
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
caret: ImmutableVimCaret,
|
caret: ImmutableVimCaret,
|
||||||
@ -30,15 +30,24 @@ open class MotionBackspaceAction(private val allowPastEnd: Boolean = false) : Mo
|
|||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
val allowWrap = injector.options(editor).whichwrap.contains("b")
|
val allowWrap = injector.options(editor).whichwrap.contains("b")
|
||||||
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowPastEnd, allowWrap)
|
return injector.motion.getHorizontalMotion(editor, caret, -operatorArguments.count1, allowPastEnd = false, allowWrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the motion is used with an operator, the EOL character is counted.
|
@CommandOrMotion(keys = ["<Space>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
|
||||||
// This allows e.g., `d<BS>` to delete the end of line character on the previous line when wrap is active
|
class MotionSpaceAction : MotionActionHandler.ForEachCaret() {
|
||||||
// ('whichwrap' contains "b")
|
override fun getOffset(
|
||||||
// See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
|
editor: VimEditor,
|
||||||
@CommandOrMotion(keys = ["<BS>", "<C-H>"], modes = [Mode.OP_PENDING])
|
caret: ImmutableVimCaret,
|
||||||
class MotionBackspaceOpPendingModeAction : MotionBackspaceAction(allowPastEnd = true)
|
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 com.maddyhome.idea.vim.helper.isEndAllowed
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
abstract class MotionLastColumnBaseAction(private val isMotionForOperator: Boolean = false)
|
@CommandOrMotion(keys = ["<End>"], modes = [Mode.INSERT])
|
||||||
: MotionActionHandler.ForEachCaret() {
|
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 val motionType: MotionType = MotionType.INCLUSIVE
|
||||||
|
|
||||||
override fun getOffset(
|
override fun getOffset(
|
||||||
@ -39,26 +43,13 @@ abstract class MotionLastColumnBaseAction(private val isMotionForOperator: Boole
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
val allowPastEnd = if (editor.inVisualMode) {
|
val allow = if (editor.inVisualMode) {
|
||||||
injector.options(editor).selection != "old"
|
injector.options(editor).selection != "old"
|
||||||
} else {
|
} 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
|
if (operatorArguments.isOperatorPending) false else editor.isEndAllowed
|
||||||
// the end of line character
|
|
||||||
if (isMotionForOperator) 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)
|
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.Motion
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
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 val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
|
|
||||||
override fun getOffset(
|
override fun getOffset(
|
||||||
@ -32,16 +33,23 @@ abstract class MotionLeftBaseAction(private val allowPastEnd: Boolean) : MotionA
|
|||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
val allowWrap = injector.options(editor).whichwrap.contains("h")
|
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])
|
@CommandOrMotion(keys = ["<Left>", "<kLeft>"], modes = [Mode.INSERT])
|
||||||
class MotionLeftAction : MotionLeftBaseAction(allowPastEnd = false)
|
class MotionLeftInsertModeAction : MotionActionHandler.ForEachCaret() {
|
||||||
|
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
|
|
||||||
// When the motion is used with an operator, the EOL character is counted.
|
override fun getOffset(
|
||||||
// This allows e.g., `dh` to delete the end of line character on the previous line when wrap is active
|
editor: VimEditor,
|
||||||
// ('whichwrap' contains "h")
|
caret: ImmutableVimCaret,
|
||||||
// See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
|
context: ExecutionContext,
|
||||||
@CommandOrMotion(keys = ["h"], modes = [Mode.OP_PENDING])
|
argument: Argument?,
|
||||||
class MotionLeftOpPendingModeAction : MotionLeftBaseAction(allowPastEnd = true)
|
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.isEndAllowed
|
||||||
import com.maddyhome.idea.vim.helper.usesVirtualSpace
|
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 val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
|
|
||||||
override fun getOffset(
|
override fun getOffset(
|
||||||
@ -34,20 +35,24 @@ abstract class MotionRightBaseAction() : MotionActionHandler.ForEachCaret() {
|
|||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
val allowWrap = injector.options(editor).whichwrap.contains("l")
|
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])
|
override fun getOffset(
|
||||||
class MotionRightAction : MotionRightBaseAction()
|
editor: VimEditor,
|
||||||
|
caret: ImmutableVimCaret,
|
||||||
@CommandOrMotion(keys = ["l"], modes = [Mode.OP_PENDING])
|
context: ExecutionContext,
|
||||||
class MotionRightOpPendingAction : MotionRightBaseAction() {
|
argument: Argument?,
|
||||||
// When the motion is used with an operator, the EOL character is counted.
|
operatorArguments: OperatorArguments,
|
||||||
// This allows e.g., `dl` to delete the last character in a line. Note that we can't use editor.isEndAllowed to give
|
): Motion {
|
||||||
// us this because the current mode when we execute the operator/motion is no longer OP_PENDING.
|
val allowWrap = injector.options(editor).whichwrap.contains("]")
|
||||||
// See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
|
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd = true, allowWrap)
|
||||||
override fun allowPastEnd(editor: VimEditor) = true
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,19 +18,23 @@ import com.maddyhome.idea.vim.api.moveToMotion
|
|||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.handler.ShiftedArrowKeyHandler
|
import com.maddyhome.idea.vim.handler.ShiftedArrowKeyHandler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Alex Plate
|
||||||
|
*/
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<S-Left>"], modes = [Mode.INSERT, Mode.NORMAL, Mode.VISUAL, Mode.SELECT])
|
@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 val type: Command.Type = Command.Type.OTHER_READONLY
|
||||||
|
|
||||||
override fun motionWithKeyModel(editor: VimEditor, caret: VimCaret, context: ExecutionContext, cmd: Command) {
|
override fun motionWithKeyModel(editor: VimEditor, caret: VimCaret, context: ExecutionContext, cmd: Command) {
|
||||||
val motion = injector.motion.getHorizontalMotion(editor, caret, -cmd.count, true)
|
val vertical = injector.motion.getHorizontalMotion(editor, caret, -cmd.count, true)
|
||||||
caret.moveToMotion(motion)
|
caret.moveToMotion(vertical)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||||
val caret = editor.currentCaret()
|
val caret = editor.currentCaret()
|
||||||
val motion = injector.motion.findOffsetOfNextWord(editor, caret.offset, -cmd.count, false)
|
val newOffset = injector.motion.findOffsetOfNextWord(editor, caret.offset, -cmd.count, false)
|
||||||
caret.moveToMotion(motion)
|
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.injector
|
||||||
import com.maddyhome.idea.vim.api.moveToMotion
|
import com.maddyhome.idea.vim.api.moveToMotion
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.ShiftedArrowKeyHandler
|
import com.maddyhome.idea.vim.handler.ShiftedArrowKeyHandler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Alex Plate
|
||||||
|
*/
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<S-Right>"], modes = [Mode.INSERT, Mode.NORMAL, Mode.VISUAL, Mode.SELECT])
|
@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 val type: Command.Type = Command.Type.OTHER_READONLY
|
||||||
|
|
||||||
override fun motionWithKeyModel(editor: VimEditor, caret: VimCaret, context: ExecutionContext, cmd: Command) {
|
override fun motionWithKeyModel(editor: VimEditor, caret: VimCaret, context: ExecutionContext, cmd: Command) {
|
||||||
val motion = injector.motion.getHorizontalMotion(editor, caret, cmd.count, true)
|
val vertical = injector.motion.getHorizontalMotion(editor, caret, cmd.count, true)
|
||||||
caret.moveToMotion(motion)
|
caret.moveToMotion(vertical)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||||
val caret = editor.currentCaret()
|
val caret = editor.currentCaret()
|
||||||
val motion = injector.motion.findOffsetOfNextWord(editor, caret.offset, cmd.count, false)
|
val newOffset = injector.motion.findOffsetOfNextWord(editor, caret.offset, cmd.count, false)
|
||||||
caret.moveToMotion(motion)
|
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.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
import com.maddyhome.idea.vim.command.MotionType
|
import com.maddyhome.idea.vim.command.MotionType
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.Direction
|
import com.maddyhome.idea.vim.common.Direction
|
||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||||
import com.maddyhome.idea.vim.handler.toMotionOrError
|
import com.maddyhome.idea.vim.handler.toMotionOrError
|
||||||
|
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
enum class TillCharacterMotionType {
|
enum class TillCharacterMotionType {
|
||||||
LAST_F,
|
LAST_F,
|
||||||
@ -48,6 +51,9 @@ sealed class TillCharacterMotion(
|
|||||||
private val finishBeforeCharacter: Boolean,
|
private val finishBeforeCharacter: Boolean,
|
||||||
) : MotionActionHandler.ForEachCaret() {
|
) : MotionActionHandler.ForEachCaret() {
|
||||||
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
|
||||||
|
|
||||||
|
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
|
||||||
|
|
||||||
override val motionType: MotionType =
|
override val motionType: MotionType =
|
||||||
if (direction == Direction.BACKWARDS) MotionType.EXCLUSIVE else MotionType.INCLUSIVE
|
if (direction == Direction.BACKWARDS) MotionType.EXCLUSIVE else MotionType.INCLUSIVE
|
||||||
|
|
||||||
@ -58,7 +64,7 @@ sealed class TillCharacterMotion(
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
val res = if (finishBeforeCharacter) {
|
val res = if (finishBeforeCharacter) {
|
||||||
injector.motion
|
injector.motion
|
||||||
.moveCaretToBeforeNextCharacterOnLine(
|
.moveCaretToBeforeNextCharacterOnLine(
|
||||||
|
@ -37,7 +37,7 @@ class MotionGotoFileMarkAction : MotionActionHandler.ForEachCaret() {
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
val mark = argument.character
|
val mark = argument.character
|
||||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||||
@ -57,7 +57,7 @@ class MotionGotoFileMarkNoSaveJumpAction : MotionActionHandler.ForEachCaret() {
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
val mark = argument.character
|
val mark = argument.character
|
||||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||||
|
@ -37,7 +37,7 @@ class MotionGotoFileMarkLineAction : MotionActionHandler.ForEachCaret() {
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
val mark = argument.character
|
val mark = argument.character
|
||||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||||
@ -57,7 +57,7 @@ class MotionGotoFileMarkLineNoSaveJumpAction : MotionActionHandler.ForEachCaret(
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
val mark = argument.character
|
val mark = argument.character
|
||||||
return injector.motion.moveCaretToMark(caret, mark, true)
|
return injector.motion.moveCaretToMark(caret, mark, true)
|
||||||
|
@ -37,7 +37,7 @@ class MotionGotoMarkAction : MotionActionHandler.ForEachCaret() {
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
val mark = argument.character
|
val mark = argument.character
|
||||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||||
@ -57,7 +57,7 @@ class MotionGotoMarkNoSaveJumpAction : MotionActionHandler.ForEachCaret() {
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
val mark = argument.character
|
val mark = argument.character
|
||||||
return injector.motion.moveCaretToMark(caret, mark, false)
|
return injector.motion.moveCaretToMark(caret, mark, false)
|
||||||
|
@ -37,7 +37,7 @@ class MotionGotoMarkLineAction : MotionActionHandler.ForEachCaret() {
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
val mark = argument.character
|
val mark = argument.character
|
||||||
return injector.motion.moveCaretToMark(caret, mark, true)
|
return injector.motion.moveCaretToMark(caret, mark, true)
|
||||||
@ -57,7 +57,7 @@ class MotionGotoMarkLineNoSaveJumpAction : MotionActionHandler.ForEachCaret() {
|
|||||||
argument: Argument?,
|
argument: Argument?,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Motion {
|
): Motion {
|
||||||
if (argument !is Argument.Character) return Motion.Error
|
if (argument == null) return Motion.Error
|
||||||
|
|
||||||
val mark = argument.character
|
val mark = argument.character
|
||||||
return injector.motion.moveCaretToMark(caret, mark, true)
|
return injector.motion.moveCaretToMark(caret, mark, true)
|
||||||
|
@ -24,6 +24,7 @@ class MotionMarkAction : VimActionHandler.SingleExecution() {
|
|||||||
override val argumentType: Argument.Type = Argument.Type.CHARACTER
|
override val argumentType: Argument.Type = Argument.Type.CHARACTER
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
return 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.Argument
|
||||||
import com.maddyhome.idea.vim.command.MotionType
|
import com.maddyhome.idea.vim.command.MotionType
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
|
||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||||
import com.maddyhome.idea.vim.handler.toMotion
|
import com.maddyhome.idea.vim.handler.toMotion
|
||||||
@ -29,7 +28,7 @@ import com.maddyhome.idea.vim.options.OptionConstants
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<Left>"], modes = [Mode.SELECT])
|
@CommandOrMotion(keys = ["<Left>"], modes = [Mode.SELECT])
|
||||||
class SelectMotionArrowLeftAction : MotionActionHandler.ForEachCaret() {
|
class SelectMotionLeftAction : MotionActionHandler.ForEachCaret() {
|
||||||
|
|
||||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
|
|
||||||
@ -59,6 +58,6 @@ class SelectMotionArrowLeftAction : MotionActionHandler.ForEachCaret() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
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.Argument
|
||||||
import com.maddyhome.idea.vim.command.MotionType
|
import com.maddyhome.idea.vim.command.MotionType
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
|
||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||||
import com.maddyhome.idea.vim.handler.toMotion
|
import com.maddyhome.idea.vim.handler.toMotion
|
||||||
@ -29,7 +28,7 @@ import com.maddyhome.idea.vim.options.OptionConstants
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<Right>"], modes = [Mode.SELECT])
|
@CommandOrMotion(keys = ["<Right>"], modes = [Mode.SELECT])
|
||||||
class SelectMotionArrowRightAction : MotionActionHandler.ForEachCaret() {
|
class SelectMotionRightAction : MotionActionHandler.ForEachCaret() {
|
||||||
|
|
||||||
override val motionType: MotionType = MotionType.EXCLUSIVE
|
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
|
|
||||||
@ -59,6 +58,6 @@ class SelectMotionArrowRightAction : MotionActionHandler.ForEachCaret() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
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)
|
fun initInsert(editor: VimEditor, context: ExecutionContext, mode: Mode)
|
||||||
|
|
||||||
/**
|
fun processEscape(editor: VimEditor, context: ExecutionContext?, operatorArguments: OperatorArguments)
|
||||||
* 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 processEnter(editor: VimEditor, caret: VimCaret, context: ExecutionContext)
|
fun processEnter(editor: VimEditor, caret: VimCaret, context: ExecutionContext)
|
||||||
fun processEnter(editor: VimEditor, context: ExecutionContext)
|
fun processEnter(editor: VimEditor, context: ExecutionContext)
|
||||||
fun processBackspace(editor: VimEditor, context: ExecutionContext)
|
|
||||||
|
|
||||||
fun processPostChangeModeSwitch(editor: VimEditor, context: ExecutionContext, toSwitch: Mode)
|
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 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
|
fun processKey(editor: VimEditor, key: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
|
||||||
|
|
||||||
@ -102,6 +92,7 @@ interface VimChangeGroup {
|
|||||||
range: TextRange,
|
range: TextRange,
|
||||||
type: SelectionType?,
|
type: SelectionType?,
|
||||||
isChange: Boolean,
|
isChange: Boolean,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
saveToRegister: Boolean = true,
|
saveToRegister: Boolean = true,
|
||||||
): Boolean
|
): Boolean
|
||||||
fun changeCharacters(editor: VimEditor, caret: VimCaret, operatorArguments: OperatorArguments): 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 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 changeCaseRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: ChangeCaseType): Boolean
|
||||||
|
|
||||||
fun changeRange(
|
fun changeRange(
|
||||||
@ -129,6 +122,7 @@ interface VimChangeGroup {
|
|||||||
range: TextRange,
|
range: TextRange,
|
||||||
type: SelectionType,
|
type: SelectionType,
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
fun changeCaseMotion(editor: VimEditor, caret: VimCaret, context: ExecutionContext?, type: ChangeCaseType, argument: Argument, 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,
|
context: ExecutionContext,
|
||||||
count: Int,
|
count: Int,
|
||||||
started: Boolean,
|
started: Boolean,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char)
|
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.CommandFlags
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.ChangesListener
|
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.common.TextRange
|
||||||
import com.maddyhome.idea.vim.diagnostic.debug
|
import com.maddyhome.idea.vim.diagnostic.debug
|
||||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||||
@ -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.EditorActionHandlerBase
|
||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
|
||||||
import com.maddyhome.idea.vim.helper.CharacterHelper
|
import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||||
import com.maddyhome.idea.vim.helper.NumberType
|
import com.maddyhome.idea.vim.helper.NumberType
|
||||||
@ -111,6 +111,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
TextRange(caret.offset, endOffset.offset),
|
TextRange(caret.offset, endOffset.offset),
|
||||||
SelectionType.CHARACTER_WISE,
|
SelectionType.CHARACTER_WISE,
|
||||||
caret,
|
caret,
|
||||||
|
operatorArguments,
|
||||||
)
|
)
|
||||||
val pos = caret.offset
|
val pos = caret.offset
|
||||||
val norm = editor.normalizeOffset(caret.getBufferPosition().line, pos, isChange)
|
val norm = editor.normalizeOffset(caret.getBufferPosition().line, pos, isChange)
|
||||||
@ -161,21 +162,21 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
range: TextRange,
|
range: TextRange,
|
||||||
type: SelectionType?,
|
type: SelectionType?,
|
||||||
caret: VimCaret,
|
caret: VimCaret,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
saveToRegister: Boolean = true,
|
saveToRegister: Boolean = true,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var updatedRange = range
|
var updatedRange = range
|
||||||
|
|
||||||
// Fix for https://youtrack.jetbrains.net/issue/VIM-35
|
// Fix for https://youtrack.jetbrains.net/issue/VIM-35
|
||||||
if (!range.normalize(editor.fileSize().toInt())) {
|
if (!range.normalize(editor.fileSize().toInt())) {
|
||||||
updatedRange = if (range.startOffset == range.endOffset
|
updatedRange = if (range.startOffset == range.endOffset && range.startOffset == editor.fileSize()
|
||||||
&& range.startOffset == editor.fileSize().toInt()
|
.toInt() && range.startOffset != 0
|
||||||
&& range.startOffset != 0) {
|
) {
|
||||||
TextRange(range.startOffset - 1, range.endOffset)
|
TextRange(range.startOffset - 1, range.endOffset)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val mode = editor.mode
|
val mode = operatorArguments.mode
|
||||||
if (type == null ||
|
if (type == null ||
|
||||||
(mode == Mode.INSERT || mode == Mode.REPLACE) ||
|
(mode == Mode.INSERT || mode == Mode.REPLACE) ||
|
||||||
!saveToRegister ||
|
!saveToRegister ||
|
||||||
@ -238,10 +239,11 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
count: Int,
|
count: Int,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
) {
|
) {
|
||||||
val myLastStrokes = lastStrokes ?: return
|
val myLastStrokes = lastStrokes ?: return
|
||||||
for (caret in editor.nativeCarets()) {
|
for (caret in editor.nativeCarets()) {
|
||||||
repeat(count) {
|
for (i in 0 until count) {
|
||||||
for (lastStroke in myLastStrokes) {
|
for (lastStroke in myLastStrokes) {
|
||||||
when (lastStroke) {
|
when (lastStroke) {
|
||||||
is NativeAction -> {
|
is NativeAction -> {
|
||||||
@ -250,7 +252,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is EditorActionHandlerBase -> {
|
is EditorActionHandlerBase -> {
|
||||||
injector.actionExecutor.executeVimAction(editor, lastStroke, context, OperatorArguments(0, editor.mode))
|
injector.actionExecutor.executeVimAction(editor, lastStroke, context, operatorArguments)
|
||||||
strokes.add(lastStroke)
|
strokes.add(lastStroke)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,6 +281,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
count: Int,
|
count: Int,
|
||||||
started: Boolean,
|
started: Boolean,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
) {
|
) {
|
||||||
for (caret in editor.nativeCarets()) {
|
for (caret in editor.nativeCarets()) {
|
||||||
if (repeatLines > 0) {
|
if (repeatLines > 0) {
|
||||||
@ -299,17 +302,17 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
val updatedCount = if (started) (if (i == 0) count else count + 1) else count
|
val updatedCount = if (started) (if (i == 0) count else count + 1) else count
|
||||||
if (repeatColumn >= VimMotionGroupBase.LAST_COLUMN) {
|
if (repeatColumn >= VimMotionGroupBase.LAST_COLUMN) {
|
||||||
caret.moveToOffset(injector.motion.moveCaretToLineEnd(editor, bufferLine + i, true))
|
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) {
|
} else if (editor.getVisualLineLength(visualLine + i) >= repeatColumn) {
|
||||||
val visualPosition = VimVisualPosition(visualLine + i, repeatColumn, false)
|
val visualPosition = VimVisualPosition(visualLine + i, repeatColumn, false)
|
||||||
val inlaysCount = injector.engineEditorHelper.amountOfInlaysBeforeVisualPosition(editor, visualPosition)
|
val inlaysCount = injector.engineEditorHelper.amountOfInlaysBeforeVisualPosition(editor, visualPosition)
|
||||||
caret.moveToVisualPosition(VimVisualPosition(visualLine + i, repeatColumn + inlaysCount, false))
|
caret.moveToVisualPosition(VimVisualPosition(visualLine + i, repeatColumn + inlaysCount, false))
|
||||||
repeatInsertText(editor, context, updatedCount)
|
repeatInsertText(editor, context, updatedCount, operatorArguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
caret.moveToOffset(position)
|
caret.moveToOffset(position)
|
||||||
} else {
|
} else {
|
||||||
repeatInsertText(editor, context, count)
|
repeatInsertText(editor, context, count, operatorArguments)
|
||||||
val position = injector.motion.getHorizontalMotion(editor, caret, -1, false)
|
val position = injector.motion.getHorizontalMotion(editor, caret, -1, false)
|
||||||
caret.moveToMotion(position)
|
caret.moveToMotion(position)
|
||||||
}
|
}
|
||||||
@ -327,7 +330,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
val oldFragmentLength = oldFragment.length
|
val oldFragmentLength = oldFragment.length
|
||||||
|
|
||||||
// Repeat buffer limits
|
// Repeat buffer limits
|
||||||
if (repeatCharsCount > MAX_REPEAT_CHARS_COUNT) {
|
if (repeatCharsCount > Companion.MAX_REPEAT_CHARS_COUNT) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +351,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
if (oldFragmentLength > 0) {
|
if (oldFragmentLength > 0) {
|
||||||
val editorDelete = injector.nativeActionManager.deleteAction
|
val editorDelete = injector.nativeActionManager.deleteAction
|
||||||
if (editorDelete != null) {
|
if (editorDelete != null) {
|
||||||
repeat(oldFragmentLength) {
|
for (i in 0 until oldFragmentLength) {
|
||||||
strokes.add(editorDelete)
|
strokes.add(editorDelete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,7 +370,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
val motionName = if (delta < 0) "VimMotionLeftAction" else "VimMotionRightAction"
|
val motionName = if (delta < 0) "VimMotionLeftAction" else "VimMotionRightAction"
|
||||||
val action = injector.actionExecutor.findVimAction(motionName)!!
|
val action = injector.actionExecutor.findVimAction(motionName)!!
|
||||||
val count = abs(delta)
|
val count = abs(delta)
|
||||||
repeat(count) {
|
for (i in 0 until count) {
|
||||||
positionCaretActions.add(action)
|
positionCaretActions.add(action)
|
||||||
}
|
}
|
||||||
return positionCaretActions
|
return positionCaretActions
|
||||||
@ -446,8 +449,25 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
if (mode == Mode.REPLACE) {
|
if (mode == Mode.REPLACE) {
|
||||||
editor.insertMode = false
|
editor.insertMode = false
|
||||||
}
|
}
|
||||||
val count = if (cmd.flags.contains(CommandFlags.FLAG_NO_REPEAT_INSERT)) 1 else cmd.count
|
if (cmd.flags.contains(CommandFlags.FLAG_NO_REPEAT_INSERT)) {
|
||||||
repeatInsert(editor, context, count, false)
|
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) {
|
if (mode == Mode.REPLACE) {
|
||||||
editor.insertMode = true
|
editor.insertMode = true
|
||||||
}
|
}
|
||||||
@ -512,9 +532,9 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
exit: Boolean,
|
exit: Boolean,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
) {
|
) {
|
||||||
repeatInsertText(editor, context, 1)
|
repeatInsertText(editor, context, 1, operatorArguments)
|
||||||
if (exit) {
|
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
|
* 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
|
// Get the offset for marks before we exit insert mode - switching from insert to overtype subtracts one from the
|
||||||
// column offset.
|
// column offset.
|
||||||
val markGroup = injector.markService
|
val markGroup = injector.markService
|
||||||
@ -533,24 +553,17 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
if (editor.mode is Mode.REPLACE) {
|
if (editor.mode is Mode.REPLACE) {
|
||||||
editor.insertMode = true
|
editor.insertMode = true
|
||||||
}
|
}
|
||||||
val repeatCount0 = lastInsert?.let {
|
var cnt = if (lastInsert != null) lastInsert!!.count else 0
|
||||||
// How many times do we want to *repeat* the insert? For a simple insert or change action, this is count-1. But if
|
if (lastInsert != null && lastInsert!!.flags.contains(CommandFlags.FLAG_NO_REPEAT_INSERT)) {
|
||||||
// the command is an operator+motion, then the count applies to the motion, not the insert/change. I.e., `2cw`
|
cnt = 1
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
} ?: 0
|
|
||||||
if (vimDocument != null && vimDocumentListener != null) {
|
if (vimDocument != null && vimDocumentListener != null) {
|
||||||
vimDocument!!.removeChangeListener(vimDocumentListener!!)
|
vimDocument!!.removeChangeListener(vimDocumentListener!!)
|
||||||
vimDocumentListener = null
|
vimDocumentListener = null
|
||||||
}
|
}
|
||||||
lastStrokes = ArrayList(strokes)
|
lastStrokes = ArrayList(strokes)
|
||||||
if (context != null) {
|
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) {
|
if (editor.mode is Mode.INSERT) {
|
||||||
updateLastInsertedTextRegister()
|
updateLastInsertedTextRegister()
|
||||||
@ -664,7 +677,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
val rangeToDelete = TextRange(startOffset, offset)
|
val rangeToDelete = TextRange(startOffset, offset)
|
||||||
editor.nativeCarets().filter { it != caret && rangeToDelete.contains(it.offset) }
|
editor.nativeCarets().filter { it != caret && rangeToDelete.contains(it.offset) }
|
||||||
.forEach { editor.removeCaret(it) }
|
.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) {
|
if (editor.usesVirtualSpace) {
|
||||||
caret.moveToOffset(startOffset)
|
caret.moveToOffset(startOffset)
|
||||||
} else {
|
} else {
|
||||||
@ -691,6 +704,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
caret: VimCaret,
|
caret: VimCaret,
|
||||||
count: Int,
|
count: Int,
|
||||||
spaces: Boolean,
|
spaces: Boolean,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var myCount = count
|
var myCount = count
|
||||||
if (myCount < 2) myCount = 2
|
if (myCount < 2) myCount = 2
|
||||||
@ -699,7 +713,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
return if (lline + myCount > total) {
|
return if (lline + myCount > total) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
deleteJoinNLines(editor, caret, lline, myCount, spaces)
|
deleteJoinNLines(editor, caret, lline, myCount, spaces, operatorArguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,14 +731,12 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
logger.debug { "processKey($key)" }
|
logger.debug { "processKey($key)" }
|
||||||
if (key.keyChar != KeyEvent.CHAR_UNDEFINED) {
|
if (key.keyChar != KeyEvent.CHAR_UNDEFINED) {
|
||||||
editor.replaceMask?.recordChangeAtCaret(editor)
|
|
||||||
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, key.keyChar) }
|
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, key.keyChar) }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift-space
|
// Shift-space
|
||||||
if (key.keyCode == 32 && key.modifiers and KeyEvent.SHIFT_DOWN_MASK != 0) {
|
if (key.keyCode == 32 && key.modifiers and KeyEvent.SHIFT_DOWN_MASK != 0) {
|
||||||
editor.replaceMask?.recordChangeAtCaret(editor)
|
|
||||||
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, ' ') }
|
processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, ' ') }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -772,7 +784,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
logger.debug("offset=$offset")
|
logger.debug("offset=$offset")
|
||||||
}
|
}
|
||||||
if (offset != -1) {
|
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) {
|
if (res && caret.offset >= editor.fileSize() && caret.offset != 0) {
|
||||||
caret.moveToOffset(
|
caret.moveToOffset(
|
||||||
injector.motion.moveCaretToRelativeLineStartSkipLeading(
|
injector.motion.moveCaretToRelativeLineStartSkipLeading(
|
||||||
@ -795,7 +807,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
lline + count <= total
|
lline + count <= total
|
||||||
}
|
}
|
||||||
if (!allowedExecution) return false
|
if (!allowedExecution) return false
|
||||||
repeat(executions) {
|
for (i in 0 until executions) {
|
||||||
val joinLinesAction = injector.nativeActionManager.joinLines
|
val joinLinesAction = injector.nativeActionManager.joinLines
|
||||||
if (joinLinesAction != null) {
|
if (joinLinesAction != null) {
|
||||||
injector.actionExecutor.executeAction(editor, joinLinesAction, context)
|
injector.actionExecutor.executeAction(editor, joinLinesAction, context)
|
||||||
@ -825,7 +837,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
val endLine = editor.offsetToBufferPosition(range.endOffset).line
|
val endLine = editor.offsetToBufferPosition(range.endOffset).line
|
||||||
var count = endLine - startLine + 1
|
var count = endLine - startLine + 1
|
||||||
if (count < 2) count = 2
|
if (count < 2) count = 2
|
||||||
return deleteJoinNLines(editor, caret, startLine, count, spaces)
|
return deleteJoinNLines(editor, caret, startLine, count, spaces, operatorArguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun joinViaIdeaBySelections(
|
override fun joinViaIdeaBySelections(
|
||||||
@ -862,25 +874,28 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
isChange: Boolean,
|
isChange: Boolean,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): Pair<TextRange, SelectionType>? {
|
): Pair<TextRange, SelectionType>? {
|
||||||
check(argument is Argument.Motion) { "Unexpected argument: $argument" }
|
|
||||||
|
|
||||||
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments) ?: return null
|
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:
|
// Delete motion commands that are not linewise become linewise if all the following are true:
|
||||||
// 1) The range is across multiple lines
|
// 1) The range is across multiple lines
|
||||||
// 2) There is only whitespace before the start of the range
|
// 2) There is only whitespace before the start of the range
|
||||||
// 3) There is only whitespace after the end 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 start = editor.offsetToBufferPosition(range.startOffset)
|
||||||
val end = editor.offsetToBufferPosition(range.endOffset)
|
val end = editor.offsetToBufferPosition(range.endOffset)
|
||||||
if (start.line != end.line
|
if (start.line != end.line) {
|
||||||
&& !editor.anyNonWhitespace(range.startOffset, -1)
|
if (!editor.anyNonWhitespace(range.startOffset, -1) && !editor.anyNonWhitespace(range.endOffset, 1)) {
|
||||||
&& !editor.anyNonWhitespace(range.endOffset, 1)) {
|
type = SelectionType.LINE_WISE
|
||||||
motionType = SelectionType.LINE_WISE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Pair(range, motionType)
|
}
|
||||||
|
return Pair(range, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -900,12 +915,13 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
range: TextRange,
|
range: TextRange,
|
||||||
type: SelectionType?,
|
type: SelectionType?,
|
||||||
isChange: Boolean,
|
isChange: Boolean,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
saveToRegister: Boolean,
|
saveToRegister: Boolean,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val intendedColumn = caret.vimLastColumn
|
val intendedColumn = caret.vimLastColumn
|
||||||
|
|
||||||
val removeLastNewLine = removeLastNewLine(editor, range, type)
|
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
|
var processedCaret = editor.findLastVersionOfCaret(caret) ?: caret
|
||||||
if (removeLastNewLine) {
|
if (removeLastNewLine) {
|
||||||
val textLength = editor.fileSize().toInt()
|
val textLength = editor.fileSize().toInt()
|
||||||
@ -1024,6 +1040,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
startLine: Int,
|
startLine: Int,
|
||||||
count: Int,
|
count: Int,
|
||||||
spaces: Boolean,
|
spaces: Boolean,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): 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
|
// 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
|
// caret and then be unable to delete
|
||||||
@ -1040,7 +1057,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
return i > 1
|
return i > 1
|
||||||
}
|
}
|
||||||
// Note that caret isn't moved here; it's only used for register + mark storage
|
// 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) {
|
if (spaces && !hasTrailingWhitespace) {
|
||||||
insertText(editor, caret, startOffset, " ")
|
insertText(editor, caret, startOffset, " ")
|
||||||
}
|
}
|
||||||
@ -1127,8 +1144,8 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
var count0 = operatorArguments.count0
|
var count0 = operatorArguments.count0
|
||||||
// Vim treats cw as ce and cW as cE if cursor is on a non-blank character
|
// 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 motion = argument.motion
|
||||||
val id = motionArgument.motion.id
|
val id = motion.action.id
|
||||||
var kludge = false
|
var kludge = false
|
||||||
val bigWord = id == VIM_MOTION_BIG_WORD_RIGHT
|
val bigWord = id == VIM_MOTION_BIG_WORD_RIGHT
|
||||||
val chars = editor.text()
|
val chars = editor.text()
|
||||||
@ -1138,7 +1155,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
val charType = charType(editor, chars[offset], bigWord)
|
val charType = charType(editor, chars[offset], bigWord)
|
||||||
if (charType !== CharacterHelper.CharacterType.WHITESPACE) {
|
if (charType !== CharacterHelper.CharacterType.WHITESPACE) {
|
||||||
val lastWordChar = offset >= fileSize - 1 || charType(editor, chars[offset + 1], bigWord) !== charType
|
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)
|
val res = deleteCharacter(editor, caret, 1, true, operatorArguments)
|
||||||
if (res) {
|
if (res) {
|
||||||
editor.vimChangeActionSwitchMode = Mode.INSERT
|
editor.vimChangeActionSwitchMode = Mode.INSERT
|
||||||
@ -1148,50 +1165,49 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
when (id) {
|
when (id) {
|
||||||
VIM_MOTION_WORD_RIGHT -> {
|
VIM_MOTION_WORD_RIGHT -> {
|
||||||
kludge = true
|
kludge = true
|
||||||
motionArgument = Argument.Motion(
|
motion.action = injector.actionExecutor.findVimActionOrDie(VIM_MOTION_WORD_END_RIGHT)
|
||||||
injector.actionExecutor.findVimActionOrDie(VIM_MOTION_WORD_END_RIGHT) as MotionActionHandler,
|
|
||||||
motionArgument.argument
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VIM_MOTION_BIG_WORD_RIGHT -> {
|
VIM_MOTION_BIG_WORD_RIGHT -> {
|
||||||
kludge = true
|
kludge = true
|
||||||
motionArgument = Argument.Motion(
|
motion.action = injector.actionExecutor.findVimActionOrDie(VIM_MOTION_BIG_WORD_END_RIGHT)
|
||||||
injector.actionExecutor.findVimActionOrDie(VIM_MOTION_BIG_WORD_END_RIGHT) as MotionActionHandler,
|
|
||||||
motionArgument.argument
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VIM_MOTION_CAMEL_RIGHT -> {
|
VIM_MOTION_CAMEL_RIGHT -> {
|
||||||
kludge = true
|
kludge = true
|
||||||
motionArgument = Argument.Motion(
|
motion.action = injector.actionExecutor.findVimActionOrDie(VIM_MOTION_CAMEL_END_RIGHT)
|
||||||
injector.actionExecutor.findVimActionOrDie(VIM_MOTION_CAMEL_END_RIGHT) as MotionActionHandler,
|
|
||||||
motionArgument.argument
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (kludge) {
|
if (kludge) {
|
||||||
val pos1 = injector.searchHelper.findNextWordEnd(editor, offset, operatorArguments.count1, bigWord, false)
|
val cnt = operatorArguments.count1 * motion.count
|
||||||
val pos2 = injector.searchHelper.findNextWordEnd(editor, pos1, -operatorArguments.count1, bigWord, false)
|
val pos1 = injector.searchHelper.findNextWordEnd(editor, offset, cnt, bigWord, false)
|
||||||
|
val pos2 = injector.searchHelper.findNextWordEnd(editor, pos1, -cnt, bigWord, false)
|
||||||
if (logger.isDebug()) {
|
if (logger.isDebug()) {
|
||||||
logger.debug("pos=$offset")
|
logger.debug("pos=$offset")
|
||||||
logger.debug("pos1=$pos1")
|
logger.debug("pos1=$pos1")
|
||||||
logger.debug("pos2=$pos2")
|
logger.debug("pos2=$pos2")
|
||||||
logger.debug("count=" + operatorArguments.count1)
|
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--
|
count0--
|
||||||
|
} else if (motion.count > 1) {
|
||||||
|
motion.rawCount = motion.count - 1
|
||||||
|
} else {
|
||||||
|
motion.flags = EnumSet.noneOf(CommandFlags::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val (first, second) = getDeleteRangeAndType(
|
val (first, second) = getDeleteRangeAndType(
|
||||||
editor,
|
editor,
|
||||||
caret,
|
caret,
|
||||||
context,
|
context,
|
||||||
motionArgument,
|
argument,
|
||||||
true,
|
true,
|
||||||
operatorArguments.copy(count0 = count0),
|
operatorArguments.withCount0(count0),
|
||||||
) ?: return false
|
) ?: return false
|
||||||
return changeRange(
|
return changeRange(
|
||||||
editor,
|
editor,
|
||||||
@ -1199,6 +1215,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
first,
|
first,
|
||||||
second,
|
second,
|
||||||
context,
|
context,
|
||||||
|
operatorArguments,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1223,6 +1240,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
* @param caret The caret to be moved after range deletion
|
* @param caret The caret to be moved after range deletion
|
||||||
* @param range The range to change
|
* @param range The range to change
|
||||||
* @param type The type of the range
|
* @param type The type of the range
|
||||||
|
* @param operatorArguments
|
||||||
* @return true if able to delete the range, false if not
|
* @return true if able to delete the range, false if not
|
||||||
*/
|
*/
|
||||||
override fun changeRange(
|
override fun changeRange(
|
||||||
@ -1231,6 +1249,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
range: TextRange,
|
range: TextRange,
|
||||||
type: SelectionType,
|
type: SelectionType,
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var col = 0
|
var col = 0
|
||||||
var lines = 0
|
var lines = 0
|
||||||
@ -1243,7 +1262,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
}
|
}
|
||||||
val after = range.endOffset >= editor.fileSize()
|
val after = range.endOffset >= editor.fileSize()
|
||||||
val lp = editor.offsetToBufferPosition(injector.motion.moveCaretToCurrentLineStartSkipLeading(editor, caret))
|
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
|
val updatedCaret = editor.findLastVersionOfCaret(caret) ?: caret
|
||||||
if (res) {
|
if (res) {
|
||||||
if (type === SelectionType.LINE_WISE) {
|
if (type === SelectionType.LINE_WISE) {
|
||||||
@ -1905,7 +1924,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
if (pos > wsoff) {
|
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,
|
editor: VimEditor,
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
append: Boolean,
|
append: Boolean,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val lines = getLinesCountInVisualBlock(editor, range)
|
val lines = getLinesCountInVisualBlock(editor, range)
|
||||||
val startPosition = editor.offsetToBufferPosition(range.startOffset)
|
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
|
val mode = operatorArguments.mode
|
||||||
// will have been removed. Even if not, this would move them all to the same location, which would remove them and
|
val visualBlockMode = mode is Mode.VISUAL && mode.selectionType === SelectionType.BLOCK_WISE
|
||||||
// leave only the primary caret.
|
|
||||||
for (caret in editor.carets()) {
|
for (caret in editor.carets()) {
|
||||||
val line = startPosition.line
|
val line = startPosition.line
|
||||||
var column = startPosition.column
|
var column = startPosition.column
|
||||||
if (append) {
|
if (!visualBlockMode) {
|
||||||
|
column = 0
|
||||||
|
} else if (append) {
|
||||||
column += range.maxLength
|
column += range.maxLength
|
||||||
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
|
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
|
||||||
column = VimMotionGroupBase.LAST_COLUMN
|
column = VimMotionGroupBase.LAST_COLUMN
|
||||||
@ -1963,10 +1984,18 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
val offset = editor.getLineEndOffset(line)
|
val offset = editor.getLineEndOffset(line)
|
||||||
insertText(editor, caret, offset, pad)
|
insertText(editor, caret, offset, pad)
|
||||||
}
|
}
|
||||||
|
if (visualBlockMode || !append) {
|
||||||
caret.moveToInlayAwareOffset(editor.bufferPositionToOffset(BufferPosition(line, column)))
|
caret.moveToInlayAwareOffset(editor.bufferPositionToOffset(BufferPosition(line, column)))
|
||||||
|
}
|
||||||
|
if (visualBlockMode) {
|
||||||
setInsertRepeat(lines, column, append)
|
setInsertRepeat(lines, column, append)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (visualBlockMode || !append) {
|
||||||
insertBeforeCursor(editor, context)
|
insertBeforeCursor(editor, context)
|
||||||
|
} else {
|
||||||
|
insertAfterCursor(editor, context)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2039,3 +2068,9 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
VimChangeGroup.ChangeCaseType.UPPER -> Character.toUpperCase(ch)
|
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
|
package com.maddyhome.idea.vim.api
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.command.Command
|
||||||
|
|
||||||
interface VimCommandLineService {
|
interface VimCommandLineService {
|
||||||
fun isCommandLineSupported(editor: VimEditor): Boolean
|
fun isCommandLineSupported(editor: VimEditor): Boolean
|
||||||
|
|
||||||
@ -24,7 +26,7 @@ interface VimCommandLineService {
|
|||||||
* @param initialText The initial text for the entry
|
* @param initialText The initial text for the entry
|
||||||
*/
|
*/
|
||||||
fun createSearchPrompt(editor: VimEditor, context: ExecutionContext, label: String, initialText: String): VimCommandLine
|
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()")
|
@Deprecated("Please use ModalInputService.create()")
|
||||||
fun createWithoutShortcuts(editor: VimEditor, context: ExecutionContext, label: String, initText: String): VimCommandLine
|
fun createWithoutShortcuts(editor: VimEditor, context: ExecutionContext, label: String, initText: String): VimCommandLine
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.api
|
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.ex.ExException
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
||||||
@ -49,15 +50,15 @@ abstract class VimCommandLineServiceBase : VimCommandLineService {
|
|||||||
return createCommandLinePrompt(editor, context, removeSelections = false, label, initialText)
|
return createCommandLinePrompt(editor, context, removeSelections = false, label, initialText)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createCommandPrompt(editor: VimEditor, context: ExecutionContext, count0: Int, initialText: String): VimCommandLine {
|
override fun createCommandPrompt(editor: VimEditor, context: ExecutionContext, command: Command, initialText: String): VimCommandLine {
|
||||||
val rangeText = getRange(editor, count0)
|
val rangeText = getRange(editor, command)
|
||||||
return createCommandLinePrompt(editor, context, removeSelections = true, label = ":", rangeText + initialText)
|
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 -> "'<,'>"
|
editor.inVisualMode -> "'<,'>"
|
||||||
count0 == 1 -> "."
|
cmd.rawCount == 1 -> "."
|
||||||
count0 > 1 -> ".,.+" + (count0 - 1)
|
cmd.rawCount > 1 -> ".,.+" + (cmd.count - 1)
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.api
|
|||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.LiveRange
|
import com.maddyhome.idea.vim.common.LiveRange
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
@ -129,7 +128,6 @@ interface VimEditor {
|
|||||||
val lfMakesNewLine: Boolean
|
val lfMakesNewLine: Boolean
|
||||||
var vimChangeActionSwitchMode: Mode?
|
var vimChangeActionSwitchMode: Mode?
|
||||||
val indentConfig: VimIndentConfig
|
val indentConfig: VimIndentConfig
|
||||||
var replaceMask: VimEditorReplaceMask?
|
|
||||||
|
|
||||||
fun fileSize(): Long
|
fun fileSize(): Long
|
||||||
|
|
||||||
@ -246,9 +244,7 @@ interface VimEditor {
|
|||||||
// Can be used as a key to store something for specific project
|
// Can be used as a key to store something for specific project
|
||||||
val projectId: String
|
val projectId: String
|
||||||
|
|
||||||
@Deprecated("Use overload without OperatorArguments", replaceWith = ReplaceWith("exitInsertMode(context)"))
|
fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments)
|
||||||
fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) { exitInsertMode(context) }
|
|
||||||
fun exitInsertMode(context: ExecutionContext)
|
|
||||||
fun exitSelectModeNative(adjustCaret: Boolean)
|
fun exitSelectModeNative(adjustCaret: Boolean)
|
||||||
|
|
||||||
var vimLastSelectionType: SelectionType?
|
var vimLastSelectionType: SelectionType?
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.api
|
package com.maddyhome.idea.vim.api
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.common.forgetAllReplaceMasks
|
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
|
||||||
abstract class VimEditorBase : VimEditor {
|
abstract class VimEditorBase : VimEditor {
|
||||||
@ -19,9 +18,6 @@ abstract class VimEditorBase : VimEditor {
|
|||||||
if (vimState.mode == value) return
|
if (vimState.mode == value) return
|
||||||
|
|
||||||
val oldValue = vimState.mode
|
val oldValue = vimState.mode
|
||||||
if (oldValue == Mode.REPLACE) {
|
|
||||||
forgetAllReplaceMasks()
|
|
||||||
}
|
|
||||||
updateMode(value)
|
updateMode(value)
|
||||||
injector.listenersNotifier.notifyModeChanged(this, oldValue)
|
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.action.change.LazyVimCommand
|
||||||
import com.maddyhome.idea.vim.command.MappingMode
|
import com.maddyhome.idea.vim.command.MappingMode
|
||||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
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.KeyMapping
|
||||||
import com.maddyhome.idea.vim.key.KeyMappingLayer
|
import com.maddyhome.idea.vim.key.KeyMappingLayer
|
||||||
import com.maddyhome.idea.vim.key.MappingInfo
|
import com.maddyhome.idea.vim.key.MappingInfo
|
||||||
import com.maddyhome.idea.vim.key.MappingOwner
|
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.key.ShortcutOwnerInfo
|
||||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
interface VimKeyGroup {
|
interface VimKeyGroup {
|
||||||
fun getKeyRoot(mappingMode: MappingMode): RootNode<LazyVimCommand>
|
fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand>
|
||||||
fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer
|
fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer
|
||||||
fun getActions(editor: VimEditor, keyStroke: KeyStroke): List<NativeAction>
|
fun getActions(editor: VimEditor, keyStroke: KeyStroke): List<NativeAction>
|
||||||
fun getKeymapConflicts(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.command.MappingMode
|
||||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
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.KeyMapping
|
||||||
import com.maddyhome.idea.vim.key.KeyMappingLayer
|
import com.maddyhome.idea.vim.key.KeyMappingLayer
|
||||||
import com.maddyhome.idea.vim.key.MappingInfo
|
import com.maddyhome.idea.vim.key.MappingInfo
|
||||||
@ -29,7 +30,7 @@ abstract class VimKeyGroupBase : VimKeyGroup {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val myShortcutConflicts: MutableMap<KeyStroke, ShortcutOwnerInfo> = LinkedHashMap()
|
val myShortcutConflicts: MutableMap<KeyStroke, ShortcutOwnerInfo> = LinkedHashMap()
|
||||||
val requiredShortcutKeys: MutableSet<RequiredShortcut> = HashSet(300)
|
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)
|
val keyMappings: MutableMap<MappingMode, KeyMapping> = EnumMap(MappingMode::class.java)
|
||||||
|
|
||||||
override fun removeKeyMapping(modes: Set<MappingMode>, keys: List<KeyStroke>) {
|
override fun removeKeyMapping(modes: Set<MappingMode>, keys: List<KeyStroke>) {
|
||||||
@ -62,7 +63,7 @@ abstract class VimKeyGroupBase : VimKeyGroup {
|
|||||||
* @param mappingMode The mapping mode
|
* @param mappingMode The mapping mode
|
||||||
* @return The key mapping tree root
|
* @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)
|
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.Graphemes
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.group.findMatchingPairOnCurrentLine
|
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
|
||||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
||||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||||
@ -117,9 +116,9 @@ abstract class VimMotionGroupBase : VimMotionGroup {
|
|||||||
val text = editor.text()
|
val text = editor.text()
|
||||||
val oldOffset = caret.offset
|
val oldOffset = caret.offset
|
||||||
var current = oldOffset
|
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)
|
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) {
|
val offset = if (allowWrap) {
|
||||||
@ -314,51 +313,50 @@ abstract class VimMotionGroupBase : VimMotionGroup {
|
|||||||
argument: Argument,
|
argument: Argument,
|
||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
): TextRange? {
|
): TextRange? {
|
||||||
if (argument !is Argument.Motion) {
|
|
||||||
throw RuntimeException("Unexpected argument passed to getMotionRange2: $argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
var start: Int
|
var start: Int
|
||||||
var end: Int
|
var end: Int
|
||||||
|
if (argument.type === Argument.Type.OFFSETS) {
|
||||||
val action = argument.motion
|
val offsets = argument.offsets[caret] ?: return null
|
||||||
when (action) {
|
val (first, second) = offsets.getNativeStartAndEnd()
|
||||||
is MotionActionHandler -> {
|
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
|
// This is where we are now
|
||||||
start = caret.offset
|
start = caret.offset
|
||||||
|
|
||||||
// Execute the motion (without moving the cursor) and get where we end
|
// Execute the motion (without moving the cursor) and get where we end
|
||||||
val motion = action.getHandlerOffset(editor, caret, context, argument.argument, operatorArguments)
|
val motion =
|
||||||
if (Motion.Error == motion || Motion.NoMotion == motion) return null
|
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
|
end = (motion as AbsoluteOffset).offset
|
||||||
|
|
||||||
// If inclusive, add the last character to the range
|
// If inclusive, add the last character to the range
|
||||||
if (action.motionType === MotionType.INCLUSIVE) {
|
if (cmdAction.motionType === MotionType.INCLUSIVE) {
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
if (start < editor.fileSize()) start++
|
if (start < editor.fileSize()) start++
|
||||||
} else {
|
} else {
|
||||||
if (end < editor.fileSize()) end++
|
if (end < editor.fileSize()) end++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (cmdAction is TextObjectActionHandler) {
|
||||||
|
val range: TextRange = cmdAction.getRange(editor, caret, context, cnt, raw)
|
||||||
is TextObjectActionHandler -> {
|
|
||||||
val range: TextRange = action.getRange(editor, caret, context, operatorArguments.count1, operatorArguments.count0)
|
|
||||||
?: return null
|
?: return null
|
||||||
start = range.startOffset
|
start = range.startOffset
|
||||||
end = range.endOffset
|
end = range.endOffset
|
||||||
if (argument.isLinewiseMotion()) end--
|
if (cmd.isLinewiseMotion()) end--
|
||||||
}
|
} else {
|
||||||
|
throw RuntimeException(
|
||||||
is ExternalActionHandler -> {
|
"Commands doesn't take " + cmdAction.javaClass.simpleName + " as an operator",
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the range
|
// 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
|
// 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.
|
// 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) {
|
if (caret.getBufferPosition().line != editor.lineCount() - 1) {
|
||||||
start = editor.getLineStartForOffset(start)
|
start = editor.getLineStartForOffset(start)
|
||||||
end = min((editor.getLineEndForOffset(end) + 1).toLong(), editor.fileSize()).toInt()
|
end = min((editor.getLineEndForOffset(end) + 1).toLong(), editor.fileSize()).toInt()
|
||||||
@ -379,19 +377,19 @@ abstract class VimMotionGroupBase : VimMotionGroup {
|
|||||||
end = editor.getLineEndForOffset(end)
|
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.
|
// 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 text = editor.text().subSequence(start, end).toString()
|
||||||
val lastNewLine = text.lastIndexOf('\n')
|
val lastNewLine = text.lastIndexOf('\n')
|
||||||
if (lastNewLine > 0) {
|
if (lastNewLine > 0) {
|
||||||
val id = action.id
|
val id = argument.motion.action.id
|
||||||
if (id == "VimMotionWordRightAction" || id == "VimMotionBigWordRightAction" || id == "VimMotionCamelRightAction") {
|
if (id == "VimMotionWordRightAction" || id == "VimMotionBigWordRightAction" || id == "VimMotionCamelRightAction") {
|
||||||
if (!editor.anyNonWhitespace(end, -1)) {
|
if (!editor.anyNonWhitespace(end, -1)) {
|
||||||
end = start + lastNewLine
|
end = start + lastNewLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextRange(start, end)
|
return TextRange(start, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,88 +8,51 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.command
|
package com.maddyhome.idea.vim.command
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.handler.ExternalActionHandler
|
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.MotionActionHandler
|
||||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
|
import java.util.*
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an argument to a command's action
|
* 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
|
||||||
* 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`).
|
|
||||||
*/
|
*/
|
||||||
sealed class Argument {
|
class Argument private constructor(
|
||||||
/** A simple character argument */
|
val character: Char = 0.toChar(),
|
||||||
class Character(val character: Char) : Argument()
|
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 {
|
enum class Type {
|
||||||
|
MOTION, CHARACTER, DIGRAPH, EX_STRING, OFFSETS
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
companion object {
|
||||||
* A motion argument used to complete an operator, such as `dw` or `diw`
|
@JvmField
|
||||||
*
|
val EMPTY_COMMAND: Command = Command(
|
||||||
* A motion argument will often have its own argument, such as when deleting up to the next occurrence of a
|
0,
|
||||||
* character, as in `dfx`.
|
object : MotionActionHandler.SingleExecution() {
|
||||||
*/
|
override fun getOffset(
|
||||||
MOTION,
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
argument: Argument?,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
|
) = Motion.NoMotion
|
||||||
|
|
||||||
/** A character argument, such as the character to move to with the `f` command. */
|
override val motionType: MotionType = MotionType.EXCLUSIVE
|
||||||
CHARACTER,
|
},
|
||||||
|
Command.Type.MOTION,
|
||||||
/**
|
EnumSet.noneOf(CommandFlags::class.java),
|
||||||
* 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,47 +8,34 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.command
|
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.EditorActionHandlerBase
|
||||||
|
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||||
|
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This represents a single Vim command to be executed (action, motion, operator+motion, v_textobject, etc.)
|
* 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.
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
data class Command(
|
data class Command(
|
||||||
val register: Char?,
|
var rawCount: Int,
|
||||||
val rawCount: Int,
|
var action: EditorActionHandlerBase,
|
||||||
val action: EditorActionHandlerBase,
|
|
||||||
val argument: Argument?,
|
|
||||||
val type: Type,
|
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 {
|
init {
|
||||||
action.process(this)
|
action.process(this)
|
||||||
}
|
}
|
||||||
@ -56,7 +43,20 @@ data class Command(
|
|||||||
val count: Int
|
val count: Int
|
||||||
get() = rawCount.coerceAtLeast(1)
|
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 {
|
enum class Type {
|
||||||
/**
|
/**
|
||||||
@ -85,6 +85,10 @@ data class Command(
|
|||||||
COPY,
|
COPY,
|
||||||
PASTE,
|
PASTE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents commands that select the register.
|
||||||
|
*/
|
||||||
|
SELECT_REGISTER,
|
||||||
OTHER_READONLY,
|
OTHER_READONLY,
|
||||||
OTHER_WRITABLE,
|
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.trace
|
||||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||||
import com.maddyhome.idea.vim.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.CommandPartNode
|
||||||
|
import com.maddyhome.idea.vim.key.Node
|
||||||
import com.maddyhome.idea.vim.key.RootNode
|
import com.maddyhome.idea.vim.key.RootNode
|
||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
class CommandBuilder private constructor(
|
class CommandBuilder(
|
||||||
private var currentCommandPartNode: CommandPartNode<LazyVimCommand>,
|
private var currentCommandPartNode: CommandPartNode<LazyVimCommand>,
|
||||||
private val counts: MutableList<Int>,
|
private val commandParts: ArrayDeque<Command>,
|
||||||
private val keyList: MutableList<KeyStroke>,
|
private val keyList: MutableList<KeyStroke>,
|
||||||
|
initialUncommittedRawCount: Int,
|
||||||
) : Cloneable {
|
) : Cloneable {
|
||||||
|
|
||||||
constructor(rootNode: RootNode<LazyVimCommand>, initialUncommittedRawCount: Int = 0)
|
constructor(
|
||||||
: this(rootNode, mutableListOf(initialUncommittedRawCount), mutableListOf())
|
currentCommandPartNode: CommandPartNode<LazyVimCommand>,
|
||||||
|
initialUncommittedRawCount: Int = 0
|
||||||
|
) : this(
|
||||||
|
currentCommandPartNode,
|
||||||
|
ArrayDeque(),
|
||||||
|
mutableListOf(),
|
||||||
|
initialUncommittedRawCount
|
||||||
|
)
|
||||||
|
|
||||||
private var commandState: CurrentCommandState = CurrentCommandState.NEW_COMMAND
|
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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* This value is not coerced, and can be 0.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* The returned value is the product of all count components. In other words, given a command that is an
|
* There are very few reasons for using this value. It is incomplete (the user could type another digit), and there
|
||||||
* operator+motion, both the operator and motion can have a count, such as `2d3w`, which means delete the next six
|
* can be other committed command parts, such as operator and multiple register selections, each of which will can a
|
||||||
* words. Furthermore, Vim allows a count when selecting register, and it is valid to select register multiple times.
|
* count (e.g., `2"a3"b4"c5d6` waiting for a motion). The count is only final after [buildCommand], and then only via
|
||||||
* E.g., `2"a3"b4"c5d6w` will delete the next 720 words and save the text to the register `c`.
|
* [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
|
* The [aggregatedUncommittedCount] property can be used to get the current total count across all command parts,
|
||||||
* are specified, the value will naturally be greater than 0.
|
* although this value is also not guaranteed to be final.
|
||||||
*/
|
*/
|
||||||
fun calculateCount0Snapshot(): Int {
|
var count: Int = initialUncommittedRawCount
|
||||||
return if (counts.all { it == 0 }) 0 else counts.map { it.coerceAtLeast(1) }.reduce { acc, i -> acc * i }
|
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
|
// 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
|
// 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`.
|
// 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
|
// This doesn't cause any issues with existing extensions
|
||||||
val register: Char?
|
val register: Char?
|
||||||
get() = selectedRegister
|
get() = commandParts.lastOrNull { it.register != null }?.register
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The argument type for the current in-progress command part's action
|
* 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.
|
* For digraph arguments, this can fall back to [Argument.Type.CHARACTER] if there isn't a digraph match.
|
||||||
*/
|
*/
|
||||||
val expectedArgumentType: Argument.Type?
|
val expectedArgumentType: Argument.Type?
|
||||||
get() = fallbackArgumentType
|
get() = fallbackArgumentType ?: commandParts.lastOrNull()?.action?.argumentType
|
||||||
?: motionArgument?.let { return it.motion.argumentType }
|
|
||||||
?: action?.argumentType
|
|
||||||
|
|
||||||
/**
|
private var fallbackArgumentType: Argument.Type? = null
|
||||||
* Returns true if the command builder is waiting for an argument
|
|
||||||
*
|
val isReady: Boolean get() = commandState == CurrentCommandState.READY
|
||||||
* The command builder might be waiting for the argument to a simple motion action such as `f`, waiting for a
|
val isEmpty: Boolean get() = commandParts.isEmpty()
|
||||||
* character to move to, or it might be waiting for the argument to a motion that is itself an argument to an operator
|
val isAtDefaultState: Boolean get() = isEmpty && count == 0 && expectedArgumentType == null
|
||||||
* argument. For example, the character argument to `f` in `df{character}`.
|
|
||||||
*/
|
val isExpectingCount: Boolean
|
||||||
val isAwaitingArgument: Boolean
|
get() {
|
||||||
get() = expectedArgumentType != null && (motionArgument?.let { it.argument == null } ?: (argument == null))
|
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() {
|
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,
|
// 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.
|
// in which case try to handle input as a character argument.
|
||||||
assert(expectedArgumentType == Argument.Type.DIGRAPH) { "Cannot move state from $expectedArgumentType to CHARACTER" }
|
assert(expectedArgumentType == Argument.Type.DIGRAPH) { "Cannot move state from $expectedArgumentType to CHARACTER" }
|
||||||
fallbackArgumentType = Argument.Type.CHARACTER
|
fallbackArgumentType = Argument.Type.CHARACTER
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isAwaitingCharOrDigraphArgument(): Boolean {
|
fun addKey(key: KeyStroke) {
|
||||||
val awaiting = expectedArgumentType == Argument.Type.CHARACTER || expectedArgumentType == Argument.Type.DIGRAPH
|
logger.trace { "added key to command builder" }
|
||||||
logger.debug { "Awaiting char or digraph: $awaiting" }
|
keyList.add(key)
|
||||||
return awaiting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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,
|
// 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.
|
// 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
|
// See https://github.com/vim/vim/blob/b376ace1aeaa7614debc725487d75c8f756dd773/src/normal.c#L631
|
||||||
if (currentCount < 0) {
|
if (count < 0) {
|
||||||
currentCount = 999999999
|
count = 999999999
|
||||||
}
|
}
|
||||||
addKey(key)
|
addKey(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteCountCharacter() {
|
fun deleteCountCharacter() {
|
||||||
currentCount /= 10
|
count /= 10
|
||||||
keyList.removeAt(keyList.size - 1)
|
keyList.removeAt(keyList.size - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isRegisterPending: Boolean = false
|
fun setCurrentCommandPartNode(newNode: CommandPartNode<LazyVimCommand>) {
|
||||||
private set
|
logger.trace { "setCurrentCommandPartNode is executed" }
|
||||||
|
currentCommandPartNode = newNode
|
||||||
fun startWaitingForRegister(key: KeyStroke) {
|
|
||||||
isRegisterPending = true
|
|
||||||
addKey(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectRegister(register: Char) {
|
fun getChildNode(key: KeyStroke): Node<LazyVimCommand>? {
|
||||||
logger.trace { "Selected register '$register'" }
|
return currentCommandPartNode[key]
|
||||||
selectedRegister = register
|
|
||||||
isRegisterPending = false
|
|
||||||
fallbackArgumentType = null
|
|
||||||
counts.add(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun isAwaitingCharOrDigraphArgument(): Boolean {
|
||||||
* Adds a keystroke to the command builder
|
val awaiting = expectedArgumentType == Argument.Type.CHARACTER || expectedArgumentType == Argument.Type.DIGRAPH
|
||||||
*
|
logger.debug { "Awaiting char or digraph: $awaiting" }
|
||||||
* Only public use is when entering a digraph/literal, where each key isn't handled by [CommandBuilder], but should
|
return awaiting
|
||||||
* 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 isBuildingMultiKeyCommand(): Boolean {
|
fun isBuildingMultiKeyCommand(): Boolean {
|
||||||
@ -324,47 +178,67 @@ class CommandBuilder private constructor(
|
|||||||
return isMultikey
|
return isMultikey
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun isDone(): Boolean {
|
||||||
* Build the command with the current counts, register, actions and arguments
|
return commandParts.isEmpty()
|
||||||
*
|
}
|
||||||
* The command builder is reset after the command is built.
|
|
||||||
*/
|
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 {
|
fun buildCommand(): Command {
|
||||||
val rawCount = calculateCount0Snapshot()
|
var command: Command = commandParts.removeFirst()
|
||||||
val command = Command(selectedRegister, rawCount, action!!, argument, action!!.type, action?.flags ?: noneOfEnum())
|
while (commandParts.size > 0) {
|
||||||
resetAll(currentCommandPartNode.root as RootNode<LazyVimCommand>)
|
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
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetAll(rootNode: RootNode<LazyVimCommand>) {
|
fun resetAll(commandPartNode: CommandPartNode<LazyVimCommand>) {
|
||||||
logger.trace("resetAll is executed")
|
logger.trace { "resetAll is executed" }
|
||||||
currentCommandPartNode = rootNode
|
resetInProgressCommandPart(commandPartNode)
|
||||||
commandState = CurrentCommandState.NEW_COMMAND
|
commandState = CurrentCommandState.NEW_COMMAND
|
||||||
counts.clear()
|
commandParts.clear()
|
||||||
counts.add(0)
|
|
||||||
isRegisterPending = false
|
|
||||||
selectedRegister = null
|
|
||||||
action = null
|
|
||||||
argument = null
|
|
||||||
keyList.clear()
|
keyList.clear()
|
||||||
fallbackArgumentType = null
|
fallbackArgumentType = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun resetCount() {
|
||||||
* Change the command trie root node used to find commands for the current mode
|
count = 0
|
||||||
*
|
}
|
||||||
* 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
|
fun resetInProgressCommandPart(commandPartNode: CommandPartNode<LazyVimCommand>) {
|
||||||
* or fully resetting the command builder, such as when switching to Op-pending while entering an operator+motion.
|
logger.trace { "resetInProgressCommandPart is executed" }
|
||||||
*/
|
count = 0
|
||||||
fun resetCommandTrieRootNode(rootNode: RootNode<LazyVimCommand>) {
|
setCurrentCommandPartNode(commandPartNode)
|
||||||
logger.trace("resetCommandTrieRootNode is executed")
|
|
||||||
currentCommandPartNode = rootNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestOnly
|
@TestOnly
|
||||||
fun getCurrentTrie(): CommandPartNode<LazyVimCommand> = currentCommandPartNode
|
fun getCurrentTrie(): CommandPartNode<LazyVimCommand> = currentCommandPartNode
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
@ -372,12 +246,10 @@ class CommandBuilder private constructor(
|
|||||||
other as CommandBuilder
|
other as CommandBuilder
|
||||||
|
|
||||||
if (currentCommandPartNode != other.currentCommandPartNode) return false
|
if (currentCommandPartNode != other.currentCommandPartNode) return false
|
||||||
if (counts != other.counts) return false
|
if (commandParts != other.commandParts) return false
|
||||||
if (selectedRegister != other.selectedRegister) return false
|
|
||||||
if (action != other.action) return false
|
|
||||||
if (argument != other.argument) return false
|
|
||||||
if (keyList != other.keyList) return false
|
if (keyList != other.keyList) return false
|
||||||
if (commandState != other.commandState) return false
|
if (commandState != other.commandState) return false
|
||||||
|
if (count != other.count) return false
|
||||||
if (expectedArgumentType != other.expectedArgumentType) return false
|
if (expectedArgumentType != other.expectedArgumentType) return false
|
||||||
if (fallbackArgumentType != other.fallbackArgumentType) return false
|
if (fallbackArgumentType != other.fallbackArgumentType) return false
|
||||||
|
|
||||||
@ -386,38 +258,24 @@ class CommandBuilder private constructor(
|
|||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = currentCommandPartNode.hashCode()
|
var result = currentCommandPartNode.hashCode()
|
||||||
result = 31 * result + counts.hashCode()
|
result = 31 * result + commandParts.hashCode()
|
||||||
result = 31 * result + selectedRegister.hashCode()
|
|
||||||
result = 31 * result + action.hashCode()
|
|
||||||
result = 31 * result + argument.hashCode()
|
|
||||||
result = 31 * result + keyList.hashCode()
|
result = 31 * result + keyList.hashCode()
|
||||||
result = 31 * result + commandState.hashCode()
|
result = 31 * result + commandState.hashCode()
|
||||||
result = 31 * result + expectedArgumentType.hashCode()
|
result = 31 * result + count
|
||||||
result = 31 * result + fallbackArgumentType.hashCode()
|
result = 31 * result + (expectedArgumentType?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (fallbackArgumentType?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun clone(): CommandBuilder {
|
public override fun clone(): CommandBuilder {
|
||||||
val result = CommandBuilder(
|
val result = CommandBuilder(currentCommandPartNode, ArrayDeque(commandParts), keyList.toMutableList(), count)
|
||||||
currentCommandPartNode,
|
|
||||||
counts.toMutableList(),
|
|
||||||
keyList.toMutableList()
|
|
||||||
)
|
|
||||||
result.selectedRegister = selectedRegister
|
|
||||||
result.action = action
|
|
||||||
result.argument = argument
|
|
||||||
result.commandState = commandState
|
result.commandState = commandState
|
||||||
result.fallbackArgumentType = fallbackArgumentType
|
result.fallbackArgumentType = fallbackArgumentType
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Command state = $commandState, " +
|
return "Command state = $commandState, key list = ${ injector.parser.toKeyNotation(keyList) }, command parts = ${ commandParts }, count = $count\n" +
|
||||||
"key list = ${ injector.parser.toKeyNotation(keyList) }, " +
|
|
||||||
"selected register = $selectedRegister, " +
|
|
||||||
"counts = $counts, " +
|
|
||||||
"action = $action, " +
|
|
||||||
"argument = $argument, " +
|
|
||||||
"command part node - $currentCommandPartNode"
|
"command part node - $currentCommandPartNode"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,11 @@ enum class CommandFlags {
|
|||||||
*/
|
*/
|
||||||
FLAG_EXPECT_MORE,
|
FLAG_EXPECT_MORE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the character argument may come from a digraph
|
||||||
|
*/
|
||||||
|
FLAG_ALLOW_DIGRAPH,
|
||||||
|
|
||||||
FLAG_START_EX,
|
FLAG_START_EX,
|
||||||
FLAG_END_EX,
|
FLAG_END_EX,
|
||||||
|
|
||||||
|
@ -42,12 +42,12 @@ object MappingProcessor: KeyConsumer {
|
|||||||
val keyState = keyProcessResultBuilder.state
|
val keyState = keyProcessResultBuilder.state
|
||||||
val mappingState = keyState.mappingState
|
val mappingState = keyState.mappingState
|
||||||
val commandBuilder = keyState.commandBuilder
|
val commandBuilder = keyState.commandBuilder
|
||||||
if (commandBuilder.isAwaitingCharOrDigraphArgument()
|
if (commandBuilder.isAwaitingCharOrDigraphArgument() ||
|
||||||
|| commandBuilder.isBuildingMultiKeyCommand()
|
commandBuilder.isBuildingMultiKeyCommand() ||
|
||||||
|| commandBuilder.isRegisterPending
|
isMappingDisabledForKey(key, keyState) ||
|
||||||
|| 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
|
return false
|
||||||
}
|
}
|
||||||
mappingState.stopMappingTimer()
|
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
|
// "0" can be mapped, but the mapping isn't applied when entering a count. Other digits are always mapped, even when
|
||||||
// entering a count.
|
// entering a count.
|
||||||
// See `:help :map-modes`
|
// 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" }
|
log.debug { "Mapping disabled for key: $isMappingDisabled" }
|
||||||
return isMappingDisabled
|
return isMappingDisabled
|
||||||
}
|
}
|
||||||
|
@ -8,53 +8,21 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.command
|
package com.maddyhome.idea.vim.command
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents arguments used when executing a command - either an action, operator or motion
|
* [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.
|
||||||
* TODO: Remove, rename or otherwise refactor this class
|
* The terminology is taken directly from vim.
|
||||||
*
|
* If no count is provided, [count0] defaults to zero.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
data class OperatorArguments
|
data class OperatorArguments(
|
||||||
|
val isOperatorPending: Boolean,
|
||||||
@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,
|
|
||||||
val count0: Int,
|
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)
|
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 {
|
enum class CurrentCommandState {
|
||||||
NEW_COMMAND,
|
NEW_COMMAND,
|
||||||
READY,
|
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