1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-18 19:24:55 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
8cad0e6434 Fix(VIM-3615): Escape closes dialog while waiting for more keys 2024-08-25 10:06:16 +02:00
160 changed files with 1603 additions and 2054 deletions

1
.gitattributes vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val state = injector.vimState
var lastCommand = VimRepeater.lastChangeCommand
val lastCommand = VimRepeater.lastChangeCommand
if (lastCommand == null && Extension.lastExtensionHandler == null) return false
@@ -57,7 +57,12 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
)
} else if (!repeatHandler && lastCommand != null) {
if (cmd.rawCount > 0) {
lastCommand = lastCommand.copy(rawCount = cmd.rawCount)
lastCommand.rawCount = cmd.count
val arg = lastCommand.argument
if (arg != null) {
val mot = arg.motion
mot.rawCount = 0
}
}
state.executingCommand = lastCommand

View File

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

View File

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

View File

@@ -69,8 +69,7 @@ object VimExtensionFacade {
@JvmStatic
@Deprecated(
"Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
@Deprecated("Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
ReplaceWith(
"VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
"com.maddyhome.idea.vim.VimPlugin"
@@ -196,7 +195,7 @@ object VimExtensionFacade {
@JvmStatic
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
val reg = injector.registerGroup.getRegister(register) ?: return null
val reg = caret.registerStorage.getRegister(register) ?: return null
return reg.keys
}
@@ -209,7 +208,7 @@ object VimExtensionFacade {
/** Set the current contents of the given register */
@JvmStatic
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList())
caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
}
/** Set the current contents of the given register */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.icons.VimIcons
@Service(Service.Level.APP)
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
VimPlugin.getPluginId(),
updateTimestampProperty = PROPERTY_NAME,
NotificationService.IDEAVIM_STICKY_GROUP,
VimIcons.IDEAVIM,
) {
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
companion object {
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
fun getInstance(): VimStandalonePluginUpdateChecker = service()
}
}

View File

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

View File

@@ -16,9 +16,7 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.codeInsight.template.TemplateManagerListener
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.codeInsight.template.impl.actions.NextVariableAction
import com.intellij.find.FindModelListener
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
@@ -30,7 +28,6 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.ScrollingModelImpl
import com.intellij.openapi.keymap.KeymapManager
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
@@ -61,7 +58,6 @@ internal object IdeaSpecifics {
private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
@@ -71,7 +67,6 @@ internal object IdeaSpecifics {
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
editor = hostEditor
caretOffset = hostEditor.caretModel.offset
}
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
@@ -120,8 +115,7 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return
val editor = editor
if (editor != null) {
if (action is ChooseItemAction && injector.registerGroup.isRecording) {
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
@@ -154,27 +148,9 @@ internal object IdeaSpecifics {
KeyHandler.getInstance().reset(it.vim)
}
}
else if (action is NextVariableAction && TemplateManagerImpl.getTemplateState(editor) == null) {
editor.vim.exitInsertMode(event.dataContext.vim)
KeyHandler.getInstance().reset(editor.vim)
}
//endregion
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
val scrollModel = editor.scrollingModel as ScrollingModelImpl
if (scrollModel.isScrollingNow) {
val v = scrollModel.verticalScrollOffset
val h = scrollModel.horizontalScrollOffset
scrollModel.finishAnimation()
scrollModel.scroll(h, v)
scrollModel.finishAnimation()
}
injector.scroll.scrollCaretIntoView(editor.vim)
}
}
this.editor = null
this.caretOffset = -1
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,12 @@
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<!--
~ Copyright 2003-2023 The IdeaVim authors
~
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE.txt file or at
~ https://opensource.org/licenses/MIT.
-->
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IdeaVim</name>
<id>IdeaVIM</id>
<description><![CDATA[
@@ -13,7 +21,7 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul>
]]></description>
<version>chylex</version>
<version>SNAPSHOT</version>
<vendor>JetBrains</vendor>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
@@ -125,11 +133,9 @@
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions resource-bundle="messages.IdeaVimBundle">
<group id="com.chylex.intellij.vim" text="Vim" popup="true">
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction">
<add-to-group group-id="ToolsMenu" anchor="last"/>
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/>
<action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/>
</group>
</action>
<!-- Internal -->
<!--suppress PluginXmlI18n -->
@@ -148,6 +154,5 @@
</group>
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
</actions>
</idea-plugin>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
class RedoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
class UndoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -52,8 +52,7 @@ class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
// The converted digraph character has been captured as an argument, push it back through key handler
val argument = cmd.argument as? Argument.Character ?: return false
val keyStroke = KeyStroke.getKeyStroke(argument.character)
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
return true

View File

@@ -52,8 +52,7 @@ class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
// The converted literal character has been captured as an argument, push it back through key handler
val argument = cmd.argument as? Argument.Character ?: return false
val keyStroke = KeyStroke.getKeyStroke(argument.character)
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState)
return true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ class ExEntryAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
if (editor.isOneLineMode()) return false
injector.commandLine.createCommandPrompt(editor, context, cmd.rawCount, initialText = "")
injector.commandLine.createCommandPrompt(editor, context, cmd, initialText = "")
return true
}
}

