1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-10-18 01:42:46 +02:00

Compare commits

...

10 Commits

Author SHA1 Message Date
edb9b194bb
Set plugin version to chylex-12 2022-07-06 01:15:57 +02:00
Alex Plate
eae7ed95e2
Revert "Fix(VIM-308) Undo requires one more step if the cursor is not at the position where it was after undo"
This reverts commit 9dbe3c33
2022-07-06 01:15:56 +02:00
a1e2ae0eb9
Change matchit plugin to use HTML patterns in unrecognized files 2022-07-06 01:15:56 +02:00
eae2e3b6b8
Fix put command not working with multiple cursors 2022-07-06 01:15:56 +02:00
c2d997a520
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2022-07-06 01:15:56 +02:00
e2a8a3c21a
Add VimScript 'renaming()' function 2022-07-06 01:08:14 +02:00
9b7fee6163
Add support for repeatable actions with ':raction' 2022-07-06 01:08:14 +02:00
d0f9d3dc70
Implement partial code completion support in macros
Works ok with insertions (Enter, Ctrl+Enter) but not with replacements (Tab)
2022-07-06 01:08:14 +02:00
8d3a69b338
Disable taking over arrow keys and Home/End 2022-07-06 01:08:14 +02:00
3c530474a1
Set custom plugin version 2022-07-06 01:08:14 +02:00
23 changed files with 393 additions and 96 deletions

View File

@ -1,9 +1,9 @@
# suppress inspection "UnusedProperty" for whole file
ideaVersion=LATEST-EAP-SNAPSHOT
ideaVersion=2022.1.2
downloadIdeaSources=true
instrumentPluginCode=true
version=SNAPSHOT
version=chylex-12
javaVersion=11
remoteRobotVersion=0.11.10
antlrVersion=4.10.1

View File

@ -148,6 +148,10 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
if (keyCode == KeyEvent.VK_TAB && editor.isTemplateActive()) return false
if ((keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_ENTER) && editor.appCodeTemplateCaptured()) return false
if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT) return false
if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN) return false
if (keyCode == KeyEvent.VK_HOME || keyCode == KeyEvent.VK_END) return false
if (editor.inInsertMode) {
if (keyCode == KeyEvent.VK_TAB) {

View File

@ -233,7 +233,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns
} else {
return null
this.htmlPatterns
}
}

View File

@ -22,12 +22,14 @@ import com.intellij.openapi.application.runWriteAction
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.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.VimStateMachine
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.command.VimStateMachine
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
@ -37,9 +39,9 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.editorMode
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
@ -84,22 +86,20 @@ class VimSurroundExtension : VimExtension {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext) {
setOperatorFunction(Operator())
setOperatorFunction(Operator(supportsMultipleCursors = false)) // TODO
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
}
private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway
if (!Operator().apply(editor.ij, context.ij, SelectionType.CHARACTER_WISE)) {
if (!Operator(supportsMultipleCursors = true).apply(editor.ij, context.ij, SelectionType.CHARACTER_WISE)) {
return
}
runWriteAction {
// Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
editor.ij.caretModel.moveToOffset(selectionStart)
}
}
}
@ -120,6 +120,10 @@ class VimSurroundExtension : VimExtension {
companion object {
fun change(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) {
editor.runWithEveryCaretAndRestore { changeAtCaret(editor, charFrom, newSurround) }
}
fun changeAtCaret(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) {
// We take over the " register, so preserve it
val oldValue: List<KeyStroke>? = getRegister(REGISTER)
// Empty the " register
@ -184,25 +188,43 @@ class VimSurroundExtension : VimExtension {
}
}
private class Operator : OperatorFunction {
private class Operator(private val supportsMultipleCursors: Boolean) : OperatorFunction {
override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean {
val c = getChar(editor)
if (c.code == 0) return true
val pair = getOrInputPair(c, editor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor) ?: return false
runWriteAction {
val change = VimPlugin.getChange()
val leftSurround = pair.first
val primaryCaret = editor.caretModel.primaryCaret
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, leftSurround)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + leftSurround.length, pair.second)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
if (supportsMultipleCursors) {
editor.runWithEveryCaretAndRestore {
applyOnce(editor, change, pair)
}
}
else {
applyOnce(editor, change, pair)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
}
}
return true
}
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>) {
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor)
if (range != null) {
val primaryCaret = editor.caretModel.primaryCaret
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, pair.first)
change.insertText(
IjVimEditor(editor),
IjVimCaret(primaryCaret),
range.endOffset + pair.first.length,
pair.second
)
}
}
private fun getSurroundRange(editor: Editor): TextRange? = when (editor.editorMode) {
VimStateMachine.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor.vim)

