mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-10-31 11:17:13 +01:00 
			
		
		
		
	Compare commits
	
		
			10 Commits
		
	
	
		
			customized
			...
			edb9b194bb
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| edb9b194bb | |||
|   | eae7ed95e2 | ||
| a1e2ae0eb9 | |||
| eae2e3b6b8 | |||
| c2d997a520 | |||
| e2a8a3c21a | |||
| 9b7fee6163 | |||
| d0f9d3dc70 | |||
| 8d3a69b338 | |||
| 3c530474a1 | 
| @@ -1,9 +1,9 @@ | |||||||
| # suppress inspection "UnusedProperty" for whole file | # suppress inspection "UnusedProperty" for whole file | ||||||
|  |  | ||||||
| ideaVersion=LATEST-EAP-SNAPSHOT | ideaVersion=2022.1.2 | ||||||
| downloadIdeaSources=true | downloadIdeaSources=true | ||||||
| instrumentPluginCode=true | instrumentPluginCode=true | ||||||
| version=SNAPSHOT | version=chylex-12 | ||||||
| javaVersion=11 | javaVersion=11 | ||||||
| remoteRobotVersion=0.11.10 | remoteRobotVersion=0.11.10 | ||||||
| antlrVersion=4.10.1 | antlrVersion=4.10.1 | ||||||
|   | |||||||
| @@ -148,6 +148,10 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ { | |||||||
|       if (keyCode == KeyEvent.VK_TAB && editor.isTemplateActive()) return false |       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_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 (editor.inInsertMode) { | ||||||
|         if (keyCode == KeyEvent.VK_TAB) { |         if (keyCode == KeyEvent.VK_TAB) { | ||||||
|   | |||||||
| @@ -233,7 +233,7 @@ private object FileTypePatterns { | |||||||
|     } else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") { |     } else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") { | ||||||
|       this.cMakePatterns |       this.cMakePatterns | ||||||
|     } else { |     } else { | ||||||
|       return null |       this.htmlPatterns | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,12 +22,14 @@ import com.intellij.openapi.application.runWriteAction | |||||||
| import com.intellij.openapi.editor.Editor | import com.intellij.openapi.editor.Editor | ||||||
| import com.maddyhome.idea.vim.VimPlugin | import com.maddyhome.idea.vim.VimPlugin | ||||||
| import com.maddyhome.idea.vim.api.ExecutionContext | import com.maddyhome.idea.vim.api.ExecutionContext | ||||||
|  | import com.maddyhome.idea.vim.api.VimChangeGroup | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.command.VimStateMachine |  | ||||||
| import com.maddyhome.idea.vim.command.SelectionType |  | ||||||
| import com.maddyhome.idea.vim.command.MappingMode | 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.common.TextRange | ||||||
|  | import com.maddyhome.idea.vim.extension.ExtensionHandler | ||||||
| import com.maddyhome.idea.vim.extension.VimExtension | import com.maddyhome.idea.vim.extension.VimExtension | ||||||
| import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping | import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping | ||||||
| import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister | 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.putKeyMappingIfMissing | ||||||
| import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction | import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction | ||||||
| import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister | 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.EditorHelper | ||||||
| import com.maddyhome.idea.vim.helper.editorMode | 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.key.OperatorFunction | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimCaret | import com.maddyhome.idea.vim.newapi.IjVimCaret | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor | import com.maddyhome.idea.vim.newapi.IjVimEditor | ||||||
| @@ -84,22 +86,20 @@ class VimSurroundExtension : VimExtension { | |||||||
|     override val isRepeatable = true |     override val isRepeatable = true | ||||||
|  |  | ||||||
|     override fun execute(editor: VimEditor, context: ExecutionContext) { |     override fun execute(editor: VimEditor, context: ExecutionContext) { | ||||||
|       setOperatorFunction(Operator()) |       setOperatorFunction(Operator(supportsMultipleCursors = false)) // TODO | ||||||
|       executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) |       executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private class VSurroundHandler : ExtensionHandler { |   private class VSurroundHandler : ExtensionHandler { | ||||||
|     override fun execute(editor: VimEditor, context: ExecutionContext) { |     override fun execute(editor: VimEditor, context: ExecutionContext) { | ||||||
|       val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart |  | ||||||
|       // NB: Operator ignores SelectionType anyway |       // 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 |         return | ||||||
|       } |       } | ||||||
|       runWriteAction { |       runWriteAction { | ||||||
|         // Leave visual mode |         // Leave visual mode | ||||||
|         executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) |         executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) | ||||||
|         editor.ij.caretModel.moveToOffset(selectionStart) |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -120,6 +120,10 @@ class VimSurroundExtension : VimExtension { | |||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|       fun change(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) { |       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 |         // We take over the " register, so preserve it | ||||||
|         val oldValue: List<KeyStroke>? = getRegister(REGISTER) |         val oldValue: List<KeyStroke>? = getRegister(REGISTER) | ||||||
|         // Empty the " 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 { |     override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean { | ||||||
|       val c = getChar(editor) |       val c = getChar(editor) | ||||||
|       if (c.code == 0) return true |       if (c.code == 0) return true | ||||||
|  |  | ||||||
|       val pair = getOrInputPair(c, editor) ?: return false |       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 { |       runWriteAction { | ||||||
|         val change = VimPlugin.getChange() |         val change = VimPlugin.getChange() | ||||||
|         val leftSurround = pair.first |         if (supportsMultipleCursors) { | ||||||
|         val primaryCaret = editor.caretModel.primaryCaret |           editor.runWithEveryCaretAndRestore { | ||||||
|         change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, leftSurround) |             applyOnce(editor, change, pair) | ||||||
|         change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + leftSurround.length, pair.second) |           } | ||||||
|         // Jump back to start |         } | ||||||
|         executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor) |         else { | ||||||
|  |           applyOnce(editor, change, pair) | ||||||
|  |           // Jump back to start | ||||||
|  |           executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor) | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|       return true |       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) { |     private fun getSurroundRange(editor: Editor): TextRange? = when (editor.editorMode) { | ||||||
|       VimStateMachine.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor.vim) |       VimStateMachine.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor.vim) | ||||||
|   | |||||||
| @@ -50,7 +50,10 @@ import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt; | |||||||
| import com.maddyhome.idea.vim.helper.*; | import com.maddyhome.idea.vim.helper.*; | ||||||
| import com.maddyhome.idea.vim.key.KeyHandlerKeeper; | import com.maddyhome.idea.vim.key.KeyHandlerKeeper; | ||||||
| import com.maddyhome.idea.vim.listener.VimInsertListener; | 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.OptionConstants; | ||||||
| import com.maddyhome.idea.vim.options.OptionScope; | import com.maddyhome.idea.vim.options.OptionScope; | ||||||
| import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString; | import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString; | ||||||
| @@ -200,6 +203,61 @@ public class ChangeGroup extends VimChangeGroupBase { | |||||||
|     return new Pair<>(range, type); |     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 |   @Override | ||||||
|   public void insertLineAround(@NotNull VimEditor editor, @NotNull ExecutionContext context, int shift) { |   public void insertLineAround(@NotNull VimEditor editor, @NotNull ExecutionContext context, int shift) { | ||||||
|     com.maddyhome.idea.vim.newapi.ChangeGroupKt.insertLineAround(editor, context, shift); |     com.maddyhome.idea.vim.newapi.ChangeGroupKt.insertLineAround(editor, context, shift); | ||||||
| @@ -228,7 +286,8 @@ public class ChangeGroup extends VimChangeGroupBase { | |||||||
|                               @NotNull VimCaret caret, |                               @NotNull VimCaret caret, | ||||||
|                               @NotNull ExecutionContext context, |                               @NotNull ExecutionContext context, | ||||||
|                               @NotNull Argument argument, |                               @NotNull Argument argument, | ||||||
|                               @NotNull OperatorArguments operatorArguments) { |                               @NotNull OperatorArguments operatorArguments, | ||||||
|  |                               boolean noYank) { | ||||||
|     int count0 = operatorArguments.getCount0(); |     int count0 = operatorArguments.getCount0(); | ||||||
|     // Vim treats cw as ce and cW as cE if cursor is on a non-blank character |     // Vim treats cw as ce and cW as cE if cursor is on a non-blank character | ||||||
|     final Command motion = argument.getMotion(); |     final Command motion = argument.getMotion(); | ||||||
| @@ -306,7 +365,7 @@ public class ChangeGroup extends VimChangeGroupBase { | |||||||
|       Pair<TextRange, SelectionType> deleteRangeAndType = |       Pair<TextRange, SelectionType> deleteRangeAndType = | ||||||
|         getDeleteRangeAndType(editor, caret, context, argument, true, operatorArguments.withCount0(count0)); |         getDeleteRangeAndType(editor, caret, context, argument, true, operatorArguments.withCount0(count0)); | ||||||
|       if (deleteRangeAndType == null) return false; |       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 VimCaret caret, | ||||||
|                              @NotNull TextRange range, |                              @NotNull TextRange range, | ||||||
|                              @NotNull SelectionType type, |                              @NotNull SelectionType type, | ||||||
|                              ExecutionContext context) { |                              @Nullable ExecutionContext context, | ||||||
|  |                              boolean noYank) { | ||||||
|     int col = 0; |     int col = 0; | ||||||
|     int lines = 0; |     int lines = 0; | ||||||
|     if (type == SelectionType.BLOCK_WISE) { |     if (type == SelectionType.BLOCK_WISE) { | ||||||
| @@ -450,7 +510,7 @@ public class ChangeGroup extends VimChangeGroupBase { | |||||||
|  |  | ||||||
|     final VimLogicalPosition lp = editor.offsetToLogicalPosition(injector.getMotion().moveCaretToLineStartSkipLeading(editor, caret)); |     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 (res) { | ||||||
|       if (type == SelectionType.LINE_WISE) { |       if (type == SelectionType.LINE_WISE) { | ||||||
|         // Please don't use `getDocument().getText().isEmpty()` because it converts CharSequence into String |         // 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) { |             if (pos > wsoff) { | ||||||
|               deleteText(editor, new TextRange(wsoff, pos), null); |               deleteText(editor, new TextRange(wsoff, pos), null, false); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -92,7 +92,7 @@ public class ProcessGroup extends VimProcessGroupBase { | |||||||
|     String initText = getRange(((IjVimEditor) editor).getEditor(), cmd); |     String initText = getRange(((IjVimEditor) editor).getEditor(), cmd); | ||||||
|     VimStateMachine.getInstance(editor).pushModes(VimStateMachine.Mode.CMD_LINE, VimStateMachine.SubMode.NONE); |     VimStateMachine.getInstance(editor).pushModes(VimStateMachine.Mode.CMD_LINE, VimStateMachine.SubMode.NONE); | ||||||
|     ExEntryPanel panel = ExEntryPanel.getInstance(); |     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 |   @Override | ||||||
| @@ -123,7 +123,7 @@ public class ProcessGroup extends VimProcessGroupBase { | |||||||
|  |  | ||||||
|       logger.debug("processing command"); |       logger.debug("processing command"); | ||||||
|  |  | ||||||
|       final String text = panel.getText(); |       String text = panel.getText(); | ||||||
|  |  | ||||||
|       if (!panel.getLabel().equals(":")) { |       if (!panel.getLabel().equals(":")) { | ||||||
|         // Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for |         // 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()); |       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) { |     catch (ExException e) { | ||||||
|       VimPlugin.showMessage(e.getMessage()); |       VimPlugin.showMessage(e.getMessage()); | ||||||
|   | |||||||
| @@ -97,7 +97,22 @@ class PutGroup : VimPutBase() { | |||||||
|       EditorHelper.getOrderedCaretsList(editor.ij).map { IjVimCaret(it) } |       EditorHelper.getOrderedCaretsList(editor.ij).map { IjVimCaret(it) } | ||||||
|     } |     } | ||||||
|     injector.application.runWriteAction { |     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) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ package com.maddyhome.idea.vim.helper | |||||||
|  |  | ||||||
| import com.intellij.codeWithMe.ClientId | import com.intellij.codeWithMe.ClientId | ||||||
| import com.intellij.openapi.editor.Caret | import com.intellij.openapi.editor.Caret | ||||||
|  | import com.intellij.openapi.editor.CaretState | ||||||
| import com.intellij.openapi.editor.Editor | import com.intellij.openapi.editor.Editor | ||||||
| import com.intellij.openapi.editor.ex.util.EditorUtil | import com.intellij.openapi.editor.ex.util.EditorUtil | ||||||
| import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx | import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx | ||||||
| @@ -106,3 +107,41 @@ val Caret.vimLine: Int | |||||||
|  */ |  */ | ||||||
| val Editor.vimLine: Int | val Editor.vimLine: Int | ||||||
|   get() = this.caretModel.currentCaret.vimLine |   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 | ||||||
|  |   }  | ||||||
|  | } | ||||||
|   | |||||||
| @@ -25,9 +25,7 @@ import com.intellij.openapi.command.undo.UndoManager | |||||||
| import com.intellij.openapi.components.Service | import com.intellij.openapi.components.Service | ||||||
| import com.maddyhome.idea.vim.api.ExecutionContext | import com.maddyhome.idea.vim.api.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.injector | 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.listener.SelectionVimListenerSuppressor | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor |  | ||||||
| import com.maddyhome.idea.vim.newapi.ij | import com.maddyhome.idea.vim.newapi.ij | ||||||
| import com.maddyhome.idea.vim.newapi.vim | import com.maddyhome.idea.vim.newapi.vim | ||||||
| import com.maddyhome.idea.vim.options.OptionScope | import com.maddyhome.idea.vim.options.OptionScope | ||||||
| @@ -49,7 +47,7 @@ class UndoRedoHelper : UndoRedoBase() { | |||||||
|         SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) } |         SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) } | ||||||
|       } else { |       } else { | ||||||
|         val editor = CommonDataKeys.EDITOR.getData(context.ij)?.vim |         val editor = CommonDataKeys.EDITOR.getData(context.ij)?.vim | ||||||
|         performUntilFileChanges(editor, { undoManager.isUndoAvailable(fileEditor) }, { undoManager.undo(fileEditor) }) |         undoManager.undo(fileEditor) | ||||||
|         editor?.carets()?.forEach { |         editor?.carets()?.forEach { | ||||||
|           val ijCaret = it.ij |           val ijCaret = it.ij | ||||||
|           val hasSelection = ijCaret.hasSelection() |           val hasSelection = ijCaret.hasSelection() | ||||||
| @@ -75,30 +73,14 @@ class UndoRedoHelper : UndoRedoBase() { | |||||||
|         SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) } |         SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) } | ||||||
|       } else { |       } else { | ||||||
|         val editor = CommonDataKeys.EDITOR.getData(context.ij)?.vim |         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() } |         editor?.carets()?.forEach { it.ij.removeSelection() } | ||||||
|       } |       } | ||||||
|       return true |       return true | ||||||
|     } |     } | ||||||
|     return false |     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) |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,8 +19,10 @@ | |||||||
| package com.maddyhome.idea.vim.listener | package com.maddyhome.idea.vim.listener | ||||||
|  |  | ||||||
| import com.intellij.codeInsight.lookup.Lookup | import com.intellij.codeInsight.lookup.Lookup | ||||||
|  | import com.intellij.codeInsight.lookup.LookupManager | ||||||
| import com.intellij.codeInsight.lookup.LookupManagerListener | import com.intellij.codeInsight.lookup.LookupManagerListener | ||||||
| import com.intellij.codeInsight.lookup.impl.LookupImpl | 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.Template | ||||||
| import com.intellij.codeInsight.template.TemplateEditingAdapter | import com.intellij.codeInsight.template.TemplateEditingAdapter | ||||||
| import com.intellij.codeInsight.template.TemplateManagerListener | import com.intellij.codeInsight.template.TemplateManagerListener | ||||||
| @@ -35,10 +37,10 @@ import com.intellij.openapi.actionSystem.ex.AnActionListener | |||||||
| import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet | import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet | ||||||
| import com.intellij.openapi.editor.Editor | import com.intellij.openapi.editor.Editor | ||||||
| import com.intellij.openapi.project.DumbAwareToggleAction | import com.intellij.openapi.project.DumbAwareToggleAction | ||||||
|  | import com.intellij.openapi.util.TextRange | ||||||
| import com.maddyhome.idea.vim.KeyHandler | import com.maddyhome.idea.vim.KeyHandler | ||||||
| import com.maddyhome.idea.vim.VimPlugin | import com.maddyhome.idea.vim.VimPlugin | ||||||
| import com.maddyhome.idea.vim.command.VimStateMachine | 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.EditorDataContext | ||||||
| import com.maddyhome.idea.vim.helper.vimStateMachine | import com.maddyhome.idea.vim.helper.vimStateMachine | ||||||
| import com.maddyhome.idea.vim.helper.inNormalMode | 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.datatypes.VimInt | ||||||
| import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper | import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper | ||||||
| import org.jetbrains.annotations.NonNls | import org.jetbrains.annotations.NonNls | ||||||
|  | import java.awt.event.KeyEvent | ||||||
|  | import javax.swing.KeyStroke | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @author Alex Plate |  * @author Alex Plate | ||||||
| @@ -60,6 +64,8 @@ object IdeaSpecifics { | |||||||
|     private val surrounderAction = |     private val surrounderAction = | ||||||
|       "com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction" |       "com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction" | ||||||
|     private var editor: Editor? = null |     private var editor: Editor? = null | ||||||
|  |     private var completionPrevDocumentLength: Int? = null | ||||||
|  |     private var completionPrevDocumentOffset: Int? = null | ||||||
|     override fun beforeActionPerformed(action: AnAction, dataContext: DataContext, event: AnActionEvent) { |     override fun beforeActionPerformed(action: AnAction, dataContext: DataContext, event: AnActionEvent) { | ||||||
|       if (!VimPlugin.isEnabled()) return |       if (!VimPlugin.isEnabled()) return | ||||||
|  |  | ||||||
| @@ -68,19 +74,52 @@ object IdeaSpecifics { | |||||||
|         editor = hostEditor |         editor = hostEditor | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       //region Track action id |  | ||||||
|       if (VimPlugin.getOptionService().isSet(OptionScope.GLOBAL, OptionConstants.trackactionidsName)) { |       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 | ||||||
|           val id: String? = ActionManager.getInstance().getId(action) ?: (action.shortcutSet as? ProxyShortcutSet)?.actionId |         VimPlugin.getNotifications(dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id) | ||||||
|           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) { |     override fun afterActionPerformed(action: AnAction, dataContext: DataContext, event: AnActionEvent) { | ||||||
|       if (!VimPlugin.isEnabled()) return |       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 |       //region Enter insert mode after surround with if | ||||||
|       if (surrounderAction == action.javaClass.name && surrounderItems.any { |       if (surrounderAction == action.javaClass.name && surrounderItems.any { | ||||||
|         action.templatePresentation.text.endsWith( |         action.templatePresentation.text.endsWith( | ||||||
| @@ -99,7 +138,7 @@ object IdeaSpecifics { | |||||||
|       } |       } | ||||||
|       //endregion |       //endregion | ||||||
|  |  | ||||||
|       editor = null |       this.editor = null | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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.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.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.JoinFunctionHandler" name="join"/> | ||||||
|  |     <vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.RenamingFunctionHandler" name="renaming"/> | ||||||
|   </extensions> |   </extensions> | ||||||
| </idea-plugin> | </idea-plugin> | ||||||
|   | |||||||
| @@ -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> |   <name>IdeaVim</name> | ||||||
|   <id>IdeaVIM</id> |   <id>IdeaVIM</id> | ||||||
|   <change-notes><![CDATA[ |   <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> |         <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li> | ||||||
|       </ul> |       </ul> | ||||||
|     ]]></description> |     ]]></description> | ||||||
|   <version>SNAPSHOT</version> |   <version>chylex</version> | ||||||
|   <vendor>JetBrains</vendor> |   <vendor>JetBrains</vendor> | ||||||
|  |  | ||||||
|   <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version --> |   <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version --> | ||||||
|   | |||||||
| @@ -75,28 +75,6 @@ class UndoActionTest : VimTestCase() { | |||||||
|     assertFalse(hasSelection()) |     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 { |   private fun hasSelection(): Boolean { | ||||||
|     val editor = myFixture.editor |     val editor = myFixture.editor | ||||||
|     return editor.caretModel.primaryCaret.hasSelection() |     return editor.caretModel.primaryCaret.hasSelection() | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|  */ |  */ | ||||||
| package com.maddyhome.idea.vim.action.change.change | 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.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| @@ -34,6 +35,19 @@ class ChangeMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO | |||||||
|  |  | ||||||
|   override val duplicateWith: Char = 'c' |   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( |   override fun execute( | ||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     caret: VimCaret, |     caret: VimCaret, | ||||||
| @@ -46,7 +60,8 @@ class ChangeMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO | |||||||
|       caret, |       caret, | ||||||
|       context, |       context, | ||||||
|       argument, |       argument, | ||||||
|       operatorArguments |       operatorArguments, | ||||||
|  |       noYank = isMultiCaret | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|  */ |  */ | ||||||
| package com.maddyhome.idea.vim.action.change.change | 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.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | 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) |   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( |   override fun executeAction( | ||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     caret: VimCaret, |     caret: VimCaret, | ||||||
| @@ -50,7 +63,8 @@ class ChangeVisualAction : VisualOperatorActionHandler.ForEachCaret() { | |||||||
|       caret, |       caret, | ||||||
|       range.toVimTextRange(false), |       range.toVimTextRange(false), | ||||||
|       range.type, |       range.type, | ||||||
|       context |       context, | ||||||
|  |       noYank = isMultiCaret | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|  */ |  */ | ||||||
| package com.maddyhome.idea.vim.action.change.delete | 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.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| @@ -36,6 +37,19 @@ class DeleteMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO | |||||||
|  |  | ||||||
|   override val duplicateWith: Char = 'd' |   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( |   override fun execute( | ||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     caret: VimCaret, |     caret: VimCaret, | ||||||
| @@ -53,7 +67,7 @@ class DeleteMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableO | |||||||
|       val (first, second) = injector.changeGroup |       val (first, second) = injector.changeGroup | ||||||
|         .getDeleteRangeAndType(editor, caret, context, argument, false, operatorArguments) |         .getDeleteRangeAndType(editor, caret, context, argument, false, operatorArguments) | ||||||
|         ?: return false |         ?: return false | ||||||
|       return injector.changeGroup.deleteRange(editor, caret, first, second, false) |       return injector.changeGroup.deleteRange(editor, caret, first, second, false, noYank = isMultiCaret) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|  */ |  */ | ||||||
| package com.maddyhome.idea.vim.action.change.delete | 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.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | 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) |   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( |   override fun executeAction( | ||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     caret: VimCaret, |     caret: VimCaret, | ||||||
| @@ -45,7 +58,13 @@ class DeleteVisualAction : VisualOperatorActionHandler.ForEachCaret() { | |||||||
|     range: VimSelection, |     range: VimSelection, | ||||||
|     operatorArguments: OperatorArguments, |     operatorArguments: OperatorArguments, | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     val selectionType = range.type |     return injector.changeGroup.deleteRange( | ||||||
|     return injector.changeGroup.deleteRange(editor, caret, range.toVimTextRange(false), selectionType, false) |       editor, | ||||||
|  |       caret, | ||||||
|  |       range.toVimTextRange(false), | ||||||
|  |       range.type, | ||||||
|  |       false, | ||||||
|  |       noYank = isMultiCaret | ||||||
|  |     ) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -38,8 +38,27 @@ class YankMotionAction : VimActionHandler.SingleExecution(), DuplicableOperatorA | |||||||
|     context: ExecutionContext, |     context: ExecutionContext, | ||||||
|     cmd: Command, |     cmd: Command, | ||||||
|     operatorArguments: OperatorArguments, |     operatorArguments: OperatorArguments, | ||||||
|  |   ): Boolean { | ||||||
|  |     return executeYank(cmd, editor, context, operatorArguments) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private fun executeYank( | ||||||
|  |     cmd: Command, | ||||||
|  |     editor: VimEditor, | ||||||
|  |     context: ExecutionContext, | ||||||
|  |     operatorArguments: OperatorArguments, | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     val argument = cmd.argument ?: return false |     val argument = cmd.argument ?: return false | ||||||
|     return injector.yank.yankMotion(editor, context, argument, operatorArguments) |     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 | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -44,6 +44,13 @@ class YankVisualAction : VisualOperatorActionHandler.SingleExecution() { | |||||||
|     cmd: Command, |     cmd: Command, | ||||||
|     caretsAndSelections: Map<VimCaret, VimSelection>, |     caretsAndSelections: Map<VimCaret, VimSelection>, | ||||||
|     operatorArguments: OperatorArguments, |     operatorArguments: OperatorArguments, | ||||||
|  |   ): Boolean { | ||||||
|  |     return executeYank(editor, caretsAndSelections) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private fun executeYank( | ||||||
|  |     editor: VimEditor, | ||||||
|  |     caretsAndSelections: Map<VimCaret, VimSelection>, | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     val selections = caretsAndSelections.values |     val selections = caretsAndSelections.values | ||||||
|     val starts: MutableList<Int> = ArrayList() |     val starts: MutableList<Int> = ArrayList() | ||||||
| @@ -58,4 +65,14 @@ class YankVisualAction : VisualOperatorActionHandler.SingleExecution() { | |||||||
|     val endsArray = ends.toIntArray() |     val endsArray = ends.toIntArray() | ||||||
|     return injector.yank.yankRange(editor, TextRange(startsArray, endsArray), vimSelection.type, true) |     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 | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -83,14 +83,14 @@ interface VimChangeGroup { | |||||||
|  |  | ||||||
|   fun getDeleteRangeAndType2(editor: VimEditor, caret: VimCaret, context: ExecutionContext, argument: Argument, isChange: Boolean, operatorArguments: OperatorArguments): Pair<TextRange, SelectionType>? |   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 deleteRange2(editor: VimEditor, caret: VimCaret, range: TextRange, type: SelectionType): Boolean | ||||||
|  |  | ||||||
|   fun changeCharacters(editor: VimEditor, caret: VimCaret, count: Int): Boolean |   fun changeCharacters(editor: VimEditor, caret: VimCaret, count: Int): Boolean | ||||||
|  |  | ||||||
|   fun changeEndOfLine(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 |   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 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 |   fun changeCaseMotion(editor: VimEditor, caret: VimCaret, context: ExecutionContext?, type: Char, argument: Argument, operatorArguments: OperatorArguments): Boolean | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,10 +4,10 @@ import com.maddyhome.idea.vim.KeyHandler | |||||||
| import com.maddyhome.idea.vim.command.Argument | import com.maddyhome.idea.vim.command.Argument | ||||||
| import com.maddyhome.idea.vim.command.Command | import com.maddyhome.idea.vim.command.Command | ||||||
| import com.maddyhome.idea.vim.command.CommandFlags | import com.maddyhome.idea.vim.command.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.OperatorArguments | ||||||
| import com.maddyhome.idea.vim.command.SelectionType | 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.ChangesListener | ||||||
| import com.maddyhome.idea.vim.common.Offset | import com.maddyhome.idea.vim.common.Offset | ||||||
| import com.maddyhome.idea.vim.common.OperatedRange | 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.diagnostic.vimLogger | ||||||
| import com.maddyhome.idea.vim.group.visual.VimSelection | import com.maddyhome.idea.vim.group.visual.VimSelection | ||||||
| import com.maddyhome.idea.vim.handler.EditorActionHandlerBase | import com.maddyhome.idea.vim.handler.EditorActionHandlerBase | ||||||
| import com.maddyhome.idea.vim.helper.vimStateMachine |  | ||||||
| import com.maddyhome.idea.vim.helper.inInsertMode | import com.maddyhome.idea.vim.helper.inInsertMode | ||||||
| import com.maddyhome.idea.vim.helper.inSingleCommandMode | import com.maddyhome.idea.vim.helper.inSingleCommandMode | ||||||
| import com.maddyhome.idea.vim.helper.usesVirtualSpace | 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.listener.SelectionVimListenerSuppressor | ||||||
| import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_END | import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_END | ||||||
| import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS | import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS | ||||||
| @@ -132,6 +132,7 @@ abstract class VimChangeGroupBase : VimChangeGroup { | |||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     range: TextRange, |     range: TextRange, | ||||||
|     type: SelectionType?, |     type: SelectionType?, | ||||||
|  |     noYank: Boolean = false | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     var updatedRange = range |     var updatedRange = range | ||||||
|     // Fix for https://youtrack.jetbrains.net/issue/VIM-35 |     // Fix for https://youtrack.jetbrains.net/issue/VIM-35 | ||||||
| @@ -145,6 +146,7 @@ abstract class VimChangeGroupBase : VimChangeGroup { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (type == null || |     if (type == null || | ||||||
|  |       noYank || | ||||||
|       editor.inInsertMode || injector.registerGroup.storeText(editor, updatedRange, type, true) |       editor.inInsertMode || injector.registerGroup.storeText(editor, updatedRange, type, true) | ||||||
|     ) { |     ) { | ||||||
|       val startOffsets = updatedRange.startOffsets |       val startOffsets = updatedRange.startOffsets | ||||||
| @@ -851,6 +853,7 @@ abstract class VimChangeGroupBase : VimChangeGroup { | |||||||
|     range: TextRange, |     range: TextRange, | ||||||
|     type: SelectionType?, |     type: SelectionType?, | ||||||
|     isChange: Boolean, |     isChange: Boolean, | ||||||
|  |     noYank: Boolean | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|  |  | ||||||
|     // Update the last column before we delete, or we might be retrieving the data for a line that no longer exists |     // Update the last column before we delete, or we might be retrieving the data for a line that no longer exists | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ sealed class ChangeEditorActionHandler : EditorActionHandlerBase(false) { | |||||||
|     ): Boolean |     ): Boolean | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   final override fun baseExecute( |   override fun baseExecute( | ||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     caret: VimCaret, |     caret: VimCaret, | ||||||
|     context: ExecutionContext, |     context: ExecutionContext, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user