View File

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

View File

@@ -13,7 +13,6 @@ import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
@@ -28,8 +27,8 @@ class LeaveCommandLineAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val argument = cmd.argument as? Argument.ExString ?: return true
val historyType = VimHistory.Type.getTypeByLabel(argument.label.toString())
val argument = cmd.argument ?: return true
val historyType = VimHistory.Type.getTypeByLabel(argument.character.toString())
injector.historyGroup.addEntry(historyType, argument.string)
return true
}

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ class ToggleRecordingAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
return if (!injector.registerGroup.isRecording) {
val argument = cmd.argument as? Argument.Character ?: return false
val argument = cmd.argument ?: return false
val reg = argument.character
injector.registerGroup.startRecording(reg)
} else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,8 @@ import com.maddyhome.idea.vim.handler.MotionActionHandler
import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.usesVirtualSpace
abstract class MotionRightBaseAction() : MotionActionHandler.ForEachCaret() {
@CommandOrMotion(keys = ["l"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
class MotionRightAction : MotionActionHandler.ForEachCaret() {
override val motionType: MotionType = MotionType.EXCLUSIVE
override fun getOffset(
@@ -34,20 +35,24 @@ abstract class MotionRightBaseAction() : MotionActionHandler.ForEachCaret() {
operatorArguments: OperatorArguments,
): Motion {
val allowWrap = injector.options(editor).whichwrap.contains("l")
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd(editor), allowWrap)
val allowEnd = editor.usesVirtualSpace || editor.isEndAllowed ||
operatorArguments.isOperatorPending // because of `dl` removing the last character
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd = allowEnd, allowWrap)
}
protected open fun allowPastEnd(editor: VimEditor) = editor.usesVirtualSpace || editor.isEndAllowed
}
@CommandOrMotion(keys = ["l"], modes = [Mode.NORMAL, Mode.VISUAL])
class MotionRightAction : MotionRightBaseAction()
@CommandOrMotion(keys = ["<Right>", "<kRight>"], modes = [Mode.INSERT])
class MotionRightInsertAction : MotionActionHandler.ForEachCaret() {
override val motionType: MotionType = MotionType.EXCLUSIVE
@CommandOrMotion(keys = ["l"], modes = [Mode.OP_PENDING])
class MotionRightOpPendingAction : MotionRightBaseAction() {
// When the motion is used with an operator, the EOL character is counted.
// This allows e.g., `dl` to delete the last character in a line. Note that we can't use editor.isEndAllowed to give
// us this because the current mode when we execute the operator/motion is no longer OP_PENDING.
// See `:help whichwrap`. This says a delete or change operator, but it appears to apply to all operators
override fun allowPastEnd(editor: VimEditor) = true
override fun getOffset(
editor: VimEditor,
caret: ImmutableVimCaret,
context: ExecutionContext,
argument: Argument?,
operatorArguments: OperatorArguments,
): Motion {
val allowWrap = injector.options(editor).whichwrap.contains("]")
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, allowPastEnd = true, allowWrap)
}
}

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