View File

@ -50,7 +50,10 @@ import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt;
import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.key.KeyHandlerKeeper;
import com.maddyhome.idea.vim.listener.VimInsertListener;
import com.maddyhome.idea.vim.newapi.*;
import com.maddyhome.idea.vim.newapi.IjExecutionContext;
import com.maddyhome.idea.vim.newapi.IjExecutionContextKt;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.options.OptionConstants;
import com.maddyhome.idea.vim.options.OptionScope;
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
@ -200,6 +203,61 @@ public class ChangeGroup extends VimChangeGroupBase {
return new Pair<>(range, type);
}
/**
* Delete the range of text.
*
* @param editor The editor to delete the text from
* @param caret The caret to be moved after deletion
* @param range The range to delete
* @param type The type of deletion
* @param isChange Is from a change action
* @return true if able to delete the text, false if not
*/
@Override
public boolean deleteRange(@NotNull VimEditor editor,
@NotNull VimCaret caret,
@NotNull TextRange range,
@Nullable SelectionType type,
boolean isChange,
boolean noYank) {
// Update the last column before we delete, or we might be retrieving the data for a line that no longer exists
UserDataManager.setVimLastColumn(((IjVimCaret) caret).getCaret(), InlayHelperKt.getInlayAwareVisualColumn(((IjVimCaret) caret).getCaret()));
boolean removeLastNewLine = removeLastNewLine(editor, range, type);
final boolean res = deleteText(editor, range, type, noYank);
if (removeLastNewLine) {
int textLength = ((IjVimEditor) editor).getEditor().getDocument().getTextLength();
((IjVimEditor) editor).getEditor().getDocument().deleteString(textLength - 1, textLength);
}
if (res) {
int pos = EditorHelper.normalizeOffset(((IjVimEditor) editor).getEditor(), range.getStartOffset(), isChange);
if (type == SelectionType.LINE_WISE) {
pos = VimPlugin.getMotion()
.moveCaretToLineWithStartOfLineOption(editor, editor.offsetToLogicalPosition(pos).getLine(),
caret);
}
injector.getMotion().moveCaret(editor, caret, pos);
}
return res;
}
private boolean removeLastNewLine(@NotNull VimEditor editor, @NotNull TextRange range, @Nullable SelectionType type) {
int endOffset = range.getEndOffset();
int fileSize = EditorHelperRt.getFileSize(((IjVimEditor) editor).getEditor());
if (endOffset > fileSize) {
if (injector.getOptionService().isSet(OptionScope.GLOBAL.INSTANCE, OptionConstants.ideastrictmodeName, OptionConstants.ideastrictmodeName)) {
throw new IllegalStateException("Incorrect offset. File size: " + fileSize + ", offset: " + endOffset);
}
endOffset = fileSize;
}
return type == SelectionType.LINE_WISE &&
range.getStartOffset() != 0 &&
((IjVimEditor) editor).getEditor().getDocument().getCharsSequence().charAt(endOffset - 1) != '\n' &&
endOffset == fileSize;
}
@Override
public void insertLineAround(@NotNull VimEditor editor, @NotNull ExecutionContext context, int shift) {
com.maddyhome.idea.vim.newapi.ChangeGroupKt.insertLineAround(editor, context, shift);
@ -228,7 +286,8 @@ public class ChangeGroup extends VimChangeGroupBase {
@NotNull VimCaret caret,
@NotNull ExecutionContext context,
@NotNull Argument argument,
@NotNull OperatorArguments operatorArguments) {
@NotNull OperatorArguments operatorArguments,
boolean noYank) {
int count0 = operatorArguments.getCount0();
// Vim treats cw as ce and cW as cE if cursor is on a non-blank character
final Command motion = argument.getMotion();
@ -306,7 +365,7 @@ public class ChangeGroup extends VimChangeGroupBase {
Pair<TextRange, SelectionType> deleteRangeAndType =
getDeleteRangeAndType(editor, caret, context, argument, true, operatorArguments.withCount0(count0));
if (deleteRangeAndType == null) return false;
return changeRange(editor, caret, deleteRangeAndType.getFirst(), deleteRangeAndType.getSecond(), context);
return changeRange(editor, caret, deleteRangeAndType.getFirst(), deleteRangeAndType.getSecond(), context, noYank);
}
}
@ -436,7 +495,8 @@ public class ChangeGroup extends VimChangeGroupBase {
@NotNull VimCaret caret,
@NotNull TextRange range,
@NotNull SelectionType type,
ExecutionContext context) {
@Nullable ExecutionContext context,
boolean noYank) {
int col = 0;
int lines = 0;
if (type == SelectionType.BLOCK_WISE) {
@ -450,7 +510,7 @@ public class ChangeGroup extends VimChangeGroupBase {
final VimLogicalPosition lp = editor.offsetToLogicalPosition(injector.getMotion().moveCaretToLineStartSkipLeading(editor, caret));
boolean res = deleteRange(editor, caret, range, type, true);
boolean res = deleteRange(editor, caret, range, type, true, noYank);
if (res) {
if (type == SelectionType.LINE_WISE) {
// Please don't use `getDocument().getText().isEmpty()` because it converts CharSequence into String
@ -675,7 +735,7 @@ public class ChangeGroup extends VimChangeGroupBase {
}
}
if (pos > wsoff) {
deleteText(editor, new TextRange(wsoff, pos), null);
deleteText(editor, new TextRange(wsoff, pos), null, false);
}
}
}

View File

@ -92,7 +92,7 @@ public class ProcessGroup extends VimProcessGroupBase {
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd);
VimStateMachine.getInstance(editor).pushModes(VimStateMachine.Mode.CMD_LINE, VimStateMachine.SubMode.NONE);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.activate(((IjVimEditor) editor).getEditor(), ((IjExecutionContext) context).getContext(), ":", initText, 1);
panel.activate(((IjVimEditor) editor).getEditor(), ((IjExecutionContext) context).getContext(), ":", initText, cmd.getCount());
}
@Override
@ -123,7 +123,7 @@ public class ProcessGroup extends VimProcessGroupBase {
logger.debug("processing command");
final String text = panel.getText();
String text = panel.getText();
if (!panel.getLabel().equals(":")) {
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
@ -134,7 +134,15 @@ public class ProcessGroup extends VimProcessGroupBase {
if (logger.isDebugEnabled()) logger.debug("swing=" + SwingUtilities.isEventDispatchThread());
VimInjectorKt.getInjector().getVimscriptExecutor().execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext.INSTANCE);
int repeat = 1;
if (text.contains("raction ")) {
text = text.replace("raction ", "action ");
repeat = panel.getCount();
}
for (int i = 0; i < repeat; i++) {
VimInjectorKt.getInjector().getVimscriptExecutor().execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext.INSTANCE);
}
}
catch (ExException e) {
VimPlugin.showMessage(e.getMessage());

View File

@ -97,7 +97,22 @@ class PutGroup : VimPutBase() {
EditorHelper.getOrderedCaretsList(editor.ij).map { IjVimCaret(it) }
}
injector.application.runWriteAction {
myCarets.forEach { caret -> putForCaret(editor, caret, data, additionalData, context, text) }
val singleCaret = myCarets.singleOrNull()
if (singleCaret != null) {
putForCaret(editor, singleCaret, data, additionalData, context, text)
}
else {
val lines = text.text.split('\n')
if (lines.size != myCarets.size) {
myCarets.forEach { caret -> putForCaret(editor, caret, data, additionalData, context, text) }
}
else {
myCarets.asReversed().forEachIndexed { index, caret ->
val line = ProcessedTextData(lines[index], text.typeInRegister, text.transferableData)
putForCaret(editor, caret, data, additionalData, context, line)
}
}
}
}
}

View File

@ -22,6 +22,7 @@ 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
@ -106,3 +107,41 @@ val Caret.vimLine: Int
*/
val Editor.vimLine: Int
get() = this.caretModel.currentCaret.vimLine
inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
val caretModel = this.caretModel
val carets = if (this.inBlockSubMode) 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

@ -25,9 +25,7 @@ import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.components.Service
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
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.OptionScope
@ -49,7 +47,7 @@ class UndoRedoHelper : UndoRedoBase() {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
} else {
val editor = CommonDataKeys.EDITOR.getData(context.ij)?.vim
performUntilFileChanges(editor, { undoManager.isUndoAvailable(fileEditor) }, { undoManager.undo(fileEditor) })
undoManager.undo(fileEditor)
editor?.carets()?.forEach {
val ijCaret = it.ij
val hasSelection = ijCaret.hasSelection()
@ -75,30 +73,14 @@ class UndoRedoHelper : UndoRedoBase() {
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
} else {
val editor = CommonDataKeys.EDITOR.getData(context.ij)?.vim
performUntilFileChanges(editor, { undoManager.isRedoAvailable(fileEditor) }, { undoManager.redo(fileEditor) })
undoManager.redo(fileEditor)
if (editor?.primaryCaret()?.ij?.hasSelection() == true) {
undoManager.redo(fileEditor)
}
editor?.carets()?.forEach { it.ij.removeSelection() }
}
return true
}
return false
}
private fun performUntilFileChanges(editor: IjVimEditor?, check: () -> Boolean, action: Runnable) {
if (editor == null) return
val vimDocument = editor.document
val changeListener = object : ChangesListener {
var hasChanged = false
override fun documentChanged(change: ChangesListener.Change) {
hasChanged = true
}
}
vimDocument.addChangeListener(changeListener)
while (check() && !changeListener.hasChanged) {
action.run()
}
vimDocument.removeChangeListener(changeListener)
}
}

View File

@ -19,8 +19,10 @@
package com.maddyhome.idea.vim.listener
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.codeInsight.lookup.LookupManagerListener
import com.intellij.codeInsight.lookup.impl.LookupImpl
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
@ -35,10 +37,10 @@ import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.VimStateMachine
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.helper.EditorDataContext
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.helper.inNormalMode
@ -49,6 +51,8 @@ import com.maddyhome.idea.vim.options.OptionScope
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper
import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
/**
* @author Alex Plate
@ -60,6 +64,8 @@ object IdeaSpecifics {
private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
override fun beforeActionPerformed(action: AnAction, dataContext: DataContext, event: AnActionEvent) {
if (!VimPlugin.isEnabled()) return
@ -68,19 +74,52 @@ object IdeaSpecifics {
editor = hostEditor
}
//region Track action id
if (VimPlugin.getOptionService().isSet(OptionScope.GLOBAL, OptionConstants.trackactionidsName)) {
if (action !is NotificationService.ActionIdNotifier.CopyActionId && action !is NotificationService.ActionIdNotifier.StopTracking) {
val id: String? = ActionManager.getInstance().getId(action) ?: (action.shortcutSet as? ProxyShortcutSet)?.actionId
VimPlugin.getNotifications(dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id)
val id: String? = ActionManager.getInstance().getId(action) ?: (action.shortcutSet as? ProxyShortcutSet)?.actionId
VimPlugin.getNotifications(dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id)
}
if (hostEditor != null && action is ChooseItemAction && hostEditor.vimStateMachine?.isRecording == true) {
val lookup = LookupManager.getActiveLookup(hostEditor)
if (lookup != null) {
val charsToRemove = hostEditor.caretModel.primaryCaret.offset - lookup.lookupStart
val register = VimPlugin.getRegister()
val backSpace = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0)
repeat(charsToRemove) {
register.recordKeyStroke(backSpace)
}
completionPrevDocumentLength = hostEditor.document.textLength - charsToRemove
completionPrevDocumentOffset = lookup.lookupStart
}
}
//endregion
}
override fun afterActionPerformed(action: AnAction, dataContext: DataContext, event: AnActionEvent) {
if (!VimPlugin.isEnabled()) return
val editor = editor
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
@ -99,7 +138,7 @@ object IdeaSpecifics {
}
//endregion
editor = null
this.editor = null
}
}

View File

@ -0,0 +1,48 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2021 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.vimscript.model.functions.handlers
import com.intellij.refactoring.rename.inplace.InplaceRefactoring
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.vimscript.model.VimLContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
object RenamingFunctionHandler : FunctionHandler() {
override val name = "renaming"
override val minimumNumberOfArguments = 0
override val maximumNumberOfArguments = 0
override fun doFunction(
argumentValues: List<Expression>,
editor: VimEditor,
context: ExecutionContext,
vimContext: VimLContext,
): VimDataType {
return if (InplaceRefactoring.getActiveInplaceRenamer(editor.ij) == null)
VimInt.ZERO
else
VimInt.ONE
}
}

View File

@ -14,5 +14,6 @@
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.TolowerFunctionHandler" name="tolower"/>
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.ToupperFunctionHandler" name="toupper"/>
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.JoinFunctionHandler" name="join"/>
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.RenamingFunctionHandler" name="renaming"/>
</extensions>
</idea-plugin>
</idea-plugin>

View File

@ -1,4 +1,4 @@
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IdeaVim</name>
<id>IdeaVIM</id>
<change-notes><![CDATA[
@ -65,7 +65,7 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul>
]]></description>
<version>SNAPSHOT</version>
<version>chylex</version>
<vendor>JetBrains</vendor>
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->

View File

@ -75,28 +75,6 @@ class UndoActionTest : VimTestCase() {
assertFalse(hasSelection())
}
fun `test cursor movements do not require additional undo`() {
val keys = listOf("a1<Esc>ea2<Esc>ea3<Esc>", "uu")
val before = """
A Discovery
${c}I found it in a legendary land
all rocks and lavender and tufted grass,
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent()
val after = """
A Discovery
I1 found$c it in a legendary land
all rocks and lavender and tufted grass,
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent()
doTest(keys, before, after, VimStateMachine.Mode.COMMAND, VimStateMachine.SubMode.NONE)
assertFalse(hasSelection())
}
private fun hasSelection(): Boolean {
val editor = myFixture.editor
return editor.caretModel.primaryCaret.hasSelection()

View File

@ -17,6 +17,7 @@
*/
package com.maddyhome.idea.vim.action.change.change
import com.maddyhome.idea.vim.action.copy.YankMotionAction
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@ -34,6 +35,19 @@ class ChangeMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO
override val duplicateWith: Char = 'c'
private var isMultiCaret = false
override fun baseExecute(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments
): Boolean {
isMultiCaret = YankMotionAction().yankIfMultiCaret(cmd, editor, context, operatorArguments)
return super.baseExecute(editor, caret, context, cmd, operatorArguments)
}
override fun execute(
editor: VimEditor,
caret: VimCaret,
@ -46,7 +60,8 @@ class ChangeMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO
caret,
context,
argument,
operatorArguments
operatorArguments,
noYank = isMultiCaret
)
}
}

View File

@ -17,6 +17,7 @@
*/
package com.maddyhome.idea.vim.action.change.change
import com.maddyhome.idea.vim.action.copy.YankVisualAction
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@ -37,6 +38,18 @@ class ChangeVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MULTIKEY_UNDO, CommandFlags.FLAG_EXIT_VISUAL)
private var isMultiCaret = false
override fun beforeExecution(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
caretsAndSelections: Map<VimCaret, VimSelection>
): Boolean {
isMultiCaret = YankVisualAction().yankIfMultiCaret(editor, caretsAndSelections)
return super.beforeExecution(editor, context, cmd, caretsAndSelections)
}
override fun executeAction(
editor: VimEditor,
caret: VimCaret,
@ -50,7 +63,8 @@ class ChangeVisualAction : VisualOperatorActionHandler.ForEachCaret() {
caret,
range.toVimTextRange(false),
range.type,
context
context,
noYank = isMultiCaret
)
}
}

View File

@ -17,6 +17,7 @@
*/
package com.maddyhome.idea.vim.action.change.delete
import com.maddyhome.idea.vim.action.copy.YankMotionAction
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@ -36,6 +37,19 @@ class DeleteMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO
override val duplicateWith: Char = 'd'
private var isMultiCaret = false
override fun baseExecute(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments
): Boolean {
isMultiCaret = YankMotionAction().yankIfMultiCaret(cmd, editor, context, operatorArguments)
return super.baseExecute(editor, caret, context, cmd, operatorArguments)
}
override fun execute(
editor: VimEditor,
caret: VimCaret,
@ -53,7 +67,7 @@ class DeleteMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO
val (first, second) = injector.changeGroup
.getDeleteRangeAndType(editor, caret, context, argument, false, operatorArguments)
?: return false
return injector.changeGroup.deleteRange(editor, caret, first, second, false)
return injector.changeGroup.deleteRange(editor, caret, first, second, false, noYank = isMultiCaret)
}
}
}

View File

@ -17,6 +17,7 @@
*/
package com.maddyhome.idea.vim.action.change.delete
import com.maddyhome.idea.vim.action.copy.YankVisualAction
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@ -37,6 +38,18 @@ class DeleteVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
private var isMultiCaret = false
override fun beforeExecution(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
caretsAndSelections: Map<VimCaret, VimSelection>,
): Boolean {
isMultiCaret = YankVisualAction().yankIfMultiCaret(editor, caretsAndSelections)
return super.beforeExecution(editor, context, cmd, caretsAndSelections)
}
override fun executeAction(
editor: VimEditor,
caret: VimCaret,
@ -45,7 +58,13 @@ class DeleteVisualAction : VisualOperatorActionHandler.ForEachCaret() {
range: VimSelection,
operatorArguments: OperatorArguments,
): Boolean {
val selectionType = range.type
return injector.changeGroup.deleteRange(editor, caret, range.toVimTextRange(false), selectionType, false)
return injector.changeGroup.deleteRange(
editor,
caret,
range.toVimTextRange(false),
range.type,
false,
noYank = isMultiCaret
)
}
}

View File

@ -38,8 +38,27 @@ class YankMotionAction : VimActionHandler.SingleExecution(), DuplicableOperatorA
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
return executeYank(cmd, editor, context, operatorArguments)
}
private fun executeYank(
cmd: Command,
editor: VimEditor,
context: ExecutionContext,
operatorArguments: OperatorArguments,
): Boolean {
val argument = cmd.argument ?: return false
return injector.yank.yankMotion(editor, context, argument, operatorArguments)
}
fun yankIfMultiCaret(cmd: Command, editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): Boolean {
if (editor.nativeCarets().size > 1) {
executeYank(cmd, editor, context, operatorArguments)
return true
}
else {
return false
}
}
}

View File

@ -44,6 +44,13 @@ class YankVisualAction : VisualOperatorActionHandler.SingleExecution() {
cmd: Command,
caretsAndSelections: Map<VimCaret, VimSelection>,
operatorArguments: OperatorArguments,
): Boolean {
return executeYank(editor, caretsAndSelections)
}
private fun executeYank(
editor: VimEditor,
caretsAndSelections: Map<VimCaret, VimSelection>,
): Boolean {
val selections = caretsAndSelections.values
val starts: MutableList<Int> = ArrayList()
@ -58,4 +65,14 @@ class YankVisualAction : VisualOperatorActionHandler.SingleExecution() {
val endsArray = ends.toIntArray()
return injector.yank.yankRange(editor, TextRange(startsArray, endsArray), vimSelection.type, true)
}
fun yankIfMultiCaret(editor: VimEditor, caretsAndSelections: Map<VimCaret, VimSelection>): Boolean {
if (caretsAndSelections.size > 1) {
executeYank(editor, caretsAndSelections)
return true
}
else {
return false
}
}
}

View File

@ -83,14 +83,14 @@ interface VimChangeGroup {
fun getDeleteRangeAndType2(editor: VimEditor, caret: VimCaret, context: ExecutionContext, argument: Argument, isChange: Boolean, operatorArguments: OperatorArguments): Pair<TextRange, SelectionType>?
fun deleteRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: SelectionType?, isChange: Boolean): Boolean
fun deleteRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: SelectionType?, isChange: Boolean, noYank: Boolean = false): Boolean
fun deleteRange2(editor: VimEditor, caret: VimCaret, range: TextRange, type: SelectionType): Boolean
fun changeCharacters(editor: VimEditor, caret: VimCaret, count: Int): Boolean
fun changeEndOfLine(editor: VimEditor, caret: VimCaret, count: Int): Boolean
fun changeMotion(editor: VimEditor, caret: VimCaret, context: ExecutionContext, argument: Argument, operatorArguments: OperatorArguments): Boolean
fun changeMotion(editor: VimEditor, caret: VimCaret, context: ExecutionContext, argument: Argument, operatorArguments: OperatorArguments, noYank: Boolean = false): Boolean
fun changeCaseToggleCharacter(editor: VimEditor, caret: VimCaret, count: Int): Boolean
@ -98,7 +98,7 @@ interface VimChangeGroup {
fun changeCaseRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: Char): Boolean
fun changeRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: SelectionType, context: ExecutionContext?): Boolean
fun changeRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: SelectionType, context: ExecutionContext?, noYank: Boolean = false): Boolean
fun changeCaseMotion(editor: VimEditor, caret: VimCaret, context: ExecutionContext?, type: Char, argument: Argument, operatorArguments: OperatorArguments): Boolean

View File

@ -4,10 +4,10 @@ import com.maddyhome.idea.vim.KeyHandler
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.VimStateMachine
import com.maddyhome.idea.vim.command.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.command.VimStateMachine
import com.maddyhome.idea.vim.command.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.common.Offset
import com.maddyhome.idea.vim.common.OperatedRange
@ -17,10 +17,10 @@ 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.EditorActionHandlerBase
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.inSingleCommandMode
import com.maddyhome.idea.vim.helper.usesVirtualSpace
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_END
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
@ -132,6 +132,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
editor: VimEditor,
range: TextRange,
type: SelectionType?,
noYank: Boolean = false
): Boolean {
var updatedRange = range
// Fix for https://youtrack.jetbrains.net/issue/VIM-35
@ -145,6 +146,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
}
}
if (type == null ||
noYank ||
editor.inInsertMode || injector.registerGroup.storeText(editor, updatedRange, type, true)
) {
val startOffsets = updatedRange.startOffsets
@ -851,6 +853,7 @@ abstract class VimChangeGroupBase : VimChangeGroup {
range: TextRange,
type: SelectionType?,
isChange: Boolean,
noYank: Boolean
): Boolean {
// Update the last column before we delete, or we might be retrieving the data for a line that no longer exists

View File

@ -66,7 +66,7 @@ sealed class ChangeEditorActionHandler : EditorActionHandlerBase(false) {
): Boolean
}
final override fun baseExecute(
override fun baseExecute(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,