mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-10-31 02:17:13 +01:00 
			
		
		
		
	Compare commits
	
		
			22 Commits
		
	
	
		
			0f0a73c139
			...
			customized
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7c7e7728f2 | |||
| de930ed57c | |||
| 590d5bd22d | |||
| 69c748d881 | |||
| 144cc5c3fc | |||
| fb270cdbc5 | |||
| ce6a296233 | |||
| 949f359b98 | |||
| c922426e02 | |||
| 9240e82f2d | |||
| a1639d80b0 | |||
| 7860b98107 | |||
| 7157f9c5a5 | |||
| 1522618cd6 | |||
| 09862c8356 | |||
| c6ef3f286f | |||
| b358e63444 | |||
| 7ac743c604 | |||
| db3d3fc608 | |||
| 14d313907b | |||
| fe37a69544 | |||
| 54de3dac25 | 
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | * text=auto eol=lf | ||||||
| @@ -127,6 +127,7 @@ dependencies { | |||||||
|  |  | ||||||
|     // AceJump is an optional dependency. We use their SessionManager class to check if it's active |     // AceJump is an optional dependency. We use their SessionManager class to check if it's active | ||||||
|     plugin("AceJump", "3.8.19") |     plugin("AceJump", "3.8.19") | ||||||
|  |     plugin("com.intellij.classic.ui", "243.21565.122") | ||||||
|  |  | ||||||
|     bundledPlugins("org.jetbrains.plugins.terminal", "com.intellij.modules.json") |     bundledPlugins("org.jetbrains.plugins.terminal", "com.intellij.modules.json") | ||||||
|   } |   } | ||||||
| @@ -232,6 +233,8 @@ tasks { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   compileTestKotlin { |   compileTestKotlin { | ||||||
|  |     enabled = false | ||||||
|  |      | ||||||
|     kotlinOptions { |     kotlinOptions { | ||||||
|       jvmTarget = javaVersion |       jvmTarget = javaVersion | ||||||
|       apiVersion = "2.0" |       apiVersion = "2.0" | ||||||
|   | |||||||
| @@ -16,11 +16,11 @@ | |||||||
| # https://data.services.jetbrains.com/products?code=IC | # https://data.services.jetbrains.com/products?code=IC | ||||||
| # Maven releases are here: https://www.jetbrains.com/intellij-repository/releases | # Maven releases are here: https://www.jetbrains.com/intellij-repository/releases | ||||||
| # And snapshots: https://www.jetbrains.com/intellij-repository/snapshots | # And snapshots: https://www.jetbrains.com/intellij-repository/snapshots | ||||||
| ideaVersion=2024.3.3 | ideaVersion=2024.3 | ||||||
| # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type | # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type | ||||||
| ideaType=IC | ideaType=IC | ||||||
| instrumentPluginCode=true | instrumentPluginCode=true | ||||||
| version=SNAPSHOT | version=chylex-44 | ||||||
| javaVersion=21 | javaVersion=21 | ||||||
| remoteRobotVersion=0.11.23 | remoteRobotVersion=0.11.23 | ||||||
| antlrVersion=4.10.1 | antlrVersion=4.10.1 | ||||||
| @@ -41,7 +41,6 @@ youtrackToken= | |||||||
|  |  | ||||||
| # Gradle settings | # Gradle settings | ||||||
| org.gradle.jvmargs='-Dfile.encoding=UTF-8' | org.gradle.jvmargs='-Dfile.encoding=UTF-8' | ||||||
| org.gradle.configuration-cache=true |  | ||||||
| org.gradle.caching=true | org.gradle.caching=true | ||||||
|  |  | ||||||
| # Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary | # Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary | ||||||
|   | |||||||
| @@ -0,0 +1,52 @@ | |||||||
|  | package com.maddyhome.idea.vim.action | ||||||
|  |  | ||||||
|  | import com.intellij.openapi.actionSystem.ActionUpdateThread | ||||||
|  | import com.intellij.openapi.actionSystem.AnActionEvent | ||||||
|  | import com.intellij.openapi.command.UndoConfirmationPolicy | ||||||
|  | import com.intellij.openapi.command.WriteCommandAction | ||||||
|  | import com.intellij.openapi.fileEditor.TextEditor | ||||||
|  | import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx | ||||||
|  | import com.intellij.openapi.project.DumbAwareAction | ||||||
|  | import com.maddyhome.idea.vim.KeyHandler | ||||||
|  | import com.maddyhome.idea.vim.api.injector | ||||||
|  | import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext | ||||||
|  | import com.maddyhome.idea.vim.newapi.vim | ||||||
|  | import com.maddyhome.idea.vim.state.mode.Mode | ||||||
|  |  | ||||||
|  | class VimRunLastMacroInOpenFiles : DumbAwareAction() { | ||||||
|  |   override fun update(e: AnActionEvent) { | ||||||
|  |     val lastRegister = injector.macro.lastRegister | ||||||
|  |     val isEnabled = lastRegister != 0.toChar() | ||||||
|  |  | ||||||
|  |     e.presentation.isEnabled = isEnabled | ||||||
|  |     e.presentation.text = if (isEnabled) "Run Macro '${lastRegister}' in Open Files" else "Run Last Macro in Open Files" | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   override fun getActionUpdateThread(): ActionUpdateThread { | ||||||
|  |     return ActionUpdateThread.EDT | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   override fun actionPerformed(e: AnActionEvent) { | ||||||
|  |     val project = e.project ?: return | ||||||
|  |     val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return | ||||||
|  |     val editors = fileEditorManager.allEditors.filterIsInstance<TextEditor>() | ||||||
|  |      | ||||||
|  |     WriteCommandAction.writeCommandAction(project) | ||||||
|  |       .withName(e.presentation.text) | ||||||
|  |       .withGlobalUndo() | ||||||
|  |       .withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION) | ||||||
|  |       .run<RuntimeException> { | ||||||
|  |         val reg = injector.macro.lastRegister | ||||||
|  |          | ||||||
|  |         for (editor in editors) { | ||||||
|  |           fileEditorManager.openFile(editor.file, true) | ||||||
|  |            | ||||||
|  |           val vimEditor = editor.editor.vim | ||||||
|  |           vimEditor.mode = Mode.NORMAL() | ||||||
|  |           KeyHandler.getInstance().reset(vimEditor) | ||||||
|  |            | ||||||
|  |           injector.macro.playbackRegister(vimEditor, IjEditorExecutionContext(e.dataContext), reg, 1) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -21,9 +21,7 @@ import com.intellij.openapi.editor.markup.TextAttributes | |||||||
| import com.intellij.openapi.util.Disposer | import com.intellij.openapi.util.Disposer | ||||||
| import com.intellij.util.Alarm | import com.intellij.util.Alarm | ||||||
| import com.intellij.util.Alarm.ThreadToUse | import com.intellij.util.Alarm.ThreadToUse | ||||||
| import com.jetbrains.rd.util.first |  | ||||||
| import com.maddyhome.idea.vim.VimPlugin | import com.maddyhome.idea.vim.VimPlugin | ||||||
| import com.maddyhome.idea.vim.api.ImmutableVimCaret |  | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.common.ModeChangeListener | import com.maddyhome.idea.vim.common.ModeChangeListener | ||||||
| @@ -123,9 +121,9 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis | |||||||
|     initialised = false |     initialised = false | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun yankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) { |   override fun yankPerformed(editor: VimEditor, range: TextRange) { | ||||||
|     ensureInitialised() |     ensureInitialised() | ||||||
|     highlightHandler.highlightYankRange(caretToRange) |     highlightHandler.highlightYankRange(editor.ij, range) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun modeChanged(editor: VimEditor, oldMode: Mode) { |   override fun modeChanged(editor: VimEditor, oldMode: Mode) { | ||||||
| @@ -146,25 +144,22 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis | |||||||
|     private var lastEditor: Editor? = null |     private var lastEditor: Editor? = null | ||||||
|     private val highlighters = mutableSetOf<RangeHighlighter>() |     private val highlighters = mutableSetOf<RangeHighlighter>() | ||||||
|  |  | ||||||
|     fun highlightYankRange(caretToRange: Map<ImmutableVimCaret, TextRange>) { |     fun highlightYankRange(editor: Editor, range: TextRange) { | ||||||
|       // from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted |       // from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted | ||||||
|       clearYankHighlighters() |       clearYankHighlighters() | ||||||
|  |  | ||||||
|       val editor = caretToRange.first().key.editor.ij |  | ||||||
|       lastEditor = editor |       lastEditor = editor | ||||||
|  |  | ||||||
|       val attributes = getHighlightTextAttributes(editor) |       val attributes = getHighlightTextAttributes(editor) | ||||||
|       for (range in caretToRange.values) { |       for (i in 0 until range.size()) { | ||||||
|         for (i in 0 until range.size()) { |         val highlighter = editor.markupModel.addRangeHighlighter( | ||||||
|           val highlighter = editor.markupModel.addRangeHighlighter( |           range.startOffsets[i], | ||||||
|             range.startOffsets[i], |           range.endOffsets[i], | ||||||
|             range.endOffsets[i], |           HighlighterLayer.SELECTION, | ||||||
|             HighlighterLayer.SELECTION, |           attributes, | ||||||
|             attributes, |           HighlighterTargetArea.EXACT_RANGE, | ||||||
|             HighlighterTargetArea.EXACT_RANGE, |         ) | ||||||
|           ) |         highlighters.add(highlighter) | ||||||
|           highlighters.add(highlighter) |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // from vim-highlightedyank docs: A negative number makes the highlight persistent. |       // from vim-highlightedyank docs: A negative number makes the highlight persistent. | ||||||
|   | |||||||
| @@ -230,7 +230,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 | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,7 +29,6 @@ import com.maddyhome.idea.vim.extension.exportOperatorFunction | |||||||
| import com.maddyhome.idea.vim.group.visual.VimSelection | import com.maddyhome.idea.vim.group.visual.VimSelection | ||||||
| import com.maddyhome.idea.vim.helper.exitVisualMode | import com.maddyhome.idea.vim.helper.exitVisualMode | ||||||
| import com.maddyhome.idea.vim.key.OperatorFunction | import com.maddyhome.idea.vim.key.OperatorFunction | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimCopiedText |  | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor | import com.maddyhome.idea.vim.newapi.IjVimEditor | ||||||
| import com.maddyhome.idea.vim.newapi.ij | import com.maddyhome.idea.vim.newapi.ij | ||||||
| import com.maddyhome.idea.vim.newapi.vim | import com.maddyhome.idea.vim.newapi.vim | ||||||
| @@ -154,8 +153,7 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC | |||||||
|     usedType = SelectionType.CHARACTER_WISE |     usedType = SelectionType.CHARACTER_WISE | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   val copiedText = IjVimCopiedText(usedText, (savedRegister.copiedText as IjVimCopiedText).transferableData) |   val textData = PutData.TextData(usedText, usedType, savedRegister.transferableData, savedRegister.name) | ||||||
|   val textData = PutData.TextData(savedRegister.name, copiedText, usedType) |  | ||||||
|  |  | ||||||
|   val putData = PutData( |   val putData = PutData( | ||||||
|     textData, |     textData, | ||||||
|   | |||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | package com.maddyhome.idea.vim.extension.surround | ||||||
|  |  | ||||||
|  | import com.intellij.util.text.CharSequenceSubSequence | ||||||
|  |  | ||||||
|  | internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence { | ||||||
|  |   override val length = text.length * count | ||||||
|  |  | ||||||
|  |   override fun get(index: Int): Char { | ||||||
|  |     if (index < 0 || index >= length) throw IndexOutOfBoundsException() | ||||||
|  |     return text[index % text.length] | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { | ||||||
|  |     return CharSequenceSubSequence(this, startIndex, endIndex) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   override fun toString(): String { | ||||||
|  |     return text.repeat(count) | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   companion object { | ||||||
|  |     fun of(text: CharSequence, count: Int): CharSequence { | ||||||
|  |       return when (count) { | ||||||
|  |         0 -> "" | ||||||
|  |         1 -> text | ||||||
|  |         else -> RepeatedCharSequence(text, count) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -14,6 +14,7 @@ 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.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
|  | import com.maddyhome.idea.vim.api.VimChangeGroup | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.endsWithNewLine | import com.maddyhome.idea.vim.api.endsWithNewLine | ||||||
| import com.maddyhome.idea.vim.api.getLeadingCharacterOffset | import com.maddyhome.idea.vim.api.getLeadingCharacterOffset | ||||||
| @@ -36,7 +37,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret | |||||||
| import com.maddyhome.idea.vim.extension.exportOperatorFunction | import com.maddyhome.idea.vim.extension.exportOperatorFunction | ||||||
| import com.maddyhome.idea.vim.group.findBlockRange | import com.maddyhome.idea.vim.group.findBlockRange | ||||||
| import com.maddyhome.idea.vim.helper.exitVisualMode | import com.maddyhome.idea.vim.helper.exitVisualMode | ||||||
|  | import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore | ||||||
| import com.maddyhome.idea.vim.key.OperatorFunction | import com.maddyhome.idea.vim.key.OperatorFunction | ||||||
|  | import com.maddyhome.idea.vim.newapi.IjVimCaret | ||||||
|  | import com.maddyhome.idea.vim.newapi.IjVimEditor | ||||||
| import com.maddyhome.idea.vim.newapi.ij | import com.maddyhome.idea.vim.newapi.ij | ||||||
| import com.maddyhome.idea.vim.newapi.vim | import com.maddyhome.idea.vim.newapi.vim | ||||||
| import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper | import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper | ||||||
| @@ -79,7 +83,7 @@ internal class VimSurroundExtension : VimExtension { | |||||||
|       putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true) |       putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator()) |     VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private class YSurroundHandler : ExtensionHandler { |   private class YSurroundHandler : ExtensionHandler { | ||||||
| @@ -107,7 +111,7 @@ internal class VimSurroundExtension : VimExtension { | |||||||
|         val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset) |         val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset) | ||||||
|         if (lastNonWhiteSpaceOffset != null) { |         if (lastNonWhiteSpaceOffset != null) { | ||||||
|           val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1) |           val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1) | ||||||
|           performSurround(pair, range, it) |           performSurround(pair, range, it, count = operatorArguments.count1) | ||||||
|         } |         } | ||||||
| //        it.moveToOffset(lineStartOffset) | //        it.moveToOffset(lineStartOffset) | ||||||
|       } |       } | ||||||
| @@ -130,15 +134,13 @@ internal class VimSurroundExtension : VimExtension { | |||||||
|  |  | ||||||
|   private class VSurroundHandler : ExtensionHandler { |   private class VSurroundHandler : ExtensionHandler { | ||||||
|     override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { |     override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { | ||||||
|       val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart |  | ||||||
|       // NB: Operator ignores SelectionType anyway |       // NB: Operator ignores SelectionType anyway | ||||||
|       if (!Operator().apply(editor, context, editor.mode.selectionType)) { |       if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) { | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       runWriteAction { |       runWriteAction { | ||||||
|         // Leave visual mode |         // Leave visual mode | ||||||
|         editor.exitVisualMode() |         editor.exitVisualMode() | ||||||
|         editor.ij.caretModel.moveToOffset(selectionStart) |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -159,6 +161,10 @@ internal class VimSurroundExtension : VimExtension { | |||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|       fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) { |       fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) { | ||||||
|  |         editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) } | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) { | ||||||
|         // Save old register values for carets |         // Save old register values for carets | ||||||
|         val surroundings = editor.sortedCarets() |         val surroundings = editor.sortedCarets() | ||||||
|           .map { |           .map { | ||||||
| @@ -201,7 +207,7 @@ internal class VimSurroundExtension : VimExtension { | |||||||
|               val trimmedValue = if (newSurround.shouldTrim) innerValue.trim() else innerValue |               val trimmedValue = if (newSurround.shouldTrim) innerValue.trim() else innerValue | ||||||
|               it.first + trimmedValue + it.second |               it.first + trimmedValue + it.second | ||||||
|             } ?: innerValue |             } ?: innerValue | ||||||
|             val textData = PutData.TextData(null, injector.clipboardManager.dumbCopiedText(text), SelectionType.CHARACTER_WISE) |             val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), null) | ||||||
|             val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false) |             val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false) | ||||||
|  |  | ||||||
|             surrounding.caret to putData |             surrounding.caret to putData | ||||||
| @@ -272,21 +278,42 @@ internal class VimSurroundExtension : VimExtension { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private class Operator : OperatorFunction { |   private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction { | ||||||
|     override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { |     override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { | ||||||
|       val ijEditor = editor.ij |       val ijEditor = vimEditor.ij | ||||||
|       val c = getChar(ijEditor) |       val c = getChar(ijEditor) | ||||||
|       if (c.code == 0) return true |       if (c.code == 0) return true | ||||||
|  |  | ||||||
|       val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false |       val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false | ||||||
|       // XXX: Will it work with line-wise or block-wise selections? |  | ||||||
|       val range = getSurroundRange(editor.currentCaret()) ?: return false |       runWriteAction { | ||||||
|       performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE) |         val change = VimPlugin.getChange() | ||||||
|       // Jump back to start |         if (supportsMultipleCursors) { | ||||||
|       executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) |           ijEditor.runWithEveryCaretAndRestore { | ||||||
|  |             applyOnce(ijEditor, change, pair, count) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |           applyOnce(ijEditor, change, pair, count) | ||||||
|  |           // Jump back to start | ||||||
|  |           executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|       return true |       return true | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: SurroundPair, count: Int) { | ||||||
|  |       // XXX: Will it work with line-wise or block-wise selections? | ||||||
|  |       val primaryCaret = editor.caretModel.primaryCaret | ||||||
|  |       val range = getSurroundRange(primaryCaret.vim) | ||||||
|  |       if (range != null) { | ||||||
|  |         val start = RepeatedCharSequence.of(pair.first, count) | ||||||
|  |         val end = RepeatedCharSequence.of(pair.second, count) | ||||||
|  |         change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start) | ||||||
|  |         change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun getSurroundRange(caret: VimCaret): TextRange? { |     private fun getSurroundRange(caret: VimCaret): TextRange? { | ||||||
|       val editor = caret.editor |       val editor = caret.editor | ||||||
|       if (editor.mode is Mode.CMD_LINE) { |       if (editor.mode is Mode.CMD_LINE) { | ||||||
| @@ -386,15 +413,15 @@ private fun getChar(editor: Editor): Char { | |||||||
|   return res |   return res | ||||||
| } | } | ||||||
|  |  | ||||||
| private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) { | private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) { | ||||||
|   runWriteAction { |   runWriteAction { | ||||||
|     val editor = caret.editor |     val editor = caret.editor | ||||||
|     val change = VimPlugin.getChange() |     val change = VimPlugin.getChange() | ||||||
|     val leftSurround = pair.first + if (tagsOnNewLines) "\n" else "" |     val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count) | ||||||
|  |  | ||||||
|     val isEOF = range.endOffset == editor.text().length |     val isEOF = range.endOffset == editor.text().length | ||||||
|     val hasNewLine = editor.endsWithNewLine() |     val hasNewLine = editor.endsWithNewLine() | ||||||
|     val rightSurround = if (tagsOnNewLines) { |     val rightSurround = (if (tagsOnNewLines) { | ||||||
|       if (isEOF && !hasNewLine) { |       if (isEOF && !hasNewLine) { | ||||||
|         "\n" + pair.second |         "\n" + pair.second | ||||||
|       } else { |       } else { | ||||||
| @@ -402,7 +429,7 @@ private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCare | |||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       pair.second |       pair.second | ||||||
|     } |     }).let { RepeatedCharSequence.of(it, count) } | ||||||
|  |  | ||||||
|     change.insertText(editor, caret, range.startOffset, leftSurround) |     change.insertText(editor, caret, range.startOffset, leftSurround) | ||||||
|     change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround) |     change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround) | ||||||
|   | |||||||
| @@ -42,7 +42,6 @@ import com.maddyhome.idea.vim.newapi.ij | |||||||
| import com.maddyhome.idea.vim.state.mode.Mode | import com.maddyhome.idea.vim.state.mode.Mode | ||||||
| import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService | import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService | ||||||
| import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService | import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService | ||||||
| import kotlin.math.min |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Provides all the insert/replace related functionality |  * Provides all the insert/replace related functionality | ||||||
| @@ -141,6 +140,7 @@ class ChangeGroup : VimChangeGroupBase() { | |||||||
|     context: ExecutionContext, |     context: ExecutionContext, | ||||||
|     range: TextRange, |     range: TextRange, | ||||||
|   ) { |   ) { | ||||||
|  |     val startPos = editor.offsetToBufferPosition(caret.offset) | ||||||
|     val startOffset = editor.getLineStartForOffset(range.startOffset) |     val startOffset = editor.getLineStartForOffset(range.startOffset) | ||||||
|     val endOffset = editor.getLineEndForOffset(range.endOffset) |     val endOffset = editor.getLineEndForOffset(range.endOffset) | ||||||
|     val ijEditor = (editor as IjVimEditor).editor |     val ijEditor = (editor as IjVimEditor).editor | ||||||
| @@ -165,11 +165,7 @@ class ChangeGroup : VimChangeGroupBase() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     val afterAction = { |     val afterAction = { | ||||||
|       val firstLine = editor.offsetToBufferPosition( |       caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line)) | ||||||
|         min(startOffset.toDouble(), endOffset.toDouble()).toInt() |  | ||||||
|       ).line |  | ||||||
|       val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine) |  | ||||||
|       caret.moveToOffset(newOffset) |  | ||||||
|       restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line) |       restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line) | ||||||
|     } |     } | ||||||
|     if (project != null) { |     if (project != null) { | ||||||
|   | |||||||
| @@ -143,7 +143,7 @@ object IjOptions { | |||||||
|     addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true)) |     addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true)) | ||||||
|   val commandOrMotionAnnotation: ToggleOption = |   val commandOrMotionAnnotation: ToggleOption = | ||||||
|     addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true)) |     addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true)) | ||||||
|   val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isHidden = true)) |   val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true)) | ||||||
|   val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true)) |   val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true)) | ||||||
|   val vimscriptFunctionAnnotation: ToggleOption = |   val vimscriptFunctionAnnotation: ToggleOption = | ||||||
|     addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true)) |     addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true)) | ||||||
|   | |||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | package com.maddyhome.idea.vim.group | ||||||
|  |  | ||||||
|  | import com.intellij.codeInsight.daemon.ReferenceImporter | ||||||
|  | import com.intellij.openapi.actionSystem.CommonDataKeys | ||||||
|  | import com.intellij.openapi.actionSystem.DataContext | ||||||
|  | import com.intellij.openapi.application.ApplicationManager | ||||||
|  | import com.intellij.openapi.application.ReadAction | ||||||
|  | import com.intellij.openapi.command.WriteCommandAction | ||||||
|  | import com.intellij.openapi.editor.Editor | ||||||
|  | import com.intellij.openapi.fileEditor.FileDocumentManager | ||||||
|  | import com.intellij.openapi.progress.ProgressIndicator | ||||||
|  | import com.intellij.openapi.progress.ProgressManager | ||||||
|  | import com.intellij.openapi.progress.Task | ||||||
|  | import com.intellij.psi.PsiDocumentManager | ||||||
|  | import com.intellij.psi.PsiElement | ||||||
|  | import com.intellij.psi.PsiRecursiveElementWalkingVisitor | ||||||
|  | import java.util.function.BooleanSupplier | ||||||
|  |  | ||||||
|  | internal object MacroAutoImport { | ||||||
|  |   fun run(editor: Editor, dataContext: DataContext) { | ||||||
|  |     val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return | ||||||
|  |     val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return | ||||||
|  |  | ||||||
|  |     if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) { | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val importers = ReferenceImporter.EP_NAME.extensionList | ||||||
|  |     if (importers.isEmpty()) { | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) { | ||||||
|  |       override fun run(indicator: ProgressIndicator) { | ||||||
|  |         val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> { | ||||||
|  |           val fixes = mutableListOf<BooleanSupplier>() | ||||||
|  |  | ||||||
|  |           file.accept(object : PsiRecursiveElementWalkingVisitor() { | ||||||
|  |             override fun visitElement(element: PsiElement) { | ||||||
|  |               for (reference in element.references) { | ||||||
|  |                 if (reference.resolve() != null) { | ||||||
|  |                   continue | ||||||
|  |                 } | ||||||
|  |                 for (importer in importers) { | ||||||
|  |                   importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true) | ||||||
|  |                     ?.let(fixes::add) | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |               super.visitElement(element) | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |  | ||||||
|  |           return@nonBlocking fixes | ||||||
|  |         }.executeSynchronously() | ||||||
|  |  | ||||||
|  |         ApplicationManager.getApplication().invokeAndWait { | ||||||
|  |           WriteCommandAction.writeCommandAction(project) | ||||||
|  |             .withName("Auto Import") | ||||||
|  |             .withGroupId("IdeaVimAutoImportAfterMacro") | ||||||
|  |             .shouldRecordActionForActiveDocument(true) | ||||||
|  |             .run<RuntimeException> { | ||||||
|  |               fixes.forEach { it.asBoolean } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -21,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector | |||||||
| import com.maddyhome.idea.vim.helper.MessageHelper.message | import com.maddyhome.idea.vim.helper.MessageHelper.message | ||||||
| import com.maddyhome.idea.vim.macro.VimMacroBase | import com.maddyhome.idea.vim.macro.VimMacroBase | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor | import com.maddyhome.idea.vim.newapi.IjVimEditor | ||||||
|  | import com.maddyhome.idea.vim.newapi.ij | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Used to handle playback of macros |  * Used to handle playback of macros | ||||||
| @@ -89,6 +90,9 @@ internal class MacroGroup : VimMacroBase() { | |||||||
|         } finally { |         } finally { | ||||||
|           keyStack.removeFirst() |           keyStack.removeFirst() | ||||||
|         } |         } | ||||||
|  |         if (!isInternalMacro) { | ||||||
|  |           MacroAutoImport.run(editor.ij, context.ij) | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (isInternalMacro) { |       if (isInternalMacro) { | ||||||
|   | |||||||
| @@ -34,15 +34,12 @@ import com.intellij.openapi.ui.Messages | |||||||
| import com.intellij.openapi.util.SystemInfo | import com.intellij.openapi.util.SystemInfo | ||||||
| import com.maddyhome.idea.vim.VimPlugin | import com.maddyhome.idea.vim.VimPlugin | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.globalOptions |  | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.handler.KeyMapIssue | import com.maddyhome.idea.vim.handler.KeyMapIssue | ||||||
| import com.maddyhome.idea.vim.helper.MessageHelper | import com.maddyhome.idea.vim.helper.MessageHelper | ||||||
| import com.maddyhome.idea.vim.key.ShortcutOwner | import com.maddyhome.idea.vim.key.ShortcutOwner | ||||||
| import com.maddyhome.idea.vim.key.ShortcutOwnerInfo | import com.maddyhome.idea.vim.key.ShortcutOwnerInfo | ||||||
| import com.maddyhome.idea.vim.newapi.globalIjOptions | import com.maddyhome.idea.vim.newapi.globalIjOptions | ||||||
| import com.maddyhome.idea.vim.newapi.ijOptions |  | ||||||
| import com.maddyhome.idea.vim.options.OptionConstants |  | ||||||
| import com.maddyhome.idea.vim.statistic.ActionTracker | import com.maddyhome.idea.vim.statistic.ActionTracker | ||||||
| import com.maddyhome.idea.vim.ui.VimEmulationConfigurable | import com.maddyhome.idea.vim.ui.VimEmulationConfigurable | ||||||
| import com.maddyhome.idea.vim.vimscript.services.VimRcService | import com.maddyhome.idea.vim.vimscript.services.VimRcService | ||||||
| @@ -62,55 +59,11 @@ internal class NotificationService(private val project: Project?) { | |||||||
|   @Suppress("unused") |   @Suppress("unused") | ||||||
|   constructor() : this(null) |   constructor() : this(null) | ||||||
|  |  | ||||||
|   fun notifyAboutIdeaPut() { |   fun notifyAboutNewUndo() {} | ||||||
|     val notification = Notification( |  | ||||||
|       IDEAVIM_NOTIFICATION_ID, |  | ||||||
|       IDEAVIM_NOTIFICATION_TITLE, |  | ||||||
|       """Add <code>ideaput</code> to <code>clipboard</code> option to perform a put via the IDE<br/><b><code>set clipboard+=ideaput</code></b>""", |  | ||||||
|       NotificationType.INFORMATION, |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     notification.addAction(OpenIdeaVimRcAction(notification)) |   fun notifyAboutIdeaPut() {} | ||||||
|  |  | ||||||
|     notification.addAction( |   fun notifyAboutIdeaJoin(editor: VimEditor) {} | ||||||
|       AppendToIdeaVimRcAction( |  | ||||||
|         notification, |  | ||||||
|         "set clipboard^=ideaput", |  | ||||||
|         "ideaput", |  | ||||||
|       ) { |  | ||||||
|         // Technically, we're supposed to prepend values to clipboard so that it's not added to the "exclude" item. |  | ||||||
|         // Since we don't handle exclude, it's safe to append. But let's be clean. |  | ||||||
|         injector.globalOptions().clipboard.prependValue(OptionConstants.clipboard_ideaput) |  | ||||||
|       }, |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     notification.notify(project) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   fun notifyAboutIdeaJoin(editor: VimEditor) { |  | ||||||
|     val notification = Notification( |  | ||||||
|       IDEAVIM_NOTIFICATION_ID, |  | ||||||
|       IDEAVIM_NOTIFICATION_TITLE, |  | ||||||
|       """Put <b><code>set ideajoin</code></b> into your <code>~/.ideavimrc</code> to perform a join via the IDE""", |  | ||||||
|       NotificationType.INFORMATION, |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     notification.addAction(OpenIdeaVimRcAction(notification)) |  | ||||||
|  |  | ||||||
|     notification.addAction( |  | ||||||
|       AppendToIdeaVimRcAction( |  | ||||||
|         notification, |  | ||||||
|         "set ideajoin", |  | ||||||
|         "ideajoin" |  | ||||||
|       ) { |  | ||||||
|         // This is a global-local option. Setting it will always set the global value |  | ||||||
|         injector.ijOptions(editor).ideajoin = true |  | ||||||
|       }, |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     notification.addAction(HelpLink(ideajoinExamplesUrl)) |  | ||||||
|     notification.notify(project) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   fun enableRepeatingMode() = Messages.showYesNoDialog( |   fun enableRepeatingMode() = Messages.showYesNoDialog( | ||||||
|     "Do you want to enable repeating keys in macOS on press and hold?\n\n" + |     "Do you want to enable repeating keys in macOS on press and hold?\n\n" + | ||||||
| @@ -280,16 +233,16 @@ internal class NotificationService(private val project: Project?) { | |||||||
|  |  | ||||||
|       notification = |       notification = | ||||||
|         Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also { |         Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also { | ||||||
|           it.whenExpired { notification = null } |         it.whenExpired { notification = null } | ||||||
|           it.addAction(StopTracking()) |         it.addAction(StopTracking()) | ||||||
|  |  | ||||||
|           if (id != null || possibleIDs?.size == 1) { |         if (id != null || possibleIDs?.size == 1) { | ||||||
|             it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project)) |           it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project)) | ||||||
|           } |  | ||||||
|  |  | ||||||
|           it.notify(project) |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         it.notify(project) | ||||||
|  |       } | ||||||
|  |  | ||||||
|       if (id != null) { |       if (id != null) { | ||||||
|         ActionTracker.Util.logTrackedAction(id) |         ActionTracker.Util.logTrackedAction(id) | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -25,10 +25,9 @@ import org.jetbrains.annotations.Nullable; | |||||||
| import javax.swing.*; | import javax.swing.*; | ||||||
| import java.awt.event.KeyEvent; | import java.awt.event.KeyEvent; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * This group works with command associated with copying and pasting text |  * This group works with command associated with copying and pasting text | ||||||
|  */ |  */ | ||||||
| @@ -128,7 +127,7 @@ public class RegisterGroup extends VimRegisterGroupBase implements PersistentSta | |||||||
|           final String text = VimPlugin.getXML().getSafeXmlText(textElement); |           final String text = VimPlugin.getXML().getSafeXmlText(textElement); | ||||||
|           if (text != null) { |           if (text != null) { | ||||||
|             logger.trace("Register data parsed"); |             logger.trace("Register data parsed"); | ||||||
|             register = new Register(key, injector.getClipboardManager().dumbCopiedText(text), type); |             register = new Register(key, type, text, Collections.emptyList()); | ||||||
|           } |           } | ||||||
|           else { |           else { | ||||||
|             logger.trace("Cannot parse register data"); |             logger.trace("Cannot parse register data"); | ||||||
|   | |||||||
| @@ -36,7 +36,6 @@ import com.maddyhome.idea.vim.ide.isClionNova | |||||||
| import com.maddyhome.idea.vim.ide.isRider | import com.maddyhome.idea.vim.ide.isRider | ||||||
| import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS | import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimCaret | import com.maddyhome.idea.vim.newapi.IjVimCaret | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimCopiedText |  | ||||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor | import com.maddyhome.idea.vim.newapi.IjVimEditor | ||||||
| import com.maddyhome.idea.vim.newapi.ij | import com.maddyhome.idea.vim.newapi.ij | ||||||
| import com.maddyhome.idea.vim.newapi.vim | import com.maddyhome.idea.vim.newapi.vim | ||||||
| @@ -127,7 +126,7 @@ internal class PutGroup : VimPutBase() { | |||||||
|       point.dispose() |       point.dispose() | ||||||
|       if (!caret.isValid) return@forEach |       if (!caret.isValid) return@forEach | ||||||
|  |  | ||||||
|       val caretPossibleEndOffset = lastPastedRegion?.endOffset ?: (startOffset + text.copiedText.text.length) |       val caretPossibleEndOffset = lastPastedRegion?.endOffset ?: (startOffset + text.text.length) | ||||||
|       val endOffset = if (data.indent) { |       val endOffset = if (data.indent) { | ||||||
|         doIndent( |         doIndent( | ||||||
|           vimEditor, |           vimEditor, | ||||||
| @@ -179,10 +178,12 @@ internal class PutGroup : VimPutBase() { | |||||||
|     val allContentsBefore = CopyPasteManager.getInstance().allContents |     val allContentsBefore = CopyPasteManager.getInstance().allContents | ||||||
|     val sizeBeforeInsert = allContentsBefore.size |     val sizeBeforeInsert = allContentsBefore.size | ||||||
|     val firstItemBefore = allContentsBefore.firstOrNull() |     val firstItemBefore = allContentsBefore.firstOrNull() | ||||||
|     logger.debug { "Copied text: ${text.copiedText}" } |     logger.debug { "Transferable classes: ${text.transferableData.joinToString { it.javaClass.name }}" } | ||||||
|     val (textContent, transferableData) = text.copiedText as IjVimCopiedText |  | ||||||
|     val origContent: TextBlockTransferable = |     val origContent: TextBlockTransferable = | ||||||
|       injector.clipboardManager.setClipboardText(textContent, textContent, transferableData) as TextBlockTransferable |       injector.clipboardManager.setClipboardText( | ||||||
|  |       text.text, | ||||||
|  |       transferableData = text.transferableData, | ||||||
|  |     ) as TextBlockTransferable | ||||||
|     val allContentsAfter = CopyPasteManager.getInstance().allContents |     val allContentsAfter = CopyPasteManager.getInstance().allContents | ||||||
|     val sizeAfterInsert = allContentsAfter.size |     val sizeAfterInsert = allContentsAfter.size | ||||||
|     try { |     try { | ||||||
| @@ -190,7 +191,7 @@ internal class PutGroup : VimPutBase() { | |||||||
|     } finally { |     } finally { | ||||||
|       val textInClipboard = (firstItemBefore as? TextBlockTransferable) |       val textInClipboard = (firstItemBefore as? TextBlockTransferable) | ||||||
|         ?.getTransferData(DataFlavor.stringFlavor) as? String |         ?.getTransferData(DataFlavor.stringFlavor) as? String | ||||||
|       val textOnTop = textInClipboard != null && textInClipboard != text.copiedText.text |       val textOnTop = textInClipboard != null && textInClipboard != text.text | ||||||
|       if (sizeBeforeInsert != sizeAfterInsert || textOnTop) { |       if (sizeBeforeInsert != sizeAfterInsert || textOnTop) { | ||||||
|         // Sometimes an inserted text replaces an existing one. E.g. on insert with + or * register |         // Sometimes an inserted text replaces an existing one. E.g. on insert with + or * register | ||||||
|         (CopyPasteManager.getInstance() as? CopyPasteManagerEx)?.run { removeContent(origContent) } |         (CopyPasteManager.getInstance() as? CopyPasteManagerEx)?.run { removeContent(origContent) } | ||||||
|   | |||||||
| @@ -342,7 +342,7 @@ public class EditorHelper { | |||||||
|  |  | ||||||
|     final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight); |     final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight); | ||||||
|     final @NotNull VimEditor editor1 = new IjVimEditor(editor); |     final @NotNull VimEditor editor1 = new IjVimEditor(editor); | ||||||
|     final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1; |     final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount(); | ||||||
|     final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine); |     final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine); | ||||||
|  |  | ||||||
|     // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen. |     // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen. | ||||||
|   | |||||||
| @@ -12,6 +12,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 | ||||||
| @@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.api.injector | |||||||
| import com.maddyhome.idea.vim.group.IjOptionConstants | import com.maddyhome.idea.vim.group.IjOptionConstants | ||||||
| import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint | import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint | ||||||
| import com.maddyhome.idea.vim.newapi.globalIjOptions | import com.maddyhome.idea.vim.newapi.globalIjOptions | ||||||
|  | import com.maddyhome.idea.vim.newapi.vim | ||||||
|  | import com.maddyhome.idea.vim.state.mode.inBlockSelection | ||||||
| import java.awt.Component | import java.awt.Component | ||||||
| import javax.swing.JComponent | import javax.swing.JComponent | ||||||
| import javax.swing.JTable | import javax.swing.JTable | ||||||
| @@ -98,3 +101,41 @@ internal val Caret.vimLine: Int | |||||||
|  */ |  */ | ||||||
| internal val Editor.vimLine: Int | internal val Editor.vimLine: Int | ||||||
|   get() = this.caretModel.currentCaret.vimLine |   get() = this.caretModel.currentCaret.vimLine | ||||||
|  |  | ||||||
|  | internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) { | ||||||
|  |   val caretModel = this.caretModel | ||||||
|  |   val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets | ||||||
|  |   if (carets == null || carets.size == 1) { | ||||||
|  |     action() | ||||||
|  |   } | ||||||
|  |   else { | ||||||
|  |     var initialDocumentSize = this.document.textLength | ||||||
|  |     var documentSizeDifference = 0 | ||||||
|  |  | ||||||
|  |     val caretOffsets = carets.map { it.selectionStart to it.selectionEnd } | ||||||
|  |     val restoredCarets = mutableListOf<CaretState>() | ||||||
|  |  | ||||||
|  |     caretModel.removeSecondaryCarets() | ||||||
|  |      | ||||||
|  |     for ((selectionStart, selectionEnd) in caretOffsets) { | ||||||
|  |       if (selectionStart == selectionEnd) { | ||||||
|  |         caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference) | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         caretModel.primaryCaret.setSelection( | ||||||
|  |           selectionStart + documentSizeDifference, | ||||||
|  |           selectionEnd + documentSizeDifference | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       action() | ||||||
|  |       restoredCarets.add(caretModel.caretsAndSelections.single()) | ||||||
|  |  | ||||||
|  |       val documentLength = this.document.textLength | ||||||
|  |       documentSizeDifference += documentLength - initialDocumentSize | ||||||
|  |       initialDocumentSize = documentLength | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     caretModel.caretsAndSelections = restoredCarets | ||||||
|  |   }  | ||||||
|  | } | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.ex.ActionUtil | |||||||
| import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks | import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks | ||||||
| import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet | import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet | ||||||
| import com.intellij.openapi.actionSystem.impl.SimpleDataContext | import com.intellij.openapi.actionSystem.impl.SimpleDataContext | ||||||
|  | import com.intellij.openapi.actionSystem.impl.Utils | ||||||
| import com.intellij.openapi.application.ex.ApplicationManagerEx | import com.intellij.openapi.application.ex.ApplicationManagerEx | ||||||
| import com.intellij.openapi.command.CommandProcessor | import com.intellij.openapi.command.CommandProcessor | ||||||
| import com.intellij.openapi.command.UndoConfirmationPolicy | import com.intellij.openapi.command.UndoConfirmationPolicy | ||||||
| @@ -111,6 +112,7 @@ internal class IjActionExecutor : VimActionExecutor { | |||||||
|       ActionManager.getInstance(), |       ActionManager.getInstance(), | ||||||
|       0, |       0, | ||||||
|     ) |     ) | ||||||
|  |     Utils.initUpdateSession(event) | ||||||
|     // beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems |     // beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems | ||||||
|     //   because rider uses an async update method. See VIM-1819. |     //   because rider uses an async update method. See VIM-1819. | ||||||
|     // This method executes inside lastUpdateAndCheckDumb |     // This method executes inside lastUpdateAndCheckDumb | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ internal object ScrollViewHelper { | |||||||
|     // that this needs to be replaced as a more or less dumb line for line rewrite. |     // that this needs to be replaced as a more or less dumb line for line rewrite. | ||||||
|     val topLine = getVisualLineAtTopOfScreen(editor) |     val topLine = getVisualLineAtTopOfScreen(editor) | ||||||
|     val bottomLine = getVisualLineAtBottomOfScreen(editor) |     val bottomLine = getVisualLineAtBottomOfScreen(editor) | ||||||
|     val lastLine = vimEditor.getVisualLineCount() - 1 |     val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount | ||||||
|  |  | ||||||
|     // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred |     // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred | ||||||
|     val scrollOffset = injector.options(vimEditor).scrolloff |     val scrollOffset = injector.options(vimEditor).scrolloff | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import com.intellij.openapi.editor.markup.HighlighterLayer | |||||||
| import com.intellij.openapi.editor.markup.HighlighterTargetArea | import com.intellij.openapi.editor.markup.HighlighterTargetArea | ||||||
| import com.intellij.openapi.editor.markup.RangeHighlighter | import com.intellij.openapi.editor.markup.RangeHighlighter | ||||||
| import com.intellij.openapi.editor.markup.TextAttributes | import com.intellij.openapi.editor.markup.TextAttributes | ||||||
|  | import com.intellij.util.application | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.globalOptions | import com.maddyhome.idea.vim.api.globalOptions | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| @@ -30,6 +31,7 @@ import com.maddyhome.idea.vim.state.mode.inVisualMode | |||||||
| import org.jetbrains.annotations.Contract | import org.jetbrains.annotations.Contract | ||||||
| import java.awt.Font | import java.awt.Font | ||||||
| import java.util.* | import java.util.* | ||||||
|  | import javax.swing.Timer | ||||||
|  |  | ||||||
| internal fun updateSearchHighlights( | internal fun updateSearchHighlights( | ||||||
|   pattern: String?, |   pattern: String?, | ||||||
| @@ -84,6 +86,12 @@ internal fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, en | |||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | val removeHighlightsEditors = mutableListOf<Editor>() | ||||||
|  | val removeHighlightsTimer = Timer(400) { | ||||||
|  |   removeHighlightsEditors.forEach(::removeSearchHighlights) | ||||||
|  |   removeHighlightsEditors.clear() | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Refreshes current search highlights for all visible editors |  * Refreshes current search highlights for all visible editors | ||||||
|  */ |  */ | ||||||
| @@ -125,27 +133,43 @@ private fun updateSearchHighlights( | |||||||
|       // hlsearch (+ incsearch/noincsearch) |       // hlsearch (+ incsearch/noincsearch) | ||||||
|       // Make sure the range fits this editor. Note that Vim will use the same range for all windows. E.g., given |       // Make sure the range fits this editor. Note that Vim will use the same range for all windows. E.g., given | ||||||
|       // `:1,5s/foo`, Vim will highlight all occurrences of `foo` in the first five lines of all visible windows |       // `:1,5s/foo`, Vim will highlight all occurrences of `foo` in the first five lines of all visible windows | ||||||
|       val vimEditor = editor.vim |       val isSearching = injector.commandLine.getActiveCommandLine() != null | ||||||
|       val editorLastLine = vimEditor.lineCount() - 1 |       application.invokeLater { | ||||||
|       val searchStartLine = searchRange?.startLine ?: 0 |         val vimEditor = editor.vim | ||||||
|       val searchEndLine = (searchRange?.endLine ?: -1).coerceAtMost(editorLastLine) |         val editorLastLine = vimEditor.lineCount() - 1 | ||||||
|       if (searchStartLine <= editorLastLine) { |         val searchStartLine = searchRange?.startLine ?: 0 | ||||||
|         val results = |         val searchEndLine = (searchRange?.endLine ?: -1).coerceAtMost(editorLastLine) | ||||||
|           injector.searchHelper.findAll( |         if (searchStartLine <= editorLastLine) { | ||||||
|             vimEditor, |           val visibleArea = editor.scrollingModel.visibleAreaOnScrollingFinished | ||||||
|             pattern, |           val visibleTopLeft = visibleArea.location | ||||||
|             searchStartLine, |           val visibleBottomRight = visibleArea.location.apply { translate(visibleArea.width, visibleArea.height) } | ||||||
|             searchEndLine, |           val visibleStartOffset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(visibleTopLeft)) | ||||||
|             shouldIgnoreCase(pattern, shouldIgnoreSmartCase) |           val visibleEndOffset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(visibleBottomRight)) | ||||||
|           ) |           val visibleStartLine = editor.document.getLineNumber(visibleStartOffset) | ||||||
|         if (results.isNotEmpty()) { |           val visibleEndLine = editor.document.getLineNumber(visibleEndOffset) | ||||||
|           if (editor === currentEditor?.ij) { |           removeSearchHighlights(editor) | ||||||
|             currentMatchOffset = findClosestMatch(results, initialOffset, count1, forwards) |  | ||||||
|  |           val results = | ||||||
|  |             injector.searchHelper.findAll( | ||||||
|  |               vimEditor, | ||||||
|  |               pattern, | ||||||
|  |               searchStartLine.coerceAtLeast(visibleStartLine), | ||||||
|  |               searchEndLine.coerceAtMost(visibleEndLine), | ||||||
|  |               shouldIgnoreCase(pattern, shouldIgnoreSmartCase) | ||||||
|  |             ) | ||||||
|  |           if (results.isNotEmpty()) { | ||||||
|  |             if (editor === currentEditor?.ij) { | ||||||
|  |               currentMatchOffset = findClosestMatch(results, initialOffset, count1, forwards) | ||||||
|  |             } | ||||||
|  |             highlightSearchResults(editor, pattern, results, currentMatchOffset) | ||||||
|  |             if (!isSearching) { | ||||||
|  |               removeHighlightsEditors.add(editor) | ||||||
|  |               removeHighlightsTimer.restart() | ||||||
|  |             } | ||||||
|           } |           } | ||||||
|           highlightSearchResults(editor, pattern, results, currentMatchOffset) |  | ||||||
|         } |         } | ||||||
|  |         editor.vimLastSearch = pattern | ||||||
|       } |       } | ||||||
|       editor.vimLastSearch = pattern |  | ||||||
|     } else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) { |     } else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) { | ||||||
|       // nohlsearch + incsearch. Even though search highlights are disabled, we still show a highlight (current editor |       // nohlsearch + incsearch. Even though search highlights are disabled, we still show a highlight (current editor | ||||||
|       // only), because 'incsearch' is active. But we don't show a search if Visual is active (behind Command-line of |       // only), because 'incsearch' is active. But we don't show a search if Visual is active (behind Command-line of | ||||||
| @@ -179,6 +203,7 @@ private fun updateSearchHighlights( | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   removeHighlightsTimer.restart() | ||||||
|   return currentEditorCurrentMatchOffset |   return currentEditorCurrentMatchOffset | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -204,7 +229,7 @@ private fun removeSearchHighlights(editor: Editor) { | |||||||
|  */ |  */ | ||||||
| @Contract("_, _, false -> false; _, null, true -> false") | @Contract("_, _, false -> false; _, null, true -> false") | ||||||
| private fun shouldAddAllSearchHighlights(editor: Editor, newPattern: String?, hlSearch: Boolean): Boolean { | private fun shouldAddAllSearchHighlights(editor: Editor, newPattern: String?, hlSearch: Boolean): Boolean { | ||||||
|   return hlSearch && newPattern != null && newPattern != editor.vimLastSearch && newPattern != "" |   return hlSearch && newPattern != null && newPattern != "" | ||||||
| } | } | ||||||
|  |  | ||||||
| private fun findClosestMatch( | private fun findClosestMatch( | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ import com.intellij.openapi.fileEditor.TextEditor | |||||||
| import com.intellij.openapi.fileEditor.TextEditorWithPreview | import com.intellij.openapi.fileEditor.TextEditorWithPreview | ||||||
| import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider | import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider | ||||||
| import com.intellij.openapi.util.registry.Registry | import com.intellij.openapi.util.registry.Registry | ||||||
|  | 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.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| @@ -28,6 +29,8 @@ import com.maddyhome.idea.vim.common.InsertSequence | |||||||
| import com.maddyhome.idea.vim.newapi.IjVimCaret | import com.maddyhome.idea.vim.newapi.IjVimCaret | ||||||
| import com.maddyhome.idea.vim.newapi.globalIjOptions | import com.maddyhome.idea.vim.newapi.globalIjOptions | ||||||
| import com.maddyhome.idea.vim.newapi.ij | import com.maddyhome.idea.vim.newapi.ij | ||||||
|  | import com.maddyhome.idea.vim.state.mode.SelectionType | ||||||
|  | import com.maddyhome.idea.vim.state.mode.inVisualMode | ||||||
| import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService | import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -75,15 +78,7 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService { | |||||||
|       // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo |       // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo | ||||||
|       editor.runWithChangeTracking { |       editor.runWithChangeTracking { | ||||||
|         undoManager.undo(fileEditor) |         undoManager.undo(fileEditor) | ||||||
|  |         restoreVisualMode(editor) | ||||||
|         // We execute undo one more time if the previous one just restored selection |  | ||||||
|         if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) { |  | ||||||
|           undoManager.undo(fileEditor) |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       CommandProcessor.getInstance().runUndoTransparentAction { |  | ||||||
|         removeSelections(editor) |  | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) { |       runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) { | ||||||
| @@ -230,4 +225,21 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService { | |||||||
|     val hasChanges: Boolean |     val hasChanges: Boolean | ||||||
|       get() = changeListener.hasChanged || initialPath != editor.getPath() |       get() = changeListener.hasChanged || initialPath != editor.getPath() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   private fun restoreVisualMode(editor: VimEditor) { | ||||||
|  |     if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) { | ||||||
|  |       val detectedMode = VimPlugin.getVisualMotion().detectSelectionType(editor) | ||||||
|  |  | ||||||
|  |       // Visual block selection is restored into multiple carets, so multi-carets that form a block are always | ||||||
|  |       // identified as visual block mode, leading to false positives. | ||||||
|  |       // Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore | ||||||
|  |       // visual block mode. | ||||||
|  |       val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE) | ||||||
|  |         SelectionType.CHARACTER_WISE | ||||||
|  |       else | ||||||
|  |         detectedMode | ||||||
|  |  | ||||||
|  |       VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode) | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ import com.intellij.openapi.editor.VisualPosition | |||||||
| import com.intellij.openapi.editor.markup.RangeHighlighter | import com.intellij.openapi.editor.markup.RangeHighlighter | ||||||
| import com.intellij.openapi.util.Key | import com.intellij.openapi.util.Key | ||||||
| import com.intellij.openapi.util.UserDataHolder | import com.intellij.openapi.util.UserDataHolder | ||||||
| import com.maddyhome.idea.vim.api.CaretRegisterStorageBase |  | ||||||
| import com.maddyhome.idea.vim.api.LocalMarkStorage | import com.maddyhome.idea.vim.api.LocalMarkStorage | ||||||
| import com.maddyhome.idea.vim.api.SelectionInfo | import com.maddyhome.idea.vim.api.SelectionInfo | ||||||
| import com.maddyhome.idea.vim.common.InsertSequence | import com.maddyhome.idea.vim.common.InsertSequence | ||||||
| @@ -98,7 +97,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr { | |||||||
| } | } | ||||||
|  |  | ||||||
| // TODO: Data could be lost during visual block motion | // TODO: Data could be lost during visual block motion | ||||||
| internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretToEditor() |  | ||||||
| internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor() | internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor() | ||||||
| internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor() | internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,32 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2003-2023 The IdeaVim authors |  | ||||||
|  * |  | ||||||
|  * Use of this source code is governed by an MIT-style |  | ||||||
|  * license that can be found in the LICENSE.txt file or at |  | ||||||
|  * https://opensource.org/licenses/MIT. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package com.maddyhome.idea.vim.helper |  | ||||||
|  |  | ||||||
| import com.intellij.ide.plugins.StandalonePluginUpdateChecker |  | ||||||
| import com.intellij.openapi.components.Service |  | ||||||
| import com.intellij.openapi.components.service |  | ||||||
| import com.maddyhome.idea.vim.VimPlugin |  | ||||||
| import com.maddyhome.idea.vim.group.NotificationService |  | ||||||
| import com.maddyhome.idea.vim.icons.VimIcons |  | ||||||
|  |  | ||||||
| @Service(Service.Level.APP) |  | ||||||
| internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker( |  | ||||||
|   VimPlugin.getPluginId(), |  | ||||||
|   updateTimestampProperty = PROPERTY_NAME, |  | ||||||
|   NotificationService.IDEAVIM_STICKY_GROUP, |  | ||||||
|   VimIcons.IDEAVIM, |  | ||||||
| ) { |  | ||||||
|  |  | ||||||
|   override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion() |  | ||||||
|  |  | ||||||
|   companion object { |  | ||||||
|     private const val PROPERTY_NAME = "ideavim.statistics.timestamp" |  | ||||||
|     fun getInstance(): VimStandalonePluginUpdateChecker = service() |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -16,7 +16,9 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction | |||||||
| import com.intellij.codeInsight.template.Template | import com.intellij.codeInsight.template.Template | ||||||
| import com.intellij.codeInsight.template.TemplateEditingAdapter | import com.intellij.codeInsight.template.TemplateEditingAdapter | ||||||
| import com.intellij.codeInsight.template.TemplateManagerListener | import com.intellij.codeInsight.template.TemplateManagerListener | ||||||
|  | import com.intellij.codeInsight.template.impl.TemplateManagerImpl | ||||||
| import com.intellij.codeInsight.template.impl.TemplateState | import com.intellij.codeInsight.template.impl.TemplateState | ||||||
|  | import com.intellij.codeInsight.template.impl.actions.NextVariableAction | ||||||
| import com.intellij.find.FindModelListener | import com.intellij.find.FindModelListener | ||||||
| import com.intellij.openapi.actionSystem.ActionManager | import com.intellij.openapi.actionSystem.ActionManager | ||||||
| import com.intellij.openapi.actionSystem.ActionUpdateThread | import com.intellij.openapi.actionSystem.ActionUpdateThread | ||||||
| @@ -29,6 +31,7 @@ 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.editor.actions.EnterAction | import com.intellij.openapi.editor.actions.EnterAction | ||||||
|  | import com.intellij.openapi.editor.impl.ScrollingModelImpl | ||||||
| import com.intellij.openapi.keymap.KeymapManager | import com.intellij.openapi.keymap.KeymapManager | ||||||
| import com.intellij.openapi.project.DumbAwareToggleAction | import com.intellij.openapi.project.DumbAwareToggleAction | ||||||
| import com.intellij.openapi.util.TextRange | import com.intellij.openapi.util.TextRange | ||||||
| @@ -60,6 +63,7 @@ internal 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 caretOffset = -1 | ||||||
|     private var completionPrevDocumentLength: Int? = null |     private var completionPrevDocumentLength: Int? = null | ||||||
|     private var completionPrevDocumentOffset: Int? = null |     private var completionPrevDocumentOffset: Int? = null | ||||||
|  |  | ||||||
| @@ -69,6 +73,7 @@ internal object IdeaSpecifics { | |||||||
|       val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR) |       val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR) | ||||||
|       if (hostEditor != null) { |       if (hostEditor != null) { | ||||||
|         editor = hostEditor |         editor = hostEditor | ||||||
|  |         caretOffset = hostEditor.caretModel.offset | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction |       val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction | ||||||
| @@ -122,17 +127,18 @@ internal object IdeaSpecifics { | |||||||
|       if (VimPlugin.isNotEnabled()) return |       if (VimPlugin.isNotEnabled()) return | ||||||
|  |  | ||||||
|       val editor = editor |       val editor = editor | ||||||
|       if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) { |       if (editor != null) { | ||||||
|         val prevDocumentLength = completionPrevDocumentLength |         if (action is ChooseItemAction && injector.registerGroup.isRecording) { | ||||||
|         val prevDocumentOffset = completionPrevDocumentOffset |           val prevDocumentLength = completionPrevDocumentLength | ||||||
|  |           val prevDocumentOffset = completionPrevDocumentOffset | ||||||
|  |  | ||||||
|         if (prevDocumentLength != null && prevDocumentOffset != null) { |           if (prevDocumentLength != null && prevDocumentOffset != null) { | ||||||
|           val register = VimPlugin.getRegister() |             val register = VimPlugin.getRegister() | ||||||
|           val addedTextLength = editor.document.textLength - prevDocumentLength |             val addedTextLength = editor.document.textLength - prevDocumentLength | ||||||
|           val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset) |             val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset) | ||||||
|           val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0) |             val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0) | ||||||
|  |  | ||||||
|           register.recordText( |             register.recordText( | ||||||
|             editor.document.getText( |             editor.document.getText( | ||||||
|               TextRange( |               TextRange( | ||||||
|                 prevDocumentOffset, |                 prevDocumentOffset, | ||||||
| @@ -140,31 +146,49 @@ internal object IdeaSpecifics { | |||||||
|               ) |               ) | ||||||
|             ) |             ) | ||||||
|           ) |           ) | ||||||
|           repeat(caretShift.coerceAtLeast(0)) { |             repeat(caretShift.coerceAtLeast(0)) { | ||||||
|             register.recordKeyStroke(leftArrow) |               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( | ||||||
|  |               it, | ||||||
|  |             ) | ||||||
|  |           } | ||||||
|  |         ) { | ||||||
|  |           editor?.let { | ||||||
|  |             it.vim.mode = Mode.NORMAL() | ||||||
|  |             VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim) | ||||||
|  |             KeyHandler.getInstance().reset(it.vim) | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  |         else if (action is NextVariableAction && TemplateManagerImpl.getTemplateState(editor) == null) { | ||||||
|         this.completionPrevDocumentLength = null |           editor.vim.exitInsertMode(event.dataContext.vim) | ||||||
|         this.completionPrevDocumentOffset = null |           KeyHandler.getInstance().reset(editor.vim) | ||||||
|       } |  | ||||||
|  |  | ||||||
|       //region Enter insert mode after surround with if |  | ||||||
|       if (surrounderAction == action.javaClass.name && surrounderItems.any { |  | ||||||
|           action.templatePresentation.text.endsWith( |  | ||||||
|             it, |  | ||||||
|           ) |  | ||||||
|         } |         } | ||||||
|       ) { |         //endregion | ||||||
|         editor?.let { |  | ||||||
|           it.vim.mode = Mode.NORMAL() |         if (caretOffset != -1 && caretOffset != editor.caretModel.offset) { | ||||||
|           VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim) |           val scrollModel = editor.scrollingModel as ScrollingModelImpl | ||||||
|           KeyHandler.getInstance().reset(it.vim) |           if (scrollModel.isScrollingNow) { | ||||||
|  |             val v = scrollModel.verticalScrollOffset | ||||||
|  |             val h = scrollModel.horizontalScrollOffset | ||||||
|  |             scrollModel.finishAnimation() | ||||||
|  |             scrollModel.scroll(h, v) | ||||||
|  |             scrollModel.finishAnimation() | ||||||
|  |           } | ||||||
|  |           injector.scroll.scrollCaretIntoView(editor.vim) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       //endregion |  | ||||||
|  |  | ||||||
|       this.editor = null |       this.editor = null | ||||||
|  |       this.caretOffset = -1 | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -81,7 +81,6 @@ import com.maddyhome.idea.vim.handler.keyCheckRequests | |||||||
| import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener | import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener | ||||||
| import com.maddyhome.idea.vim.helper.GuicursorChangeListener | import com.maddyhome.idea.vim.helper.GuicursorChangeListener | ||||||
| import com.maddyhome.idea.vim.helper.StrictMode | import com.maddyhome.idea.vim.helper.StrictMode | ||||||
| import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker |  | ||||||
| import com.maddyhome.idea.vim.helper.exitSelectMode | import com.maddyhome.idea.vim.helper.exitSelectMode | ||||||
| import com.maddyhome.idea.vim.helper.exitVisualMode | import com.maddyhome.idea.vim.helper.exitVisualMode | ||||||
| import com.maddyhome.idea.vim.helper.forceBarCursor | import com.maddyhome.idea.vim.helper.forceBarCursor | ||||||
| @@ -98,6 +97,7 @@ import com.maddyhome.idea.vim.newapi.IjVimSearchGroup | |||||||
| import com.maddyhome.idea.vim.newapi.InsertTimeRecorder | import com.maddyhome.idea.vim.newapi.InsertTimeRecorder | ||||||
| import com.maddyhome.idea.vim.newapi.ij | import com.maddyhome.idea.vim.newapi.ij | ||||||
| import com.maddyhome.idea.vim.newapi.vim | import com.maddyhome.idea.vim.newapi.vim | ||||||
|  | import com.maddyhome.idea.vim.state.mode.Mode | ||||||
| import com.maddyhome.idea.vim.state.mode.inSelectMode | import com.maddyhome.idea.vim.state.mode.inSelectMode | ||||||
| import com.maddyhome.idea.vim.state.mode.selectionType | import com.maddyhome.idea.vim.state.mode.selectionType | ||||||
| import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener | import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener | ||||||
| @@ -412,9 +412,20 @@ internal object VimListenerManager { | |||||||
|       // We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly |       // We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly | ||||||
|       if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return |       if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return | ||||||
|        |        | ||||||
|  |       val newEditor = event.newEditor | ||||||
|  |       if (newEditor is TextEditor) { | ||||||
|  |         val editor = newEditor.editor | ||||||
|  |         if (editor.isInsertMode) { | ||||||
|  |           editor.vim.mode = Mode.NORMAL() | ||||||
|  |           KeyHandler.getInstance().reset(editor.vim) | ||||||
|  |         } | ||||||
|  |         // Breaks relativenumber for some reason | ||||||
|  | //        injector.scroll.scrollCaretIntoView(editor.vim) | ||||||
|  |       } | ||||||
|  |        | ||||||
|       MotionGroup.fileEditorManagerSelectionChangedCallback(event) |       MotionGroup.fileEditorManagerSelectionChangedCallback(event) | ||||||
|       FileGroup.fileEditorManagerSelectionChangedCallback(event) |       FileGroup.fileEditorManagerSelectionChangedCallback(event) | ||||||
|       VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event) | //      VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event) | ||||||
|       IjVimRedrawService.fileEditorManagerSelectionChangedCallback(event) |       IjVimRedrawService.fileEditorManagerSelectionChangedCallback(event) | ||||||
|       VimLastSelectedEditorTracker.setLastSelectedEditor(event.newEditor) |       VimLastSelectedEditorTracker.setLastSelectedEditor(event.newEditor) | ||||||
|     } |     } | ||||||
| @@ -485,8 +496,6 @@ internal object VimListenerManager { | |||||||
|           OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused) |           OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused) | ||||||
|         ) |         ) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       VimStandalonePluginUpdateChecker.getInstance().pluginUsed() |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun editorReleased(event: EditorFactoryEvent) { |     override fun editorReleased(event: EditorFactoryEvent) { | ||||||
|   | |||||||
| @@ -12,8 +12,6 @@ import com.intellij.openapi.editor.Caret | |||||||
| import com.intellij.openapi.editor.LogicalPosition | import com.intellij.openapi.editor.LogicalPosition | ||||||
| import com.intellij.openapi.editor.VisualPosition | import com.intellij.openapi.editor.VisualPosition | ||||||
| import com.maddyhome.idea.vim.api.BufferPosition | import com.maddyhome.idea.vim.api.BufferPosition | ||||||
| import com.maddyhome.idea.vim.api.CaretRegisterStorage |  | ||||||
| import com.maddyhome.idea.vim.api.CaretRegisterStorageBase |  | ||||||
| import com.maddyhome.idea.vim.api.ImmutableVimCaret | import com.maddyhome.idea.vim.api.ImmutableVimCaret | ||||||
| import com.maddyhome.idea.vim.api.LocalMarkStorage | import com.maddyhome.idea.vim.api.LocalMarkStorage | ||||||
| import com.maddyhome.idea.vim.api.SelectionInfo | import com.maddyhome.idea.vim.api.SelectionInfo | ||||||
| @@ -21,6 +19,7 @@ import com.maddyhome.idea.vim.api.VimCaret | |||||||
| import com.maddyhome.idea.vim.api.VimCaretBase | import com.maddyhome.idea.vim.api.VimCaretBase | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.VimVisualPosition | import com.maddyhome.idea.vim.api.VimVisualPosition | ||||||
|  | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.common.InsertSequence | import com.maddyhome.idea.vim.common.InsertSequence | ||||||
| import com.maddyhome.idea.vim.common.LiveRange | import com.maddyhome.idea.vim.common.LiveRange | ||||||
| import com.maddyhome.idea.vim.group.visual.VisualChange | import com.maddyhome.idea.vim.group.visual.VisualChange | ||||||
| @@ -29,7 +28,6 @@ import com.maddyhome.idea.vim.helper.insertHistory | |||||||
| import com.maddyhome.idea.vim.helper.lastSelectionInfo | import com.maddyhome.idea.vim.helper.lastSelectionInfo | ||||||
| import com.maddyhome.idea.vim.helper.markStorage | import com.maddyhome.idea.vim.helper.markStorage | ||||||
| import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset | import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset | ||||||
| import com.maddyhome.idea.vim.helper.registerStorage |  | ||||||
| import com.maddyhome.idea.vim.helper.resetVimLastColumn | import com.maddyhome.idea.vim.helper.resetVimLastColumn | ||||||
| import com.maddyhome.idea.vim.helper.vimInsertStart | import com.maddyhome.idea.vim.helper.vimInsertStart | ||||||
| import com.maddyhome.idea.vim.helper.vimLastColumn | import com.maddyhome.idea.vim.helper.vimLastColumn | ||||||
| @@ -37,22 +35,14 @@ import com.maddyhome.idea.vim.helper.vimLastVisualOperatorRange | |||||||
| import com.maddyhome.idea.vim.helper.vimLine | import com.maddyhome.idea.vim.helper.vimLine | ||||||
| import com.maddyhome.idea.vim.helper.vimSelectionStart | import com.maddyhome.idea.vim.helper.vimSelectionStart | ||||||
| import com.maddyhome.idea.vim.helper.vimSelectionStartClear | import com.maddyhome.idea.vim.helper.vimSelectionStartClear | ||||||
|  | import com.maddyhome.idea.vim.register.VimRegisterGroup | ||||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | import com.maddyhome.idea.vim.state.mode.SelectionType | ||||||
|  |  | ||||||
| internal class IjVimCaret(val caret: Caret) : VimCaretBase() { | internal class IjVimCaret(val caret: Caret) : VimCaretBase() { | ||||||
|  |  | ||||||
|   override val registerStorage: CaretRegisterStorage |   override val registerStorage: VimRegisterGroup | ||||||
|     get() { |     get() = injector.registerGroup | ||||||
|       var storage = this.caret.registerStorage |   | ||||||
|       if (storage == null) { |  | ||||||
|         initInjector() // To initialize injector used in CaretRegisterStorageBase |  | ||||||
|         storage = CaretRegisterStorageBase(this) |  | ||||||
|         this.caret.registerStorage = storage |  | ||||||
|       } else if (storage.caret != this) { |  | ||||||
|         storage.caret = this |  | ||||||
|       } |  | ||||||
|       return storage |  | ||||||
|     } |  | ||||||
|   override val markStorage: LocalMarkStorage |   override val markStorage: LocalMarkStorage | ||||||
|     get() { |     get() { | ||||||
|       var storage = this.caret.markStorage |       var storage = this.caret.markStorage | ||||||
|   | |||||||
| @@ -179,21 +179,38 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase( | |||||||
|     return editor.caretModel.allCarets.map { IjVimCaret(it) } |     return editor.caretModel.allCarets.map { IjVimCaret(it) } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   override var isFirstCaret = true | ||||||
|  |   override var isReversingCarets = false | ||||||
|  |    | ||||||
|   @Suppress("ideavimRunForEachCaret") |   @Suppress("ideavimRunForEachCaret") | ||||||
|   override fun forEachCaret(action: (VimCaret) -> Unit) { |   override fun forEachCaret(action: (VimCaret) -> Unit) { | ||||||
|     if (editor.vim.inBlockSelection) { |     if (editor.vim.inBlockSelection) { | ||||||
|       action(IjVimCaret(editor.caretModel.primaryCaret)) |       action(IjVimCaret(editor.caretModel.primaryCaret)) | ||||||
|     } else { |     } else { | ||||||
|       editor.caretModel.runForEachCaret({ |       try { | ||||||
|         if (it.isValid) { |         editor.caretModel.runForEachCaret({ | ||||||
|           action(IjVimCaret(it)) |           if (it.isValid) { | ||||||
|         } |             action(IjVimCaret(it)) | ||||||
|       }, false) |             isFirstCaret = false | ||||||
|  |           } | ||||||
|  |         }, false) | ||||||
|  |       } finally { | ||||||
|  |         isFirstCaret = true | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) { |   override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) { | ||||||
|     editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse) |     isReversingCarets = reverse | ||||||
|  |     try { | ||||||
|  |       editor.caretModel.runForEachCaret({ | ||||||
|  |         action(IjVimCaret(it)) | ||||||
|  |         isFirstCaret = false | ||||||
|  |       }, reverse) | ||||||
|  |     } finally { | ||||||
|  |       isFirstCaret = true | ||||||
|  |       isReversingCarets = false | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun isInForEachCaretScope(): Boolean { |   override fun isInForEachCaretScope(): Boolean { | ||||||
|   | |||||||
| @@ -353,7 +353,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine { | |||||||
|         int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder() |         int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder() | ||||||
|           .calculateCount0Snapshot()); |           .calculateCount0Snapshot()); | ||||||
|  |  | ||||||
|         if (labelText.equals("/") || labelText.equals("?") || searchCommand) { |         if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) { | ||||||
|           final boolean forwards = !labelText.equals("?");  // :s, :g, :v are treated as forwards |           final boolean forwards = !labelText.equals("?");  // :s, :g, :v are treated as forwards | ||||||
|           int patternEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0); |           int patternEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0); | ||||||
|           final String pattern = searchText.substring(0, patternEnd); |           final String pattern = searchText.substring(0, patternEnd); | ||||||
|   | |||||||
| @@ -1,12 +1,4 @@ | |||||||
| <!-- | <idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude"> | ||||||
|   ~ Copyright 2003-2023 The IdeaVim authors |  | ||||||
|   ~ |  | ||||||
|   ~ Use of this source code is governed by an MIT-style |  | ||||||
|   ~ license that can be found in the LICENSE.txt file or at |  | ||||||
|   ~ https://opensource.org/licenses/MIT. |  | ||||||
|   --> |  | ||||||
|  |  | ||||||
| <idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude"> |  | ||||||
|   <name>IdeaVim</name> |   <name>IdeaVim</name> | ||||||
|   <id>IdeaVIM</id> |   <id>IdeaVIM</id> | ||||||
|   <description><![CDATA[ |   <description><![CDATA[ | ||||||
| @@ -21,7 +13,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> | ||||||
|  |  | ||||||
|   <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) --> |   <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) --> | ||||||
| @@ -147,9 +139,11 @@ | |||||||
|   <xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/> |   <xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/> | ||||||
|  |  | ||||||
|   <actions resource-bundle="messages.IdeaVimBundle"> |   <actions resource-bundle="messages.IdeaVimBundle"> | ||||||
|     <action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"> |     <group id="com.chylex.intellij.vim" text="Vim" popup="true"> | ||||||
|       <add-to-group group-id="ToolsMenu" anchor="last"/> |       <add-to-group group-id="ToolsMenu" anchor="last"/> | ||||||
|     </action> |       <action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/> | ||||||
|  |       <action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/> | ||||||
|  |     </group> | ||||||
|      |      | ||||||
|     <!-- Internal --> |     <!-- Internal --> | ||||||
|     <!--suppress PluginXmlI18n --> |     <!--suppress PluginXmlI18n --> | ||||||
| @@ -168,5 +162,6 @@ | |||||||
|     </group> |     </group> | ||||||
|  |  | ||||||
|     <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/> |     <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/> | ||||||
|  |     <action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" /> | ||||||
|   </actions> |   </actions> | ||||||
| </idea-plugin> | </idea-plugin> | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
| package org.jetbrains.plugins.ideavim.ex.implementation.variables | package org.jetbrains.plugins.ideavim.ex.implementation.variables | ||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
|  | import com.maddyhome.idea.vim.newapi.vim | ||||||
| import org.jetbrains.plugins.ideavim.VimTestCase | import org.jetbrains.plugins.ideavim.VimTestCase | ||||||
| import org.junit.jupiter.api.Test | import org.junit.jupiter.api.Test | ||||||
| import kotlin.test.assertEquals | import kotlin.test.assertEquals | ||||||
| @@ -43,7 +44,9 @@ class RegisterVariableTest : VimTestCase() { | |||||||
|     configureByText("abcd") |     configureByText("abcd") | ||||||
|     enterCommand("""vnoremap <expr> y '"' . v:register . 'y'""") |     enterCommand("""vnoremap <expr> y '"' . v:register . 'y'""") | ||||||
|     typeText("vl\"zy") |     typeText("vl\"zy") | ||||||
|     val register = injector.registerGroup.getRegisters() |     val vimEditor = fixture.editor.vim | ||||||
|  |     val context = injector.executionContextManager.getEditorExecutionContext(vimEditor) | ||||||
|  |     val register = injector.registerGroup.getRegisters(vimEditor, context) | ||||||
|       .filter { reg -> reg.name == 'z' } |       .filter { reg -> reg.name == 'z' } | ||||||
|       .first() |       .first() | ||||||
|     assertEquals("ab", register.text) |     assertEquals("ab", register.text) | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command | |||||||
| import com.maddyhome.idea.vim.command.OperatorArguments | import com.maddyhome.idea.vim.command.OperatorArguments | ||||||
| import com.maddyhome.idea.vim.handler.VimActionHandler | import com.maddyhome.idea.vim.handler.VimActionHandler | ||||||
|  |  | ||||||
| @CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL]) | @CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL]) | ||||||
| class RedoAction : VimActionHandler.SingleExecution() { | class RedoAction : VimActionHandler.SingleExecution() { | ||||||
|   override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED |   override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command | |||||||
| import com.maddyhome.idea.vim.command.OperatorArguments | import com.maddyhome.idea.vim.command.OperatorArguments | ||||||
| import com.maddyhome.idea.vim.handler.VimActionHandler | import com.maddyhome.idea.vim.handler.VimActionHandler | ||||||
|  |  | ||||||
| @CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL]) | @CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL]) | ||||||
| class UndoAction : VimActionHandler.SingleExecution() { | class UndoAction : VimActionHandler.SingleExecution() { | ||||||
|   override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED |   override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ | |||||||
| package com.maddyhome.idea.vim.action.change.change | package com.maddyhome.idea.vim.action.change.change | ||||||
|  |  | ||||||
| import com.intellij.vim.annotations.CommandOrMotion | import com.intellij.vim.annotations.CommandOrMotion | ||||||
| import com.intellij.vim.annotations.Mode |  | ||||||
| import com.maddyhome.idea.vim.api.ExecutionContext | import com.maddyhome.idea.vim.api.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.api.VimChangeGroup | import com.maddyhome.idea.vim.api.VimChangeGroup | ||||||
| @@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler | |||||||
| /** | /** | ||||||
|  * @author vlan |  * @author vlan | ||||||
|  */ |  */ | ||||||
| @CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL]) | @CommandOrMotion(keys = [], modes = []) | ||||||
| class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() { | class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() { | ||||||
|   override val type: Command.Type = Command.Type.CHANGE |   override val type: Command.Type = Command.Type.CHANGE | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ | |||||||
| package com.maddyhome.idea.vim.action.change.change | package com.maddyhome.idea.vim.action.change.change | ||||||
|  |  | ||||||
| import com.intellij.vim.annotations.CommandOrMotion | import com.intellij.vim.annotations.CommandOrMotion | ||||||
| import com.intellij.vim.annotations.Mode |  | ||||||
| import com.maddyhome.idea.vim.api.ExecutionContext | import com.maddyhome.idea.vim.api.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.api.VimChangeGroup | import com.maddyhome.idea.vim.api.VimChangeGroup | ||||||
| @@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler | |||||||
| /** | /** | ||||||
|  * @author vlan |  * @author vlan | ||||||
|  */ |  */ | ||||||
| @CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL]) | @CommandOrMotion(keys = [], modes = []) | ||||||
| class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() { | class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() { | ||||||
|   override val type: Command.Type = Command.Type.CHANGE |   override val type: Command.Type = Command.Type.CHANGE | ||||||
|  |  | ||||||
|   | |||||||
| @@ -69,15 +69,10 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() { | |||||||
|  */ |  */ | ||||||
| @RWLockLabel.SelfSynchronized | @RWLockLabel.SelfSynchronized | ||||||
| private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Char): Boolean { | private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Char): Boolean { | ||||||
|   val register: Register? = injector.registerGroup.getRegister(editor, context, key) |   val register: Register? = injector.registerGroup.getRegister(key) | ||||||
|   if (register != null) { |   if (register != null) { | ||||||
|     val textData = PutData.TextData( |     val textData = PutData.TextData(register.text, SelectionType.CHARACTER_WISE, emptyList(), register.name) | ||||||
|       register.name, |     val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true) | ||||||
|       injector.clipboardManager.dumbCopiedText(register.text), |  | ||||||
|       SelectionType.CHARACTER_WISE |  | ||||||
|     ) |  | ||||||
|     val putData = |  | ||||||
|       PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true) |  | ||||||
|     injector.put.putText(editor, context, putData) |     injector.put.putText(editor, context, putData) | ||||||
|     return true |     return true | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.action.copy | |||||||
| import com.intellij.vim.annotations.CommandOrMotion | import com.intellij.vim.annotations.CommandOrMotion | ||||||
| import com.intellij.vim.annotations.Mode | import com.intellij.vim.annotations.Mode | ||||||
| import com.maddyhome.idea.vim.api.ExecutionContext | import com.maddyhome.idea.vim.api.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.ImmutableVimCaret |  | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.command.Argument | import com.maddyhome.idea.vim.command.Argument | ||||||
| @@ -36,33 +35,40 @@ sealed class PutTextBaseAction( | |||||||
|     val count = operatorArguments.count1 |     val count = operatorArguments.count1 | ||||||
|     val sortedCarets = editor.sortedCarets() |     val sortedCarets = editor.sortedCarets() | ||||||
|     return if (sortedCarets.size > 1) { |     return if (sortedCarets.size > 1) { | ||||||
|       val caretToPutData = sortedCarets.associateWith { getPutDataForCaret(editor, context, it, count) } |       val putData = getPutData(count) | ||||||
|  |  | ||||||
|  |       val splitText = putData.textData?.rawText?.split('\n')?.dropLastWhile(String::isEmpty) | ||||||
|  |       val caretToPutData = if (splitText != null && splitText.size == sortedCarets.size) { | ||||||
|  |         sortedCarets.mapIndexed { index, caret -> caret to putData.copy(textData = putData.textData.copy(rawText = splitText[splitText.lastIndex - index])) }.toMap() | ||||||
|  |       } else { | ||||||
|  |         sortedCarets.associateWith { putData } | ||||||
|  |       } | ||||||
|  |        | ||||||
|       var result = true |       var result = true | ||||||
|       caretToPutData.forEach { |       caretToPutData.forEach { | ||||||
|         result = injector.put.putTextForCaret(editor, it.key, context, it.value) && result |         result = injector.put.putTextForCaret(editor, it.key, context, it.value) && result | ||||||
|       } |       } | ||||||
|       result |       result | ||||||
|     } else { |     } else { | ||||||
|       val putData = getPutDataForCaret(editor, context, sortedCarets.single(), count) |       injector.put.putText(editor, context, getPutData(count)) | ||||||
|       injector.put.putText(editor, context, putData) |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private fun getPutDataForCaret( |   private fun getPutData(count: Int, | ||||||
|     editor: VimEditor, |  | ||||||
|     context: ExecutionContext, |  | ||||||
|     caret: ImmutableVimCaret, |  | ||||||
|     count: Int, |  | ||||||
|   ): PutData { |   ): PutData { | ||||||
|     val registerService = injector.registerGroup |     return PutData(getRegisterTextData(), null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1) | ||||||
|     val registerChar = if (caret.editor.carets().size == 1) { |   } | ||||||
|       registerService.currentRegister | } | ||||||
|     } else { |  | ||||||
|       registerService.getCurrentRegisterForMulticaret() | fun getRegisterTextData(): TextData? { | ||||||
|     } |   val register = injector.registerGroup.getRegister(injector.registerGroup.currentRegister) | ||||||
|     val register = caret.registerStorage.getRegister(editor, context, registerChar) |   return register?.let { | ||||||
|     val textData = register?.let { TextData(register) } |     TextData( | ||||||
|     return PutData(textData, null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1) |       register.text ?: injector.parser.toPrintableString(register.keys), | ||||||
|  |       register.type, | ||||||
|  |       register.transferableData, | ||||||
|  |       register.name, | ||||||
|  |     ) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,8 +41,22 @@ sealed class PutVisualTextBaseAction( | |||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     if (caretsAndSelections.isEmpty()) return false |     if (caretsAndSelections.isEmpty()) return false | ||||||
|     val count = cmd.count |     val count = cmd.count | ||||||
|     val caretToPutData = |     val sortedCarets = | ||||||
|       editor.sortedCarets().associateWith { getPutDataForCaret(editor, context, it, caretsAndSelections[it], count) } |       editor.sortedCarets() | ||||||
|  |  | ||||||
|  |     val textData = getRegisterTextData() | ||||||
|  |     val splitText = textData?.rawText?.split('\n')?.dropLastWhile(String::isEmpty) | ||||||
|  |  | ||||||
|  |     val caretToTextData = if (splitText != null && splitText.size == sortedCarets.size) { | ||||||
|  |       sortedCarets.mapIndexed { index, caret -> caret to textData.copy(rawText = splitText[splitText.lastIndex - index]) }.toMap() | ||||||
|  |     } else { | ||||||
|  |       sortedCarets.associateWith { textData } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     val caretToPutData = caretToTextData.mapValues { (caret, textData) -> | ||||||
|  |       getPutDataForCaret(textData, caret, caretsAndSelections[caret], count) | ||||||
|  |     } | ||||||
|  |      | ||||||
|     injector.registerGroup.resetRegister() |     injector.registerGroup.resetRegister() | ||||||
|     var result = true |     var result = true | ||||||
|     caretToPutData.forEach { |     caretToPutData.forEach { | ||||||
| @@ -51,16 +65,10 @@ sealed class PutVisualTextBaseAction( | |||||||
|     return result |     return result | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   private fun getPutDataForCaret( |   private fun getPutDataForCaret(textData: PutData.TextData?, | ||||||
|     editor: VimEditor, |  | ||||||
|     context: ExecutionContext, |  | ||||||
|     caret: VimCaret, |     caret: VimCaret, | ||||||
|     selection: VimSelection?, |     selection: VimSelection?, | ||||||
|     count: Int, |     count: Int,): PutData { | ||||||
|   ): PutData { |  | ||||||
|     val lastRegisterChar = injector.registerGroup.lastRegisterChar |  | ||||||
|     val register = caret.registerStorage.getRegister(editor, context, lastRegisterChar) |  | ||||||
|     val textData = register?.let { PutData.TextData(register) } |  | ||||||
|     val visualSelection = selection?.let { PutData.VisualSelection(mapOf(caret to it), it.type) } |     val visualSelection = selection?.let { PutData.VisualSelection(mapOf(caret to it), it.type) } | ||||||
|     return PutData(textData, visualSelection, count, insertTextBeforeCaret, indent, caretAfterInsertedText) |     return PutData(textData, visualSelection, count, insertTextBeforeCaret, indent, caretAfterInsertedText) | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -88,8 +88,7 @@ class ProcessSearchEntryAction(private val parentAction: ProcessExEntryAction) : | |||||||
|  |  | ||||||
|       else -> throw ExException("Unexpected search label ${argument.label}") |       else -> throw ExException("Unexpected search label ${argument.label}") | ||||||
|     } |     } | ||||||
|     // Vim doesn't treat not finding something as an error, although it might report either an error or warning message |     if (offsetAndMotion == null) return Motion.Error | ||||||
|     if (offsetAndMotion == null) return Motion.NoMotion |  | ||||||
|     parentAction.motionType = offsetAndMotion.second |     parentAction.motionType = offsetAndMotion.second | ||||||
|     return offsetAndMotion.first.toMotionOrError() |     return offsetAndMotion.first.toMotionOrError() | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -76,6 +76,13 @@ sealed class TillCharacterMotion( | |||||||
|       ) |       ) | ||||||
|     } |     } | ||||||
|     injector.motion.setLastFTCmd(tillCharacterMotionType, argument.character) |     injector.motion.setLastFTCmd(tillCharacterMotionType, argument.character) | ||||||
|  |      | ||||||
|  |     val offset = if (!finishBeforeCharacter) "" | ||||||
|  |     else if (direction == Direction.FORWARDS) "s-1" | ||||||
|  |     else "s+1" | ||||||
|  |      | ||||||
|  |     injector.searchGroup.setLastSearchState(argument.character.let { if (it == '.') "\\." else it.toString() }, offset, direction) | ||||||
|  |      | ||||||
|     return res.toMotionOrError() |     return res.toMotionOrError() | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,6 @@ | |||||||
| package com.maddyhome.idea.vim.api | package com.maddyhome.idea.vim.api | ||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.common.LiveRange | import com.maddyhome.idea.vim.common.LiveRange | ||||||
| import com.maddyhome.idea.vim.common.TextRange |  | ||||||
| import com.maddyhome.idea.vim.group.visual.VisualChange | import com.maddyhome.idea.vim.group.visual.VisualChange | ||||||
| import com.maddyhome.idea.vim.group.visual.vimMoveBlockSelectionToOffset | import com.maddyhome.idea.vim.group.visual.vimMoveBlockSelectionToOffset | ||||||
| import com.maddyhome.idea.vim.group.visual.vimMoveSelectionToCaret | import com.maddyhome.idea.vim.group.visual.vimMoveSelectionToCaret | ||||||
| @@ -17,13 +16,11 @@ import com.maddyhome.idea.vim.handler.Motion | |||||||
| import com.maddyhome.idea.vim.helper.RWLockLabel | import com.maddyhome.idea.vim.helper.RWLockLabel | ||||||
| import com.maddyhome.idea.vim.helper.StrictMode | import com.maddyhome.idea.vim.helper.StrictMode | ||||||
| import com.maddyhome.idea.vim.helper.exitVisualMode | import com.maddyhome.idea.vim.helper.exitVisualMode | ||||||
| import com.maddyhome.idea.vim.register.Register | import com.maddyhome.idea.vim.register.VimRegisterGroup | ||||||
| import com.maddyhome.idea.vim.state.mode.SelectionType |  | ||||||
| import com.maddyhome.idea.vim.state.mode.inBlockSelection | import com.maddyhome.idea.vim.state.mode.inBlockSelection | ||||||
| import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual | import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual | ||||||
| import com.maddyhome.idea.vim.state.mode.inSelectMode | import com.maddyhome.idea.vim.state.mode.inSelectMode | ||||||
| import com.maddyhome.idea.vim.state.mode.inVisualMode | import com.maddyhome.idea.vim.state.mode.inVisualMode | ||||||
| import javax.swing.KeyStroke |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Immutable interface of the caret. Immutable caret is an important concept of Fleet. |  * Immutable interface of the caret. Immutable caret is an important concept of Fleet. | ||||||
| @@ -65,7 +62,7 @@ interface ImmutableVimCaret { | |||||||
|   fun hasSelection(): Boolean |   fun hasSelection(): Boolean | ||||||
|  |  | ||||||
|   var lastSelectionInfo: SelectionInfo |   var lastSelectionInfo: SelectionInfo | ||||||
|   val registerStorage: CaretRegisterStorage |   val registerStorage: VimRegisterGroup | ||||||
|   val markStorage: LocalMarkStorage |   val markStorage: LocalMarkStorage | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -149,36 +146,3 @@ fun VimCaret.moveToMotion(motion: Motion): VimCaret { | |||||||
|     this |     this | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| interface CaretRegisterStorage { |  | ||||||
|   val caret: ImmutableVimCaret |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Stores text to caret's recordable (named/numbered/unnamed) register |  | ||||||
|    */ |  | ||||||
|   @Deprecated("Please use the same method, but with ExecutionContext") |  | ||||||
|   fun storeText(editor: VimEditor, range: TextRange, type: SelectionType, isDelete: Boolean): Boolean |  | ||||||
|   fun storeText( |  | ||||||
|     editor: VimEditor, |  | ||||||
|     context: ExecutionContext, |  | ||||||
|     range: TextRange, |  | ||||||
|     type: SelectionType, |  | ||||||
|     isDelete: Boolean, |  | ||||||
|   ): Boolean |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Gets text from caret's recordable register |  | ||||||
|    * If the register is not recordable - global text state will be returned |  | ||||||
|    */ |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.api.CaretRegisterStorage#getRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)") |  | ||||||
|   fun getRegister(r: Char): Register? |  | ||||||
|   fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? |  | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.api.CaretRegisterStorage#setKeys(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.util.List<? extends javax.swing.KeyStroke>)") |  | ||||||
|   fun setKeys(register: Char, keys: List<KeyStroke>) |  | ||||||
|   fun setKeys(editor: VimEditor, context: ExecutionContext, register: Char, keys: List<KeyStroke>) |  | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.api.CaretRegisterStorage#saveRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, com.maddyhome.idea.vim.register.Register)") |  | ||||||
|   fun saveRegister(r: Char, register: Register) |  | ||||||
|   fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register) |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -8,106 +8,4 @@ | |||||||
|  |  | ||||||
| package com.maddyhome.idea.vim.api | package com.maddyhome.idea.vim.api | ||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.common.TextRange |  | ||||||
| import com.maddyhome.idea.vim.register.Register |  | ||||||
| import com.maddyhome.idea.vim.register.RegisterConstants |  | ||||||
| import com.maddyhome.idea.vim.register.VimRegisterGroupBase |  | ||||||
| import com.maddyhome.idea.vim.state.mode.SelectionType |  | ||||||
| import javax.swing.KeyStroke |  | ||||||
|  |  | ||||||
| abstract class VimCaretBase : VimCaret | abstract class VimCaretBase : VimCaret | ||||||
|  |  | ||||||
| open class CaretRegisterStorageBase(override var caret: ImmutableVimCaret) : CaretRegisterStorage, |  | ||||||
|   VimRegisterGroupBase() { |  | ||||||
|   companion object { |  | ||||||
|     private const val ALLOWED_TO_STORE_REGISTERS = RegisterConstants.RECORDABLE_REGISTERS + |  | ||||||
|       RegisterConstants.SMALL_DELETION_REGISTER + |  | ||||||
|       RegisterConstants.BLACK_HOLE_REGISTER + |  | ||||||
|       RegisterConstants.LAST_INSERTED_TEXT_REGISTER + |  | ||||||
|       RegisterConstants.LAST_SEARCH_REGISTER |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override var lastRegisterChar: Char |  | ||||||
|     get() { |  | ||||||
|       return injector.registerGroup.lastRegisterChar |  | ||||||
|     } |  | ||||||
|     set(_) {} |  | ||||||
|  |  | ||||||
|   override var isRegisterSpecifiedExplicitly: Boolean |  | ||||||
|     get() { |  | ||||||
|       return injector.registerGroup.isRegisterSpecifiedExplicitly |  | ||||||
|     } |  | ||||||
|     set(_) {} |  | ||||||
|  |  | ||||||
|   @Deprecated("Please use the same method, but with ExecutionContext") |  | ||||||
|   override fun storeText(editor: VimEditor, range: TextRange, type: SelectionType, isDelete: Boolean): Boolean { |  | ||||||
|     val context = injector.executionContextManager.getEditorExecutionContext(editor) |  | ||||||
|     return storeText(editor, context, caret, range, type, isDelete) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun storeText( |  | ||||||
|     editor: VimEditor, |  | ||||||
|     context: ExecutionContext, |  | ||||||
|     range: TextRange, |  | ||||||
|     type: SelectionType, |  | ||||||
|     isDelete: Boolean, |  | ||||||
|   ): Boolean { |  | ||||||
|     val registerChar = if (caret.editor.carets().size == 1) currentRegister else getCurrentRegisterForMulticaret() |  | ||||||
|     if (caret.isPrimary) { |  | ||||||
|       val registerService = injector.registerGroup |  | ||||||
|       registerService.lastRegisterChar = registerChar |  | ||||||
|       return registerService.storeText(editor, context, caret, range, type, isDelete) |  | ||||||
|     } else { |  | ||||||
|       if (!ALLOWED_TO_STORE_REGISTERS.contains(registerChar)) { |  | ||||||
|         return false |  | ||||||
|       } |  | ||||||
|       val text = preprocessTextBeforeStoring(editor.getText(range), type) |  | ||||||
|       return storeTextInternal(editor, context, range, text, type, registerChar, isDelete) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun getRegister(r: Char): Register? { |  | ||||||
|     val editorStub = injector.fallbackWindow |  | ||||||
|     val contextStub = injector.executionContextManager.getEditorExecutionContext(editorStub) |  | ||||||
|     return getRegister(editorStub, contextStub, r) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? { |  | ||||||
|     if (caret.isPrimary || !RegisterConstants.RECORDABLE_REGISTERS.contains(r)) { |  | ||||||
|       return injector.registerGroup.getRegister(editor, context, r) |  | ||||||
|     } |  | ||||||
|     return super.getRegister(editor, context, r) ?: injector.registerGroup.getRegister(editor, context, r) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun setKeys(register: Char, keys: List<KeyStroke>) { |  | ||||||
|     val editorStub = injector.fallbackWindow |  | ||||||
|     val contextStub = injector.executionContextManager.getEditorExecutionContext(editorStub) |  | ||||||
|     setKeys(editorStub, contextStub, register, keys) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun setKeys(editor: VimEditor, context: ExecutionContext, register: Char, keys: List<KeyStroke>) { |  | ||||||
|     if (caret.isPrimary) { |  | ||||||
|       injector.registerGroup.setKeys(register, keys) |  | ||||||
|     } |  | ||||||
|     if (!RegisterConstants.RECORDABLE_REGISTERS.contains(register)) { |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     return super.setKeys(register, keys) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun saveRegister(r: Char, register: Register) { |  | ||||||
|     val editorStub = injector.fallbackWindow |  | ||||||
|     val contextStub = injector.executionContextManager.getEditorExecutionContext(editorStub) |  | ||||||
|     saveRegister(editorStub, contextStub, r, register) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register) { |  | ||||||
|     if (caret.isPrimary) { |  | ||||||
|       injector.registerGroup.saveRegister(editor, context, r, register) |  | ||||||
|     } |  | ||||||
|     if (!RegisterConstants.RECORDABLE_REGISTERS.contains(r)) { |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     return super.saveRegister(editor, context, r, register) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -271,7 +271,7 @@ interface VimChangeGroup { | |||||||
|     operatorArguments: OperatorArguments, |     operatorArguments: OperatorArguments, | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret |   fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret | ||||||
|  |  | ||||||
|   fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret |   fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret | ||||||
|  |  | ||||||
|   | |||||||
| @@ -182,31 +182,41 @@ abstract class VimChangeGroupBase : VimChangeGroup { | |||||||
|         return false |         return false | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     val mode = editor.mode | ||||||
|     val isInsertMode = editor.mode == Mode.INSERT || editor.mode == Mode.REPLACE |     if (type == null || | ||||||
|     val shouldYank = type != null && !isInsertMode && saveToRegister |       (mode == Mode.INSERT || mode == Mode.REPLACE) || | ||||||
|     if (shouldYank && !caret.registerStorage.storeText(editor, context, updatedRange, type, isDelete = true)) { |       !saveToRegister || | ||||||
|       return false |       injector.registerGroup.storeText( | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val startOffsets = updatedRange.startOffsets |  | ||||||
|     val endOffsets = updatedRange.endOffsets |  | ||||||
|     for (i in updatedRange.size() - 1 downTo 0) { |  | ||||||
|       val (newRange, _) = editor.search( |  | ||||||
|         startOffsets[i] to endOffsets[i], |  | ||||||
|         editor, |         editor, | ||||||
|         LineDeleteShift.NL_ON_END |         context, | ||||||
|       ) ?: continue |         caret, | ||||||
|       injector.application.runWriteAction { |         updatedRange, | ||||||
|  |         type, | ||||||
|  |         true, | ||||||
|  |         !editor.isFirstCaret, | ||||||
|  |         editor.isReversingCarets | ||||||
|  |       ) | ||||||
|  |     ) { | ||||||
|  |       val startOffsets = updatedRange.startOffsets | ||||||
|  |       val endOffsets = updatedRange.endOffsets | ||||||
|  |       for (i in updatedRange.size() - 1 downTo 0) { | ||||||
|  |         val (newRange, _) = editor.search( | ||||||
|  |           startOffsets[i] to endOffsets[i], | ||||||
|  |           editor, | ||||||
|  |           LineDeleteShift.NL_ON_END | ||||||
|  |         ) ?: continue | ||||||
|  |         injector.application.runWriteAction { | ||||||
|         editor.deleteString(TextRange(newRange.first, newRange.second)) |         editor.deleteString(TextRange(newRange.first, newRange.second)) | ||||||
|       } |       } | ||||||
|  |       } | ||||||
|  |       if (type != null) { | ||||||
|  |         val start = updatedRange.startOffset | ||||||
|  |         injector.markService.setMark(caret, MARK_CHANGE_POS, start) | ||||||
|  |         injector.markService.setChangeMarks(caret, TextRange(start, start + 1)) | ||||||
|  |       } | ||||||
|  |       return true | ||||||
|     } |     } | ||||||
|     if (type != null) { |     return false | ||||||
|       val start = updatedRange.startOffset |  | ||||||
|       injector.markService.setMark(caret, MARK_CHANGE_POS, start) |  | ||||||
|       injector.markService.setChangeMarks(caret, TextRange(start, start + 1)) |  | ||||||
|     } |  | ||||||
|     return true |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -216,7 +226,7 @@ abstract class VimChangeGroupBase : VimChangeGroup { | |||||||
|    * @param caret  The caret to start insertion in |    * @param caret  The caret to start insertion in | ||||||
|    * @param str    The text to insert |    * @param str    The text to insert | ||||||
|    */ |    */ | ||||||
|   override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret { |   override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret { | ||||||
|     injector.application.runWriteAction { |     injector.application.runWriteAction { | ||||||
|       (editor as MutableVimEditor).insertText(caret, offset, str) |       (editor as MutableVimEditor).insertText(caret, offset, str) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -111,7 +111,8 @@ interface VimEditor { | |||||||
|    * This method should perform caret merging after the operations. This is similar to IJ runForEachCaret |    * This method should perform caret merging after the operations. This is similar to IJ runForEachCaret | ||||||
|    * TODO review |    * TODO review | ||||||
|    */ |    */ | ||||||
|  |   val isFirstCaret: Boolean | ||||||
|  |   val isReversingCarets: Boolean | ||||||
|   fun forEachCaret(action: (VimCaret) -> Unit) |   fun forEachCaret(action: (VimCaret) -> Unit) | ||||||
|   fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean = false) |   fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean = false) | ||||||
|   fun isInForEachCaretScope(): Boolean |   fun isInForEachCaretScope(): Boolean | ||||||
|   | |||||||
| @@ -17,8 +17,6 @@ import com.maddyhome.idea.vim.common.VimListenersNotifier | |||||||
| import com.maddyhome.idea.vim.diagnostic.VimLogger | import com.maddyhome.idea.vim.diagnostic.VimLogger | ||||||
| import com.maddyhome.idea.vim.diagnostic.vimLogger | import com.maddyhome.idea.vim.diagnostic.vimLogger | ||||||
| import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl | import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl | ||||||
| import com.maddyhome.idea.vim.register.VimRegisterGroup |  | ||||||
| import com.maddyhome.idea.vim.register.VimRegisterGroupBase |  | ||||||
| import com.maddyhome.idea.vim.state.VimStateMachine | import com.maddyhome.idea.vim.state.VimStateMachine | ||||||
| import com.maddyhome.idea.vim.vimscript.services.VariableService | import com.maddyhome.idea.vim.vimscript.services.VariableService | ||||||
| import com.maddyhome.idea.vim.vimscript.services.VimVariableServiceBase | import com.maddyhome.idea.vim.vimscript.services.VimVariableServiceBase | ||||||
| @@ -28,7 +26,6 @@ import com.maddyhome.idea.vim.yank.YankGroupBase | |||||||
| abstract class VimInjectorBase : VimInjector { | abstract class VimInjectorBase : VimInjector { | ||||||
|   companion object { |   companion object { | ||||||
|     val logger: VimLogger by lazy { vimLogger<VimInjectorBase>() } |     val logger: VimLogger by lazy { vimLogger<VimInjectorBase>() } | ||||||
|     val registerGroupStub: VimRegisterGroupBase by lazy { object : VimRegisterGroupBase() {} } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override val vimState: VimStateMachine = VimStateMachineImpl() |   override val vimState: VimStateMachine = VimStateMachineImpl() | ||||||
| @@ -38,8 +35,6 @@ abstract class VimInjectorBase : VimInjector { | |||||||
|  |  | ||||||
|   override val variableService: VariableService by lazy { object : VimVariableServiceBase() {} } |   override val variableService: VariableService by lazy { object : VimVariableServiceBase() {} } | ||||||
|  |  | ||||||
|   override val registerGroup: VimRegisterGroup by lazy { registerGroupStub } |  | ||||||
|   override val registerGroupIfCreated: VimRegisterGroup? by lazy { registerGroupStub } |  | ||||||
|   override val messages: VimMessages by lazy { VimMessagesStub() } |   override val messages: VimMessages by lazy { VimMessagesStub() } | ||||||
|   override val processGroup: VimProcessGroup by lazy { VimProcessGroupStub() } |   override val processGroup: VimProcessGroup by lazy { VimProcessGroupStub() } | ||||||
|   override val application: VimApplication by lazy { VimApplicationStub() } |   override val application: VimApplication by lazy { VimApplicationStub() } | ||||||
|   | |||||||
| @@ -206,4 +206,17 @@ interface VimSearchGroup { | |||||||
|    * Returns true if any text is selected in the visible editors, false otherwise. |    * Returns true if any text is selected in the visible editors, false otherwise. | ||||||
|    */ |    */ | ||||||
|   fun isSomeTextHighlighted(): Boolean |   fun isSomeTextHighlighted(): Boolean | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Sets the last search state purely for tests | ||||||
|  |    * | ||||||
|  |    * @param pattern         The pattern to save. This is the last search pattern, not the last substitute pattern | ||||||
|  |    * @param patternOffset   The pattern offset, e.g. `/{pattern}/{offset}` | ||||||
|  |    * @param direction       The direction to search | ||||||
|  |    */ | ||||||
|  |   fun setLastSearchState( | ||||||
|  |     pattern: String, | ||||||
|  |     patternOffset: String, | ||||||
|  |     direction: Direction, | ||||||
|  |   ) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1425,8 +1425,7 @@ abstract class VimSearchGroupBase : VimSearchGroup { | |||||||
|    * @param patternOffset   The pattern offset, e.g. `/{pattern}/{offset}` |    * @param patternOffset   The pattern offset, e.g. `/{pattern}/{offset}` | ||||||
|    * @param direction       The direction to search |    * @param direction       The direction to search | ||||||
|    */ |    */ | ||||||
|   @TestOnly |   override fun setLastSearchState( | ||||||
|   fun setLastSearchState( |  | ||||||
|     pattern: String, |     pattern: String, | ||||||
|     patternOffset: String, |     patternOffset: String, | ||||||
|     direction: Direction, |     direction: Direction, | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ | |||||||
|  |  | ||||||
| package com.maddyhome.idea.vim.common | package com.maddyhome.idea.vim.common | ||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.api.ImmutableVimCaret |  | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.state.mode.Mode | import com.maddyhome.idea.vim.state.mode.Mode | ||||||
| @@ -72,9 +71,9 @@ class VimListenersNotifier { | |||||||
|     isReplaceCharListeners.forEach { it.isReplaceCharChanged(editor) } |     isReplaceCharListeners.forEach { it.isReplaceCharChanged(editor) } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   fun notifyYankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) { |   fun notifyYankPerformed(editor: VimEditor, range: TextRange) { | ||||||
|     if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case |     if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case | ||||||
|     yankListeners.forEach { it.yankPerformed(caretToRange) } |     yankListeners.forEach { it.yankPerformed(editor, range) } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   fun reset() { |   fun reset() { | ||||||
|   | |||||||
| @@ -8,8 +8,8 @@ | |||||||
|  |  | ||||||
| package com.maddyhome.idea.vim.common | package com.maddyhome.idea.vim.common | ||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.api.ImmutableVimCaret | import com.maddyhome.idea.vim.api.VimEditor | ||||||
|  |  | ||||||
| interface VimYankListener { | interface VimYankListener { | ||||||
|   fun yankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) |   fun yankPerformed(editor: VimEditor, range: TextRange) | ||||||
| } | } | ||||||
| @@ -260,7 +260,11 @@ class ToActionMappingInfo( | |||||||
|  |  | ||||||
|   override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { |   override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { | ||||||
|     LOG.debug("Executing 'ToAction' mapping...") |     LOG.debug("Executing 'ToAction' mapping...") | ||||||
|     injector.actionExecutor.executeAction(editor, name = action, context = context) |     val commandBuilder = KeyHandler.getInstance().keyHandlerState.commandBuilder | ||||||
|  |     for (i in 0 until commandBuilder.calculateCount0Snapshot().coerceAtLeast(1)) { | ||||||
|  |       injector.actionExecutor.executeAction(editor, name = action, context = context) | ||||||
|  |     } | ||||||
|  |     commandBuilder.resetCount() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   companion object { |   companion object { | ||||||
|   | |||||||
| @@ -8,11 +8,11 @@ | |||||||
|  |  | ||||||
| package com.maddyhome.idea.vim.put | package com.maddyhome.idea.vim.put | ||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.common.VimCopiedText |  | ||||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | import com.maddyhome.idea.vim.state.mode.SelectionType | ||||||
|  |  | ||||||
| data class ProcessedTextData( | data class ProcessedTextData( | ||||||
|   val registerChar: Char?, |   val text: String, | ||||||
|   val copiedText: VimCopiedText, |  | ||||||
|   val typeInRegister: SelectionType, |   val typeInRegister: SelectionType, | ||||||
|  |   val transferableData: List<Any>, | ||||||
|  |   val registerChar: Char?, | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -9,9 +9,7 @@ | |||||||
| package com.maddyhome.idea.vim.put | package com.maddyhome.idea.vim.put | ||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.api.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.common.VimCopiedText |  | ||||||
| import com.maddyhome.idea.vim.group.visual.VimSelection | import com.maddyhome.idea.vim.group.visual.VimSelection | ||||||
| import com.maddyhome.idea.vim.register.Register |  | ||||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | import com.maddyhome.idea.vim.state.mode.SelectionType | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -35,12 +33,9 @@ data class PutData( | |||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   data class TextData( |   data class TextData( | ||||||
|     val registerChar: Char?, |     val rawText: String?, | ||||||
|     val copiedText: VimCopiedText, |  | ||||||
|     val typeInRegister: SelectionType, |     val typeInRegister: SelectionType, | ||||||
|   ) { |     val transferableData: List<Any>, | ||||||
|     constructor(register: Register) : this(register.name, register.copiedText, register.type) |     val registerChar: Char?, | ||||||
|  |   ) | ||||||
|     val rawText = copiedText.text // TODO do not call it raw text... |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -141,7 +141,6 @@ abstract class VimPutBase : VimPut { | |||||||
|  |  | ||||||
|     if (data.visualSelection?.typeInEditor?.isLine == true && data.textData.typeInRegister.isChar) text += "\n" |     if (data.visualSelection?.typeInEditor?.isLine == true && data.textData.typeInRegister.isChar) text += "\n" | ||||||
|  |  | ||||||
|     // TODO: shouldn't it be adjusted when we are storing the text? |  | ||||||
|     if (data.textData.typeInRegister.isLine && text.isNotEmpty() && text.last() != '\n') text += '\n' |     if (data.textData.typeInRegister.isLine && text.isNotEmpty() && text.last() != '\n') text += '\n' | ||||||
|  |  | ||||||
|     if (data.textData.typeInRegister.isChar && text.lastOrNull() == '\n' && data.visualSelection?.typeInEditor?.isLine == false) { |     if (data.textData.typeInRegister.isChar && text.lastOrNull() == '\n' && data.visualSelection?.typeInEditor?.isLine == false) { | ||||||
| @@ -150,9 +149,10 @@ abstract class VimPutBase : VimPut { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return ProcessedTextData( |     return ProcessedTextData( | ||||||
|       data.textData.registerChar, |       text, | ||||||
|       data.textData.copiedText.updateText(text), |  | ||||||
|       data.textData.typeInRegister, |       data.textData.typeInRegister, | ||||||
|  |       data.textData.transferableData, | ||||||
|  |       data.textData.registerChar, | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -512,7 +512,7 @@ abstract class VimPutBase : VimPut { | |||||||
|     startOffsets.forEach { startOffset -> |     startOffsets.forEach { startOffset -> | ||||||
|       val selectionType = data.visualSelection?.typeInEditor ?: SelectionType.CHARACTER_WISE |       val selectionType = data.visualSelection?.typeInEditor ?: SelectionType.CHARACTER_WISE | ||||||
|       val (endOffset, updatedCaret) = putTextInternal( |       val (endOffset, updatedCaret) = putTextInternal( | ||||||
|         editor, updated, context, text.copiedText.text, text.typeInRegister, selectionType, |         editor, updated, context, text.text, text.typeInRegister, selectionType, | ||||||
|         startOffset, data.count, data.indent, data.caretAfterInsertedText, |         startOffset, data.count, data.indent, data.caretAfterInsertedText, | ||||||
|       ) |       ) | ||||||
|       updated = updatedCaret |       updated = updatedCaret | ||||||
|   | |||||||
| @@ -634,6 +634,12 @@ class VimRegex(pattern: String) { | |||||||
|  |  | ||||||
|     override fun nativeCarets(): List<VimCaret> = emptyList() |     override fun nativeCarets(): List<VimCaret> = emptyList() | ||||||
|      |      | ||||||
|  |     override val isFirstCaret: Boolean | ||||||
|  |       get() = false | ||||||
|  |  | ||||||
|  |     override val isReversingCarets: Boolean | ||||||
|  |       get() = false | ||||||
|  |      | ||||||
|     override fun forEachCaret(action: (VimCaret) -> Unit) {} |     override fun forEachCaret(action: (VimCaret) -> Unit) {} | ||||||
|  |  | ||||||
|     override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {} |     override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {} | ||||||
|   | |||||||
| @@ -8,32 +8,86 @@ | |||||||
| package com.maddyhome.idea.vim.register | package com.maddyhome.idea.vim.register | ||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.common.VimCopiedText |  | ||||||
| import com.maddyhome.idea.vim.helper.EngineStringHelper | import com.maddyhome.idea.vim.helper.EngineStringHelper | ||||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | import com.maddyhome.idea.vim.state.mode.SelectionType | ||||||
| import org.jetbrains.annotations.NonNls | import org.jetbrains.annotations.NonNls | ||||||
|  | import java.awt.event.KeyEvent | ||||||
| import javax.swing.KeyStroke | import javax.swing.KeyStroke | ||||||
|  |  | ||||||
| // TODO should we prefer keys over text, as they are more informative? | class Register { | ||||||
| // TODO e.g.  could be both <Esc> and <C-[> after trying to restore original keys |   var name: Char | ||||||
| data class Register( |   val type: SelectionType | ||||||
|   val name: Char, |   val keys: MutableList<KeyStroke> | ||||||
|   val copiedText: VimCopiedText, |   val transferableData: MutableList<out Any> | ||||||
|   val type: SelectionType, |   val rawText: String? | ||||||
| ) { |  | ||||||
|   val text = copiedText.text |  | ||||||
|   val keys get() = injector.parser.stringToKeys(copiedText.text) |  | ||||||
|   val printableString: String = |  | ||||||
|     EngineStringHelper.toPrintableCharacters(keys) // should be the same as [text], but we can't render control notation properly |  | ||||||
|  |  | ||||||
|   constructor(name: Char, type: SelectionType, keys: MutableList<KeyStroke>) : this( |   constructor(name: Char, type: SelectionType, keys: MutableList<KeyStroke>) { | ||||||
|     name, |     this.name = name | ||||||
|     injector.clipboardManager.dumbCopiedText(injector.parser.toPrintableString(keys)), |     this.type = type | ||||||
|     type |     this.keys = keys | ||||||
|   ) |     this.transferableData = mutableListOf() | ||||||
| //  constructor(name: Char, type: SelectionType, text: String, transferableData: MutableList<out Any>) : this(name, text, type, transferableData) |     this.rawText = text | ||||||
|  |   } | ||||||
|  |  | ||||||
|   override fun toString(): String = "@$name = $printableString" |   constructor( | ||||||
|  |     name: Char, | ||||||
|  |     type: SelectionType, | ||||||
|  |     text: String, | ||||||
|  |     transferableData: MutableList<out Any>, | ||||||
|  |   ) { | ||||||
|  |     this.name = name | ||||||
|  |     this.type = type | ||||||
|  |     this.keys = injector.parser.stringToKeys(text).toMutableList() | ||||||
|  |     this.transferableData = transferableData | ||||||
|  |     this.rawText = text | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   constructor( | ||||||
|  |     name: Char, | ||||||
|  |     type: SelectionType, | ||||||
|  |     text: String, | ||||||
|  |     transferableData: MutableList<out Any>, | ||||||
|  |     rawText: String, | ||||||
|  |   ) { | ||||||
|  |     this.name = name | ||||||
|  |     this.type = type | ||||||
|  |     this.keys = injector.parser.stringToKeys(text).toMutableList() | ||||||
|  |     this.transferableData = transferableData | ||||||
|  |     this.rawText = rawText | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   val text: String? | ||||||
|  |     get() { | ||||||
|  |       val builder = StringBuilder() | ||||||
|  |       for (key in keys) { | ||||||
|  |         val c = key.keyChar | ||||||
|  |         if (c == KeyEvent.CHAR_UNDEFINED) { | ||||||
|  |           return null | ||||||
|  |         } | ||||||
|  |         builder.append(c) | ||||||
|  |       } | ||||||
|  |       return builder.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |   val printableString: String | ||||||
|  |     get() = EngineStringHelper.toPrintableCharacters(keys) // should be the same as [text], but we can't render control notation properly | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Append the supplied text to any existing text. | ||||||
|  |    */ | ||||||
|  |   fun addTextAndResetTransferableData(text: String) { | ||||||
|  |     addKeys(injector.parser.stringToKeys(text)) | ||||||
|  |     transferableData.clear() | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   fun prependTextAndResetTransferableData(text: String) { | ||||||
|  |     this.keys.addAll(0, injector.parser.stringToKeys(text)) | ||||||
|  |     transferableData.clear() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   fun addKeys(keys: List<KeyStroke>) { | ||||||
|  |     this.keys.addAll(keys) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   object KeySorter : Comparator<Register> { |   object KeySorter : Comparator<Register> { | ||||||
|     @NonNls |     @NonNls | ||||||
| @@ -44,27 +98,3 @@ data class Register( | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Imagine you yanked two lines and have the following content in your register a - foo\nbar\n (register type is line-wise) |  | ||||||
|  * Now, there are three different ways to append content, each with a different outcome: |  | ||||||
|  * - If you append a macro qAbazq, you'll get foo\nbarbaz\n in register `a` and it stays line-wise |  | ||||||
|  * - If you use Vim script and execute let @A = "baz", the result will be foo\nbar\nbaz and the register becomes character-wise |  | ||||||
|  * - If you copy "baz" to register A, it becomes foo\nbar\nbaz\n and stays line-wise |  | ||||||
|  * |  | ||||||
|  * At the moment, we will stick to the third option to not overcomplicate the plugin |  | ||||||
|  * (until there is a user who notices the difference) |  | ||||||
|  */ |  | ||||||
| fun Register.addText(text: String): Register { |  | ||||||
|   return when (this.type) { |  | ||||||
|     SelectionType.CHARACTER_WISE -> { |  | ||||||
|       Register(this.name, injector.clipboardManager.dumbCopiedText(this.text + text), SelectionType.CHARACTER_WISE) // todo it's empty for historical reasons, but should we really clear transferable data? |  | ||||||
|     } |  | ||||||
|     SelectionType.LINE_WISE -> { |  | ||||||
|       Register(this.name, injector.clipboardManager.dumbCopiedText(this.text + text + (if (text.endsWith('\n')) "" else "\n")), SelectionType.LINE_WISE) // todo it's empty for historical reasons, but should we really clear transferable data? |  | ||||||
|     } |  | ||||||
|     SelectionType.BLOCK_WISE -> { |  | ||||||
|       Register(this.name, injector.clipboardManager.dumbCopiedText(this.text + "\n" + text), SelectionType.BLOCK_WISE) // todo it's empty for historical reasons, but should we really clear transferable data? |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -17,6 +17,13 @@ import javax.swing.KeyStroke | |||||||
|  |  | ||||||
| interface VimRegisterGroup { | interface VimRegisterGroup { | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Get the last register selected by the user | ||||||
|  |    * | ||||||
|  |    * @return The register, null if no such register | ||||||
|  |    */ | ||||||
|  |   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getLastRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)") | ||||||
|  |   val lastRegister: Register? | ||||||
|   var lastRegisterChar: Char |   var lastRegisterChar: Char | ||||||
|   val currentRegister: Char |   val currentRegister: Char | ||||||
|  |  | ||||||
| @@ -32,7 +39,6 @@ interface VimRegisterGroup { | |||||||
|   val isRegisterSpecifiedExplicitly: Boolean |   val isRegisterSpecifiedExplicitly: Boolean | ||||||
|   val defaultRegister: Char |   val defaultRegister: Char | ||||||
|  |  | ||||||
|   fun getLastRegister(editor: VimEditor, context: ExecutionContext): Register? |  | ||||||
|   fun isValid(reg: Char): Boolean |   fun isValid(reg: Char): Boolean | ||||||
|   fun selectRegister(reg: Char): Boolean |   fun selectRegister(reg: Char): Boolean | ||||||
|   fun resetRegister() |   fun resetRegister() | ||||||
| @@ -42,15 +48,6 @@ interface VimRegisterGroup { | |||||||
|   fun isRegisterWritable(reg: Char): Boolean |   fun isRegisterWritable(reg: Char): Boolean | ||||||
|  |  | ||||||
|   /** Store text into the last register. */ |   /** Store text into the last register. */ | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, com.maddyhome.idea.vim.api.ImmutableVimCaret, com.maddyhome.idea.vim.common.TextRange, com.maddyhome.idea.vim.state.mode.SelectionType, boolean)") |  | ||||||
|   fun storeText( |  | ||||||
|     editor: VimEditor, |  | ||||||
|     caret: ImmutableVimCaret, |  | ||||||
|     range: TextRange, |  | ||||||
|     type: SelectionType, |  | ||||||
|     isDelete: Boolean, |  | ||||||
|   ): Boolean |  | ||||||
|  |  | ||||||
|   fun storeText( |   fun storeText( | ||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     context: ExecutionContext, |     context: ExecutionContext, | ||||||
| @@ -58,31 +55,20 @@ interface VimRegisterGroup { | |||||||
|     range: TextRange, |     range: TextRange, | ||||||
|     type: SelectionType, |     type: SelectionType, | ||||||
|     isDelete: Boolean, |     isDelete: Boolean, | ||||||
|  |     forceAppend: Boolean = false, | ||||||
|  |     prependInsteadOfAppend: Boolean = false | ||||||
|   ): Boolean |   ): Boolean | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Stores text to any writable register (used for the let command) |    * Stores text to any writable register (used for the let command) | ||||||
|    */ |    */ | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.lang.String)") |  | ||||||
|   fun storeText(register: Char, text: String): Boolean |  | ||||||
|   fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String): Boolean |   fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String): Boolean | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Stores text to any writable register (used for multicaret tests) |    * Stores text to any writable register (used for multicaret tests) | ||||||
|    */ |    */ | ||||||
|   @TestOnly |   @TestOnly | ||||||
|   // todo better tests |   fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String, selectionType: SelectionType): Boolean | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.lang.String, com.maddyhome.idea.vim.state.mode.SelectionType)") |  | ||||||
|   fun storeText(register: Char, text: String, selectionType: SelectionType): Boolean |  | ||||||
|  |  | ||||||
|   @TestOnly |  | ||||||
|   fun storeText( |  | ||||||
|     editor: VimEditor, |  | ||||||
|     context: ExecutionContext, |  | ||||||
|     register: Char, |  | ||||||
|     text: String, |  | ||||||
|     selectionType: SelectionType, |  | ||||||
|   ): Boolean |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Stores text, character wise, in the given special register |    * Stores text, character wise, in the given special register | ||||||
| @@ -98,29 +84,17 @@ interface VimRegisterGroup { | |||||||
|    * preferable to yank from the fixture editor. |    * preferable to yank from the fixture editor. | ||||||
|    */ |    */ | ||||||
|   fun storeTextSpecial(register: Char, text: String): Boolean |   fun storeTextSpecial(register: Char, text: String): Boolean | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)") |   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)") | ||||||
|   fun getRegister(r: Char): Register? |   fun getRegister(r: Char): Register? | ||||||
|   fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? |   fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegisters(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)") |  | ||||||
|   fun getRegisters(): List<Register> |  | ||||||
|   fun getRegisters(editor: VimEditor, context: ExecutionContext): List<Register> |   fun getRegisters(editor: VimEditor, context: ExecutionContext): List<Register> | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#saveRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, com.maddyhome.idea.vim.register.Register)") |  | ||||||
|   fun saveRegister(r: Char, register: Register) |  | ||||||
|   fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register) |   fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register) | ||||||
|   fun startRecording(register: Char): Boolean |   fun startRecording(register: Char): Boolean | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getPlaybackRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)") |  | ||||||
|   fun getPlaybackRegister(r: Char): Register? |  | ||||||
|   fun getPlaybackRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? |   fun getPlaybackRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? | ||||||
|   fun recordText(text: String) |   fun recordText(text: String) | ||||||
|   fun setKeys(register: Char, keys: List<KeyStroke>) |   fun setKeys(register: Char, keys: List<KeyStroke>) | ||||||
|   fun setKeys(register: Char, keys: List<KeyStroke>, type: SelectionType) |   fun setKeys(register: Char, keys: List<KeyStroke>, type: SelectionType) | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#finishRecording(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)") |  | ||||||
|   fun finishRecording() |  | ||||||
|   fun finishRecording(editor: VimEditor, context: ExecutionContext) |   fun finishRecording(editor: VimEditor, context: ExecutionContext) | ||||||
|   fun getCurrentRegisterForMulticaret(): Char // `set clipbaard+=unnamedplus` should not make system register the default one when working with multiple carets VIM-2804 |   fun getCurrentRegisterForMulticaret(): Char // `set clipbaard+=unnamedplus` should not make system register the default one when working with multiple carets VIM-2804 | ||||||
|   fun isSystemClipboard(register: Char): Boolean |   fun isSystemClipboard(register: Char): Boolean | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.api.globalOptions | |||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.command.Argument | import com.maddyhome.idea.vim.command.Argument | ||||||
| import com.maddyhome.idea.vim.common.TextRange | import com.maddyhome.idea.vim.common.TextRange | ||||||
| import com.maddyhome.idea.vim.common.VimCopiedText |  | ||||||
| import com.maddyhome.idea.vim.diagnostic.VimLogger | import com.maddyhome.idea.vim.diagnostic.VimLogger | ||||||
| import com.maddyhome.idea.vim.diagnostic.debug | import com.maddyhome.idea.vim.diagnostic.debug | ||||||
| import com.maddyhome.idea.vim.diagnostic.vimLogger | import com.maddyhome.idea.vim.diagnostic.vimLogger | ||||||
| @@ -75,9 +74,13 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|   override val defaultRegister: Char |   override val defaultRegister: Char | ||||||
|     get() = defaultRegisterChar |     get() = defaultRegisterChar | ||||||
|  |  | ||||||
|   override fun getLastRegister(editor: VimEditor, context: ExecutionContext): Register? { |   /** | ||||||
|     return getRegister(editor, context, lastRegisterChar) |    * Get the last register selected by the user | ||||||
|   } |    * | ||||||
|  |    * @return The register, null if no such register | ||||||
|  |    */ | ||||||
|  |   override val lastRegister: Register? | ||||||
|  |     get() = getRegister(lastRegisterChar) | ||||||
|  |  | ||||||
|   private val onClipboardChanged: () -> Unit = { |   private val onClipboardChanged: () -> Unit = { | ||||||
|     val clipboardOptionValue = injector.globalOptions().clipboard |     val clipboardOptionValue = injector.globalOptions().clipboard | ||||||
| @@ -113,13 +116,20 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|     return if (isValid(reg)) { |     return if (isValid(reg)) { | ||||||
|       isRegisterSpecifiedExplicitly = true |       isRegisterSpecifiedExplicitly = true | ||||||
|       lastRegisterChar = reg |       lastRegisterChar = reg | ||||||
|       logger.debug { "register selected: $lastRegisterChar" } |       logger.debug { "register selected: $lastRegister" } | ||||||
|  |  | ||||||
|       true |       true | ||||||
|     } else { |     } else { | ||||||
|       false |       false | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   override fun getRegister(r: Char): Register? { | ||||||
|  |     val dummyEditor = injector.fallbackWindow | ||||||
|  |     val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor) | ||||||
|  |     return getRegister(dummyEditor, dummyContext, r) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Reset the selected register back to the default register. |    * Reset the selected register back to the default register. | ||||||
|    */ |    */ | ||||||
| @@ -175,6 +185,8 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|     type: SelectionType, |     type: SelectionType, | ||||||
|     register: Char, |     register: Char, | ||||||
|     isDelete: Boolean, |     isDelete: Boolean, | ||||||
|  |     forceAppend: Boolean, | ||||||
|  |     prependInsteadOfAppend: Boolean, | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     // Null register doesn't get saved, but acts like it was |     // Null register doesn't get saved, but acts like it was | ||||||
|     if (lastRegisterChar == BLACK_HOLE_REGISTER) return true |     if (lastRegisterChar == BLACK_HOLE_REGISTER) return true | ||||||
| @@ -193,50 +205,62 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|       end = t |       end = t | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     val copiedText = |  | ||||||
|       if (start != -1) { // FIXME: so, we had invalid ranges all the time?.. I've never handled such cases |  | ||||||
|         injector.clipboardManager.collectCopiedText(editor, context, range, text) |  | ||||||
|       } else { |  | ||||||
|         injector.clipboardManager.dumbCopiedText(text) |  | ||||||
|       } |  | ||||||
|     logger.debug { "Copy to '$lastRegisterChar' with copied text: $copiedText" } |  | ||||||
|     // If this is an uppercase register, we need to append the text to the corresponding lowercase register |     // If this is an uppercase register, we need to append the text to the corresponding lowercase register | ||||||
|     if (Character.isUpperCase(register)) { |     val transferableData: List<Any> = | ||||||
|  |       if (start != -1) injector.clipboardManager.getTransferableData(editor, range) else ArrayList() | ||||||
|  |     var processedText = | ||||||
|  |       if (start != -1) injector.clipboardManager.preprocessText(editor, range, text, transferableData) else text | ||||||
|  |     logger.debug { | ||||||
|  |       val transferableClasses = transferableData.joinToString(",") { it.javaClass.name } | ||||||
|  |       "Copy to '$lastRegister' with transferable data: $transferableClasses" | ||||||
|  |     } | ||||||
|  |     if (Character.isUpperCase(register) || forceAppend) { | ||||||
|  |       if (forceAppend && type == SelectionType.CHARACTER_WISE) { | ||||||
|  |         processedText = if (prependInsteadOfAppend) | ||||||
|  |           processedText + '\n' | ||||||
|  |         else | ||||||
|  |           '\n' + processedText | ||||||
|  |       } | ||||||
|       val lreg = Character.toLowerCase(register) |       val lreg = Character.toLowerCase(register) | ||||||
|       val r = myRegisters[lreg] |       val r = myRegisters[lreg] | ||||||
|       // Append the text if the lowercase register existed |       // Append the text if the lowercase register existed | ||||||
|       if (r != null) { |       if (r != null) { | ||||||
|         myRegisters[lreg] = r.addText(copiedText.text) |         if (prependInsteadOfAppend) { | ||||||
|  |           r.prependTextAndResetTransferableData(processedText) | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |           r.addTextAndResetTransferableData(processedText) | ||||||
|  |         } | ||||||
|       } else { |       } else { | ||||||
|         myRegisters[lreg] = Register(lreg, copiedText, type) |         myRegisters[lreg] = Register(lreg, type, processedText, ArrayList(transferableData)) | ||||||
|         logger.debug { "register '$register' contains: \"$copiedText\"" } |         logger.debug { "register '$register' contains: \"$processedText\"" } | ||||||
|       } // Set the text if the lowercase register didn't exist yet |       } // Set the text if the lowercase register didn't exist yet | ||||||
|     } else { |     } else { | ||||||
|       myRegisters[register] = Register(register, copiedText, type) |       myRegisters[register] = Register(register, type, processedText, ArrayList(transferableData)) | ||||||
|       logger.debug { "register '$register' contains: \"$copiedText\"" } |       logger.debug { "register '$register' contains: \"$processedText\"" } | ||||||
|     } // Put the text in the specified register |     } // Put the text in the specified register | ||||||
|  |  | ||||||
|     if (register == CLIPBOARD_REGISTER) { |     if (register == CLIPBOARD_REGISTER) { | ||||||
|       injector.clipboardManager.setClipboardContent(editor, context, copiedText) |       injector.clipboardManager.setClipboardText(processedText, text, ArrayList(transferableData)) | ||||||
|       if (!isRegisterSpecifiedExplicitly && !isDelete && isPrimaryRegisterSupported() && OptionConstants.clipboard_unnamedplus in injector.globalOptions().clipboard) { |       if (!isRegisterSpecifiedExplicitly && !isDelete && isPrimaryRegisterSupported() && OptionConstants.clipboard_unnamedplus in injector.globalOptions().clipboard) { | ||||||
|         injector.clipboardManager.setPrimaryContent(editor, context, copiedText) |         injector.clipboardManager.setPrimaryText(processedText, text, ArrayList(transferableData)) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (register == PRIMARY_REGISTER) { |     if (register == PRIMARY_REGISTER) { | ||||||
|       if (isPrimaryRegisterSupported()) { |       if (isPrimaryRegisterSupported()) { | ||||||
|         injector.clipboardManager.setPrimaryContent(editor, context, copiedText) |         injector.clipboardManager.setPrimaryText(processedText, text, ArrayList(transferableData)) | ||||||
|         if (!isRegisterSpecifiedExplicitly && !isDelete && OptionConstants.clipboard_unnamed in injector.globalOptions().clipboard) { |         if (!isRegisterSpecifiedExplicitly && !isDelete && OptionConstants.clipboard_unnamed in injector.globalOptions().clipboard) { | ||||||
|           injector.clipboardManager.setClipboardContent(editor, context, copiedText) |           injector.clipboardManager.setClipboardText(processedText, text, ArrayList(transferableData)) | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
|         injector.clipboardManager.setClipboardContent(editor, context, copiedText) |         injector.clipboardManager.setClipboardText(processedText, text, ArrayList(transferableData)) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Also add it to the unnamed register if the default wasn't specified |     // Also add it to the unnamed register if the default wasn't specified | ||||||
|     if (register != UNNAMED_REGISTER && ".:/".indexOf(register) == -1) { |     if (register != UNNAMED_REGISTER && ".:/".indexOf(register) == -1) { | ||||||
|       myRegisters[UNNAMED_REGISTER] = Register(UNNAMED_REGISTER, copiedText, type) |       myRegisters[UNNAMED_REGISTER] = Register(UNNAMED_REGISTER, type, processedText, ArrayList(transferableData)) | ||||||
|       logger.debug { "register '$UNNAMED_REGISTER' contains: \"$copiedText\"" } |       logger.debug { "register '$UNNAMED_REGISTER' contains: \"$processedText\"" } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (isDelete) { |     if (isDelete) { | ||||||
| @@ -256,44 +280,26 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|         while (d >= '1') { |         while (d >= '1') { | ||||||
|           val t = myRegisters[d] |           val t = myRegisters[d] | ||||||
|           if (t != null) { |           if (t != null) { | ||||||
|             val incName = (d.code + 1).toChar() |             t.name = (d.code + 1).toChar() | ||||||
|             myRegisters[incName] = Register(incName, t.copiedText, t.type) |             myRegisters[(d.code + 1).toChar()] = t | ||||||
|           } |           } | ||||||
|           d-- |           d-- | ||||||
|         } |         } | ||||||
|         myRegisters['1'] = Register('1', copiedText, type) |         myRegisters['1'] = Register('1', type, processedText, ArrayList(transferableData)) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // Deletes smaller than one line and without specified register go the the "-" register |       // Deletes smaller than one line and without specified register go the the "-" register | ||||||
|       if (smallInlineDeletion && register == defaultRegister) { |       if (smallInlineDeletion && register == defaultRegister) { | ||||||
|         myRegisters[SMALL_DELETION_REGISTER] = |         myRegisters[SMALL_DELETION_REGISTER] = | ||||||
|           Register(SMALL_DELETION_REGISTER, copiedText, type) |           Register(SMALL_DELETION_REGISTER, type, processedText, ArrayList(transferableData)) | ||||||
|       } |       } | ||||||
|     } else if (register == defaultRegister) { |     } else if (register == defaultRegister) { | ||||||
|       myRegisters['0'] = Register('0', copiedText, type) |       myRegisters['0'] = Register('0', type, processedText, ArrayList(transferableData)) | ||||||
|       logger.debug { "register '0' contains: \"$copiedText\"" } |       logger.debug { "register '0' contains: \"$processedText\"" } | ||||||
|     } // Yanks also go to register 0 if the default register was used |     } // Yanks also go to register 0 if the default register was used | ||||||
|     return true |     return true | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, com.maddyhome.idea.vim.api.ImmutableVimCaret, com.maddyhome.idea.vim.common.TextRange, com.maddyhome.idea.vim.state.mode.SelectionType, boolean)") |  | ||||||
|   override fun storeText( |  | ||||||
|     editor: VimEditor, |  | ||||||
|     caret: ImmutableVimCaret, |  | ||||||
|     range: TextRange, |  | ||||||
|     type: SelectionType, |  | ||||||
|     isDelete: Boolean, |  | ||||||
|   ): Boolean { |  | ||||||
|     return storeText( |  | ||||||
|       editor, |  | ||||||
|       injector.executionContextManager.getEditorExecutionContext(editor), |  | ||||||
|       caret, |  | ||||||
|       range, |  | ||||||
|       type, |  | ||||||
|       isDelete |  | ||||||
|     ) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Store text into the last register. |    * Store text into the last register. | ||||||
|    * |    * | ||||||
| @@ -310,10 +316,12 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|     range: TextRange, |     range: TextRange, | ||||||
|     type: SelectionType, |     type: SelectionType, | ||||||
|     isDelete: Boolean, |     isDelete: Boolean, | ||||||
|  |     forceAppend: Boolean, | ||||||
|  |     prependInsteadOfAppend: Boolean | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     if (isRegisterWritable()) { |     if (isRegisterWritable()) { | ||||||
|       val text = preprocessTextBeforeStoring(editor.getText(range), type) |       val text = preprocessTextBeforeStoring(editor.getText(range), type) | ||||||
|       return storeTextInternal(editor, context, range, text, type, lastRegisterChar, isDelete) |       return storeTextInternal(editor, context, range, text, type, lastRegisterChar, isDelete, forceAppend, prependInsteadOfAppend) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return false |     return false | ||||||
| @@ -348,68 +356,36 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|     if (READONLY_REGISTERS.indexOf(register) == -1 && register != LAST_SEARCH_REGISTER && register != UNNAMED_REGISTER) { |     if (READONLY_REGISTERS.indexOf(register) == -1 && register != LAST_SEARCH_REGISTER && register != UNNAMED_REGISTER) { | ||||||
|       return false |       return false | ||||||
|     } |     } | ||||||
|     myRegisters[register] = Register( |     myRegisters[register] = Register(register, | ||||||
|       register, |  | ||||||
|       injector.clipboardManager.dumbCopiedText(text), |  | ||||||
|       SelectionType.CHARACTER_WISE |       SelectionType.CHARACTER_WISE | ||||||
|     ) // TODO why transferable data is not collected? |     , text, ArrayList()) | ||||||
|     logger.debug { "register '$register' contains: \"$text\"" } |     logger.debug { "register '$register' contains: \"$text\"" } | ||||||
|     return true |     return true | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)") |   override fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String): Boolean { | ||||||
|   override fun getRegister(r: Char): Register? { |     return storeText(editor, context, register, text, SelectionType.CHARACTER_WISE) | ||||||
|     val dummyEditor = injector.fallbackWindow |  | ||||||
|     val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor) |  | ||||||
|     return getRegister(dummyEditor, dummyContext, r) |  | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.lang.String, com.maddyhome.idea.vim.state.mode.SelectionType)") |   override fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String, selectionType: SelectionType, | ||||||
|   override fun storeText(register: Char, text: String, selectionType: SelectionType): Boolean { |  | ||||||
|     val dummyEditor = injector.fallbackWindow |  | ||||||
|     val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor) |  | ||||||
|     return storeText(dummyEditor, dummyContext, register, text, selectionType) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun storeText( |  | ||||||
|     editor: VimEditor, |  | ||||||
|     context: ExecutionContext, |  | ||||||
|     register: Char, |  | ||||||
|     text: String, |  | ||||||
|     selectionType: SelectionType, |  | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     if (!WRITABLE_REGISTERS.contains(register)) { |     if (!WRITABLE_REGISTERS.contains(register)) { | ||||||
|       return false |       return false | ||||||
|     } |     } | ||||||
|     logger.debug { "register '$register' contains: \"$text\"" } |     logger.debug { "register '$register' contains: \"$text\"" } | ||||||
|     val oldRegister = getRegister(editor, context, register.lowercaseChar()) |     val textToStore = if (register.isUpperCase()) { | ||||||
|     val newRegister = if (register.isUpperCase() && oldRegister != null) { |       (getRegister(register.lowercaseChar())?.rawText ?: "") + text | ||||||
|       oldRegister.addText(text) |  | ||||||
|     } else { |     } else { | ||||||
|       Register( |       text | ||||||
|         register, |  | ||||||
|         injector.clipboardManager.dumbCopiedText(text), |  | ||||||
|         selectionType |  | ||||||
|       ) // FIXME why don't we collect transferable data? |  | ||||||
|     } |     } | ||||||
|     saveRegister(editor, context, register, newRegister) |     val reg = Register(register, selectionType, textToStore, ArrayList()) | ||||||
|  |     saveRegister(editor, context, register, reg) | ||||||
|     if (register == '/') { |     if (register == '/') { | ||||||
|       injector.searchGroup.lastSearchPattern = text // todo we should not have this field if we have the "/" register |       injector.searchGroup.lastSearchPattern = text // todo we should not have this field if we have the "/" register | ||||||
|     } |     } | ||||||
|     return true |     return true | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.lang.String)") |  | ||||||
|   override fun storeText(register: Char, text: String): Boolean { |  | ||||||
|     val dummyEditor = injector.fallbackWindow |  | ||||||
|     val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor) |  | ||||||
|     return storeText(dummyEditor, dummyContext, register, text) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String): Boolean { |  | ||||||
|     return storeText(editor, context, register, text, SelectionType.CHARACTER_WISE) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private fun guessSelectionType(text: String): SelectionType { |   private fun guessSelectionType(text: String): SelectionType { | ||||||
|     return if (text.endsWith("\n")) SelectionType.LINE_WISE else SelectionType.CHARACTER_WISE |     return if (text.endsWith("\n")) SelectionType.LINE_WISE else SelectionType.CHARACTER_WISE | ||||||
|   } |   } | ||||||
| @@ -421,10 +397,10 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|    * @param r - the register character corresponding to either the primary selection (*) or clipboard selection (+) |    * @param r - the register character corresponding to either the primary selection (*) or clipboard selection (+) | ||||||
|    * @return the content of the selection, if available, otherwise null |    * @return the content of the selection, if available, otherwise null | ||||||
|    */ |    */ | ||||||
|   private fun refreshClipboardRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? { |   private fun refreshClipboardRegister(r: Char): Register? { | ||||||
|     return when (r) { |     return when (r) { | ||||||
|       PRIMARY_REGISTER -> refreshPrimaryRegister(editor, context) |       PRIMARY_REGISTER -> refreshPrimaryRegister() | ||||||
|       CLIPBOARD_REGISTER -> refreshClipboardRegister(editor, context) |       CLIPBOARD_REGISTER -> refreshClipboardRegister() | ||||||
|       else -> throw RuntimeException("Clipboard register expected, got $r") |       else -> throw RuntimeException("Clipboard register expected, got $r") | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -433,56 +409,60 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|     return System.getenv("DISPLAY") != null && injector.systemInfoService.isXWindow |     return System.getenv("DISPLAY") != null && injector.systemInfoService.isXWindow | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private fun setSystemPrimaryRegisterText(editor: VimEditor, context: ExecutionContext, copiedText: VimCopiedText) { |   private fun setSystemPrimaryRegisterText(text: String, rawText: String, transferableData: List<Any>) { | ||||||
|     logger.trace("Setting text: $copiedText to primary selection...") |     logger.trace("Setting text: $text to primary selection...") | ||||||
|     if (isPrimaryRegisterSupported()) { |     if (isPrimaryRegisterSupported()) { | ||||||
|       try { |       try { | ||||||
|         injector.clipboardManager.setPrimaryContent(editor, context, copiedText) |         injector.clipboardManager.setPrimaryText(text, rawText, transferableData) | ||||||
|       } catch (e: Exception) { |       } catch (e: Exception) { | ||||||
|         logger.warn("False positive X11 primary selection support") |         logger.warn("False positive X11 primary selection support") | ||||||
|         logger.trace("Setting text to primary selection failed. Setting it to clipboard selection instead") |         logger.trace("Setting text to primary selection failed. Setting it to clipboard selection instead") | ||||||
|         setSystemClipboardRegisterText(editor, context, copiedText) |         setSystemClipboardRegisterText(text, rawText, transferableData) | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       logger.trace("X11 primary selection is not supporting. Setting clipboard selection instead") |       logger.trace("X11 primary selection is not supporting. Setting clipboard selection instead") | ||||||
|       setSystemClipboardRegisterText(editor, context, copiedText) |       setSystemClipboardRegisterText(text, rawText, transferableData) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private fun setSystemClipboardRegisterText(editor: VimEditor, context: ExecutionContext, copiedText: VimCopiedText) { |   private fun setSystemClipboardRegisterText(text: String, rawText: String, transferableData: List<Any>) { | ||||||
|     injector.clipboardManager.setClipboardContent(editor, context, copiedText) |     injector.clipboardManager.setClipboardText(text, rawText, transferableData) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private fun refreshPrimaryRegister(editor: VimEditor, context: ExecutionContext): Register? { |   private fun refreshPrimaryRegister(): Register? { | ||||||
|     logger.trace("Syncing cached primary selection value..") |     logger.trace("Syncing cached primary selection value..") | ||||||
|     if (!isPrimaryRegisterSupported()) { |     if (!isPrimaryRegisterSupported()) { | ||||||
|       logger.trace("X11 primary selection is not supported. Syncing clipboard selection..") |       logger.trace("X11 primary selection is not supported. Syncing clipboard selection..") | ||||||
|       return refreshClipboardRegister(editor, context) |       return refreshClipboardRegister() | ||||||
|     } |     } | ||||||
|     try { |     try { | ||||||
|       val clipboardData = injector.clipboardManager.getPrimaryContent(editor, context) ?: return null |       val clipboardData = injector.clipboardManager.getPrimaryTextAndTransferableData() ?: return null | ||||||
|       val currentRegister = myRegisters[PRIMARY_REGISTER] |       val currentRegister = myRegisters[PRIMARY_REGISTER] | ||||||
|       if (currentRegister != null && clipboardData.text == currentRegister.text) { |       val text = clipboardData.first | ||||||
|  |       val transferableData = clipboardData.second?.toMutableList() | ||||||
|  |       if (currentRegister != null && text == currentRegister.text) { | ||||||
|         return currentRegister |         return currentRegister | ||||||
|       } |       } | ||||||
|       return Register(PRIMARY_REGISTER, clipboardData, guessSelectionType(clipboardData.text)) |       return transferableData?.let { Register(PRIMARY_REGISTER, guessSelectionType(text), text, it) } | ||||||
|     } catch (e: Exception) { |     } catch (e: Exception) { | ||||||
|       logger.warn("False positive X11 primary selection support") |       logger.warn("False positive X11 primary selection support") | ||||||
|       logger.trace("Syncing primary selection failed. Syncing clipboard selection instead") |       logger.trace("Syncing primary selection failed. Syncing clipboard selection instead") | ||||||
|       return refreshClipboardRegister(editor, context) |       return refreshClipboardRegister() | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private fun refreshClipboardRegister(editor: VimEditor, context: ExecutionContext): Register? { |   private fun refreshClipboardRegister(): Register? { | ||||||
|     // for some reason non-X systems use PRIMARY_REGISTER as a clipboard storage |     // for some reason non-X systems use PRIMARY_REGISTER as a clipboard storage | ||||||
|     val systemAwareClipboardRegister = if (isPrimaryRegisterSupported()) CLIPBOARD_REGISTER else PRIMARY_REGISTER |     val systemAwareClipboardRegister = if (isPrimaryRegisterSupported()) CLIPBOARD_REGISTER else PRIMARY_REGISTER | ||||||
|  |  | ||||||
|     val clipboardData = injector.clipboardManager.getClipboardContent(editor, context) ?: return null |     val clipboardData = injector.clipboardManager.getClipboardTextAndTransferableData() ?: return null | ||||||
|     val currentRegister = myRegisters[systemAwareClipboardRegister] |     val currentRegister = myRegisters[systemAwareClipboardRegister] | ||||||
|     if (currentRegister != null && clipboardData.text == currentRegister.text) { |     val text = clipboardData.first | ||||||
|  |     val transferableData = clipboardData.second?.toMutableList() | ||||||
|  |     if (currentRegister != null && text == currentRegister.text) { | ||||||
|       return currentRegister |       return currentRegister | ||||||
|     } |     } | ||||||
|     return Register(systemAwareClipboardRegister, clipboardData, guessSelectionType(clipboardData.text)) |     return transferableData?.let { Register(systemAwareClipboardRegister, guessSelectionType(text), text, it) } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? { |   override fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? { | ||||||
| @@ -492,50 +472,35 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|       myR = Character.toLowerCase(myR) |       myR = Character.toLowerCase(myR) | ||||||
|     } |     } | ||||||
|     return if (CLIPBOARD_REGISTERS.indexOf(myR) >= 0) refreshClipboardRegister( |     return if (CLIPBOARD_REGISTERS.indexOf(myR) >= 0) refreshClipboardRegister( | ||||||
|       editor, |       myR) else myRegisters[myR] | ||||||
|       context, |  | ||||||
|       myR |  | ||||||
|     ) else myRegisters[myR] |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegisters(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)") |  | ||||||
|   override fun getRegisters(): List<Register> { |  | ||||||
|     val dummyEditor = injector.fallbackWindow |  | ||||||
|     val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor) |  | ||||||
|     return getRegisters(dummyEditor, dummyContext) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun getRegisters(editor: VimEditor, context: ExecutionContext): List<Register> { |   override fun getRegisters(editor: VimEditor, context: ExecutionContext): List<Register> { | ||||||
|     val filteredRegisters = myRegisters.values.filterNot { CLIPBOARD_REGISTERS.contains(it.name) }.toMutableList() |     val filteredRegisters = myRegisters.values.filterNot { CLIPBOARD_REGISTERS.contains(it.name) }.toMutableList() | ||||||
|     val clipboardRegisters = CLIPBOARD_REGISTERS |     val clipboardRegisters = CLIPBOARD_REGISTERS | ||||||
|       .filterNot { it == CLIPBOARD_REGISTER && !isPrimaryRegisterSupported() } // for some reason non-X systems use PRIMARY_REGISTER as a clipboard storage |       .filterNot { it == CLIPBOARD_REGISTER && !isPrimaryRegisterSupported() } // for some reason non-X systems use PRIMARY_REGISTER as a clipboard storage | ||||||
|       .mapNotNull { refreshClipboardRegister(editor, context, it) } |       .mapNotNull { refreshClipboardRegister(it) } | ||||||
|  |  | ||||||
|     return (filteredRegisters + clipboardRegisters).sortedWith(Register.KeySorter) |     return (filteredRegisters + clipboardRegisters).sortedWith(Register.KeySorter) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#saveRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, com.maddyhome.idea.vim.register.Register)") |  | ||||||
|   override fun saveRegister(r: Char, register: Register) { |  | ||||||
|     val dummyEditor = injector.fallbackWindow |  | ||||||
|     val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor) |  | ||||||
|     saveRegister(dummyEditor, dummyContext, r, register) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register) { |   override fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register) { | ||||||
|     var myR = if (Character.isUpperCase(r)) Character.toLowerCase(r) else r |     var myR = if (Character.isUpperCase(r)) Character.toLowerCase(r) else r | ||||||
|  |     val text = register.text | ||||||
|  |     val rawText = register.rawText | ||||||
|  |  | ||||||
|     if (CLIPBOARD_REGISTERS.indexOf(myR) >= 0) { |     if (CLIPBOARD_REGISTERS.indexOf(myR) >= 0 && text != null && rawText != null) { | ||||||
|       when (myR) { |       when (myR) { | ||||||
|         CLIPBOARD_REGISTER -> { |         CLIPBOARD_REGISTER -> { | ||||||
|           if (!isPrimaryRegisterSupported()) { |           if (!isPrimaryRegisterSupported()) { | ||||||
|             // it looks wrong, but for some reason non-X systems use the * register to store the clipboard content |             // it looks wrong, but for some reason non-X systems use the * register to store the clipboard content | ||||||
|             myR = PRIMARY_REGISTER |             myR = PRIMARY_REGISTER | ||||||
|           } |           } | ||||||
|           setSystemClipboardRegisterText(editor, context, register.copiedText) |           setSystemClipboardRegisterText(text, rawText, ArrayList(register.transferableData)) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         PRIMARY_REGISTER -> { |         PRIMARY_REGISTER -> { | ||||||
|           setSystemPrimaryRegisterText(editor, context, register.copiedText) |           setSystemPrimaryRegisterText(text, rawText, ArrayList(register.transferableData)) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -552,15 +517,8 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getPlaybackRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)") |  | ||||||
|   override fun getPlaybackRegister(r: Char): Register? { |  | ||||||
|     val dummyEditor = injector.fallbackWindow |  | ||||||
|     val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor) |  | ||||||
|     return getPlaybackRegister(dummyEditor, dummyContext, r) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun getPlaybackRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? { |   override fun getPlaybackRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? { | ||||||
|     return if (PLAYBACK_REGISTERS.indexOf(r) != 0) getRegister(editor, context, r) else null |     return if (PLAYBACK_REGISTERS.indexOf(r) != 0) getRegister(r) else null | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun recordText(text: String) { |   override fun recordText(text: String) { | ||||||
| @@ -578,19 +536,12 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|     myRegisters[register] = Register(register, type, keys.toMutableList()) |     myRegisters[register] = Register(register, type, keys.toMutableList()) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#finishRecording(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)") |  | ||||||
|   override fun finishRecording() { |  | ||||||
|     val dummyEditor = injector.fallbackWindow |  | ||||||
|     val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor) |  | ||||||
|     finishRecording(dummyEditor, dummyContext) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   override fun finishRecording(editor: VimEditor, context: ExecutionContext) { |   override fun finishRecording(editor: VimEditor, context: ExecutionContext) { | ||||||
|     val register = recordRegister |     val register = recordRegister | ||||||
|     if (register != null) { |     if (register != null) { | ||||||
|       var reg: Register? = null |       var reg: Register? = null | ||||||
|       if (Character.isUpperCase(register)) { |       if (Character.isUpperCase(register)) { | ||||||
|         reg = getRegister(editor, context, register) |         reg = getRegister(register) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       val myRecordList = recordList |       val myRecordList = recordList | ||||||
| @@ -599,7 +550,7 @@ abstract class VimRegisterGroupBase : VimRegisterGroup { | |||||||
|           reg = Register(Character.toLowerCase(register), SelectionType.CHARACTER_WISE, myRecordList) |           reg = Register(Character.toLowerCase(register), SelectionType.CHARACTER_WISE, myRecordList) | ||||||
|           myRegisters[Character.toLowerCase(register)] = reg |           myRegisters[Character.toLowerCase(register)] = reg | ||||||
|         } else { |         } else { | ||||||
|           myRegisters[reg.name.lowercaseChar()] = reg.addText(injector.parser.toPrintableString(myRecordList)) |           reg.addKeys(myRecordList) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.vimscript.model.commands | |||||||
| import com.intellij.vim.annotations.ExCommand | import com.intellij.vim.annotations.ExCommand | ||||||
| import com.maddyhome.idea.vim.api.ExecutionContext | import com.maddyhome.idea.vim.api.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
|  | import com.maddyhome.idea.vim.api.getText | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.command.OperatorArguments | import com.maddyhome.idea.vim.command.OperatorArguments | ||||||
| import com.maddyhome.idea.vim.ex.ranges.Range | import com.maddyhome.idea.vim.ex.ranges.Range | ||||||
| @@ -37,7 +38,7 @@ data class CopyTextCommand(val range: Range, val modifier: CommandModifier, val | |||||||
|     val carets = editor.sortedCarets() |     val carets = editor.sortedCarets() | ||||||
|     for (caret in carets) { |     for (caret in carets) { | ||||||
|       val range = getLineRange(editor, caret).toTextRange(editor) |       val range = getLineRange(editor, caret).toTextRange(editor) | ||||||
|       val copiedText = injector.clipboardManager.collectCopiedText(editor, context, range) |       val text = editor.getText(range) | ||||||
|  |  | ||||||
|       // Copy is defined as: |       // Copy is defined as: | ||||||
|       // :[range]co[py] {address} |       // :[range]co[py] {address} | ||||||
| @@ -46,7 +47,8 @@ data class CopyTextCommand(val range: Range, val modifier: CommandModifier, val | |||||||
|       // the line _before_ the first line (i.e., copy to above the first line). |       // the line _before_ the first line (i.e., copy to above the first line). | ||||||
|       val address1 = getAddressFromArgument(editor) |       val address1 = getAddressFromArgument(editor) | ||||||
|  |  | ||||||
|       val textData = PutData.TextData(null, copiedText, SelectionType.LINE_WISE) |       val transferableData = injector.clipboardManager.getTransferableData(editor, range) | ||||||
|  |       val textData = PutData.TextData(text, SelectionType.LINE_WISE, transferableData, null) | ||||||
|       var mutableCaret = caret |       var mutableCaret = caret | ||||||
|       val putData = if (address1 == 0) { |       val putData = if (address1 == 0) { | ||||||
|         // TODO: This should maintain current column location |         // TODO: This should maintain current column location | ||||||
|   | |||||||
| @@ -88,7 +88,7 @@ data class MoveTextCommand(val range: Range, val modifier: CommandModifier, val | |||||||
|     val selectionEndOffset = lastSelectionInfo.end?.let { editor.bufferPositionToOffset(it) } |     val selectionEndOffset = lastSelectionInfo.end?.let { editor.bufferPositionToOffset(it) } | ||||||
|  |  | ||||||
|     val text = editor.getText(range) |     val text = editor.getText(range) | ||||||
|     val textData = PutData.TextData(null, injector.clipboardManager.dumbCopiedText(text), SelectionType.LINE_WISE) |     val textData = PutData.TextData(text, SelectionType.LINE_WISE, emptyList(), null) | ||||||
|  |  | ||||||
|     val dropNewLineInEnd = (line + linesMoved == editor.lineCount() - 1 && text.last() == '\n') || |     val dropNewLineInEnd = (line + linesMoved == editor.lineCount() - 1 && text.last() == '\n') || | ||||||
|       (lineRange.endLine == editor.lineCount() - 1) |       (lineRange.endLine == editor.lineCount() - 1) | ||||||
|   | |||||||
| @@ -47,7 +47,12 @@ data class PutLinesCommand(val range: Range, val modifier: CommandModifier, val | |||||||
|  |  | ||||||
|     val line = if (range.size() == 0) -1 else getLine(editor) |     val line = if (range.size() == 0) -1 else getLine(editor) | ||||||
|     val textData = registerGroup.getRegister(editor, context, registerGroup.lastRegisterChar)?.let { |     val textData = registerGroup.getRegister(editor, context, registerGroup.lastRegisterChar)?.let { | ||||||
|       PutData.TextData(null, it.copiedText, SelectionType.LINE_WISE) |       PutData.TextData( | ||||||
|  |         it.text ?: injector.parser.toKeyNotation(it.keys), | ||||||
|  |         SelectionType.LINE_WISE, | ||||||
|  |         it.transferableData, | ||||||
|  |         null, | ||||||
|  |       ) | ||||||
|     } |     } | ||||||
|     val putData = PutData( |     val putData = PutData( | ||||||
|       textData, |       textData, | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import com.maddyhome.idea.vim.api.VimEditor | |||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.command.OperatorArguments | import com.maddyhome.idea.vim.command.OperatorArguments | ||||||
| import com.maddyhome.idea.vim.ex.ranges.Range | import com.maddyhome.idea.vim.ex.ranges.Range | ||||||
|  | import com.maddyhome.idea.vim.helper.EngineStringHelper | ||||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | import com.maddyhome.idea.vim.state.mode.SelectionType | ||||||
| import com.maddyhome.idea.vim.vimscript.model.ExecutionResult | import com.maddyhome.idea.vim.vimscript.model.ExecutionResult | ||||||
|  |  | ||||||
| @@ -41,7 +42,8 @@ data class RegistersCommand(val range: Range, val modifier: CommandModifier, val | |||||||
|           SelectionType.CHARACTER_WISE -> "c" |           SelectionType.CHARACTER_WISE -> "c" | ||||||
|           SelectionType.BLOCK_WISE -> "b" |           SelectionType.BLOCK_WISE -> "b" | ||||||
|         } |         } | ||||||
|         "  $type  \"${reg.name}   ${reg.printableString.take(200)}" |         val text = reg.rawText?.let { injector.parser.parseKeys(it) } ?: reg.keys | ||||||
|  |         "  $type  \"${reg.name}   ${EngineStringHelper.toPrintableCharacters(text).take(200)}" | ||||||
|       } |       } | ||||||
|  |  | ||||||
|     injector.outputPanel.output(editor, context, regs) |     injector.outputPanel.output(editor, context, regs) | ||||||
|   | |||||||
| @@ -38,8 +38,6 @@ interface VimYankGroup { | |||||||
|    * @param count  The number of lines to yank |    * @param count  The number of lines to yank | ||||||
|    * @return true if able to yank the lines, false if not |    * @return true if able to yank the lines, false if not | ||||||
|    */ |    */ | ||||||
|   @Deprecated("Please use the same method, but with ExecutionContext") |  | ||||||
|   fun yankLine(editor: VimEditor, count: Int): Boolean |  | ||||||
|   fun yankLine(editor: VimEditor, context: ExecutionContext, count: Int): Boolean |   fun yankLine(editor: VimEditor, context: ExecutionContext, count: Int): Boolean | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -50,8 +48,6 @@ interface VimYankGroup { | |||||||
|    * @param type   The type of yank |    * @param type   The type of yank | ||||||
|    * @return true if able to yank the range, false if not |    * @return true if able to yank the range, false if not | ||||||
|    */ |    */ | ||||||
|   @Deprecated("Please use the same method, but with ExecutionContext") |  | ||||||
|   fun yankRange(editor: VimEditor, range: TextRange?, type: SelectionType, moveCursor: Boolean): Boolean |  | ||||||
|   fun yankRange( |   fun yankRange( | ||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     context: ExecutionContext, |     context: ExecutionContext, | ||||||
|   | |||||||
| @@ -10,18 +10,14 @@ package com.maddyhome.idea.vim.yank | |||||||
|  |  | ||||||
| import com.maddyhome.idea.vim.action.motion.updown.MotionDownLess1FirstNonSpaceAction | import com.maddyhome.idea.vim.action.motion.updown.MotionDownLess1FirstNonSpaceAction | ||||||
| import com.maddyhome.idea.vim.api.ExecutionContext | import com.maddyhome.idea.vim.api.ExecutionContext | ||||||
| import com.maddyhome.idea.vim.api.ImmutableVimCaret |  | ||||||
| import com.maddyhome.idea.vim.api.VimCaret | import com.maddyhome.idea.vim.api.VimCaret | ||||||
| import com.maddyhome.idea.vim.api.VimEditor | import com.maddyhome.idea.vim.api.VimEditor | ||||||
| import com.maddyhome.idea.vim.api.anyNonWhitespace |  | ||||||
| import com.maddyhome.idea.vim.api.getLineEndForOffset | import com.maddyhome.idea.vim.api.getLineEndForOffset | ||||||
| import com.maddyhome.idea.vim.api.getLineStartForOffset | import com.maddyhome.idea.vim.api.getLineStartForOffset | ||||||
| import com.maddyhome.idea.vim.api.injector | import com.maddyhome.idea.vim.api.injector | ||||||
| import com.maddyhome.idea.vim.command.Argument | import com.maddyhome.idea.vim.command.Argument | ||||||
| import com.maddyhome.idea.vim.command.MotionType |  | ||||||
| import com.maddyhome.idea.vim.command.OperatorArguments | import com.maddyhome.idea.vim.command.OperatorArguments | ||||||
| import com.maddyhome.idea.vim.common.TextRange | import com.maddyhome.idea.vim.common.TextRange | ||||||
| import com.maddyhome.idea.vim.handler.MotionActionHandler |  | ||||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | import com.maddyhome.idea.vim.state.mode.SelectionType | ||||||
| import org.jetbrains.annotations.Contract | import org.jetbrains.annotations.Contract | ||||||
| import kotlin.math.min | import kotlin.math.min | ||||||
| @@ -30,20 +26,16 @@ open class YankGroupBase : VimYankGroup { | |||||||
|   private fun yankRange( |   private fun yankRange( | ||||||
|     editor: VimEditor, |     editor: VimEditor, | ||||||
|     context: ExecutionContext, |     context: ExecutionContext, | ||||||
|     caretToRange: Map<ImmutableVimCaret, Pair<TextRange, SelectionType>>, |     range: TextRange, | ||||||
|  |     type: SelectionType, | ||||||
|     startOffsets: Map<VimCaret, Int>?, |     startOffsets: Map<VimCaret, Int>?, | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     startOffsets?.forEach { (caret, offset) -> |     startOffsets?.forEach { (caret, offset) -> | ||||||
|       caret.moveToOffset(offset) |       caret.moveToOffset(offset) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     injector.listenersNotifier.notifyYankPerformed(caretToRange.mapValues { it.value.first }) |     injector.listenersNotifier.notifyYankPerformed(editor, range) | ||||||
|  |     return injector.registerGroup.storeText(editor, context, editor.primaryCaret(), range, type, false) | ||||||
|     var result = true |  | ||||||
|     for ((caret, myRange) in caretToRange) { |  | ||||||
|       result = caret.registerStorage.storeText(editor, context, myRange.first, myRange.second, false) && result |  | ||||||
|     } |  | ||||||
|     return result |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Contract("_, _ -> new") |   @Contract("_, _ -> new") | ||||||
| @@ -83,11 +75,12 @@ open class YankGroupBase : VimYankGroup { | |||||||
|     operatorArguments: OperatorArguments, |     operatorArguments: OperatorArguments, | ||||||
|   ): Boolean { |   ): Boolean { | ||||||
|     val motion = argument as? Argument.Motion ?: return false |     val motion = argument as? Argument.Motion ?: return false | ||||||
|  |     val motionType = motion.getMotionType() | ||||||
|  |  | ||||||
|     val nativeCaretCount = editor.nativeCarets().size |     val nativeCaretCount = editor.nativeCarets().size | ||||||
|     if (nativeCaretCount <= 0) return false |     if (nativeCaretCount <= 0) return false | ||||||
|  |  | ||||||
|     val caretToRange = HashMap<ImmutableVimCaret, Pair<TextRange, SelectionType>>(nativeCaretCount) |     val ranges = ArrayList<Pair<Int, Int>>(nativeCaretCount) | ||||||
|  |  | ||||||
|     // This logic is from original vim |     // This logic is from original vim | ||||||
|     val startOffsets = |     val startOffsets = | ||||||
| @@ -97,49 +90,28 @@ open class YankGroupBase : VimYankGroup { | |||||||
|         HashMap<VimCaret, Int>(nativeCaretCount) |         HashMap<VimCaret, Int>(nativeCaretCount) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |  | ||||||
|     for (caret in editor.nativeCarets()) { |     for (caret in editor.nativeCarets()) { | ||||||
|       var motionType = motion.getMotionType() |  | ||||||
|       val motionRange = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments) |       val motionRange = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments) | ||||||
|         ?: continue |         ?: continue | ||||||
|  |  | ||||||
|       assert(motionRange.size() == 1) |       assert(motionRange.size() == 1) | ||||||
|  |       ranges.add(motionRange.startOffset to motionRange.endOffset) | ||||||
|       startOffsets?.put(caret, motionRange.normalize().startOffset) |       startOffsets?.put(caret, motionRange.normalize().startOffset) | ||||||
|  |  | ||||||
|       // Yank motion commands that are not linewise become linewise if all the following are true: |  | ||||||
|       // 1) The range is across multiple lines |  | ||||||
|       // 2) There is only whitespace before the start of the range |  | ||||||
|       // 3) There is only whitespace after the end of the range |  | ||||||
|       if (argument.motion is MotionActionHandler && argument.motion.motionType == MotionType.EXCLUSIVE) { |  | ||||||
|         val start = editor.offsetToBufferPosition(motionRange.startOffset) |  | ||||||
|         val end = editor.offsetToBufferPosition(motionRange.endOffset) |  | ||||||
|         if (start.line != end.line |  | ||||||
|           && !editor.anyNonWhitespace(motionRange.startOffset, -1) |  | ||||||
|           && !editor.anyNonWhitespace(motionRange.endOffset, 1) |  | ||||||
|         ) { |  | ||||||
|           motionType = SelectionType.LINE_WISE |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       caretToRange[caret] = TextRange(motionRange.startOffset, motionRange.endOffset) to motionType |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (caretToRange.isEmpty()) return false |     val range = getTextRange(ranges, motionType) ?: return false | ||||||
|  |  | ||||||
|  |     if (range.size() == 0) return false | ||||||
|  |  | ||||||
|     return yankRange( |     return yankRange( | ||||||
|       editor, |       editor, | ||||||
|       context, |       context, | ||||||
|       caretToRange, |       range, | ||||||
|  |       motionType, | ||||||
|       startOffsets, |       startOffsets, | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Deprecated("Please use the same method, but with ExecutionContext") |  | ||||||
|   override fun yankLine(editor: VimEditor, count: Int): Boolean { |  | ||||||
|     val context = injector.executionContextManager.getEditorExecutionContext(editor) |  | ||||||
|     return yankLine(editor, context, count) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * This yanks count lines of text |    * This yanks count lines of text | ||||||
|    * |    * | ||||||
| @@ -149,24 +121,18 @@ open class YankGroupBase : VimYankGroup { | |||||||
|    */ |    */ | ||||||
|   override fun yankLine(editor: VimEditor, context: ExecutionContext, count: Int): Boolean { |   override fun yankLine(editor: VimEditor, context: ExecutionContext, count: Int): Boolean { | ||||||
|     val caretCount = editor.nativeCarets().size |     val caretCount = editor.nativeCarets().size | ||||||
|     val caretToRange = HashMap<ImmutableVimCaret, Pair<TextRange, SelectionType>>(caretCount) |     val ranges = ArrayList<Pair<Int, Int>>(caretCount) | ||||||
|     for (caret in editor.nativeCarets()) { |     for (caret in editor.nativeCarets()) { | ||||||
|       val start = injector.motion.moveCaretToCurrentLineStart(editor, caret) |       val start = injector.motion.moveCaretToCurrentLineStart(editor, caret) | ||||||
|       val end = |       val end = min(injector.motion.moveCaretToRelativeLineEnd(editor, caret, count - 1, true) + 1, editor.fileSize().toInt()) | ||||||
|         min(injector.motion.moveCaretToRelativeLineEnd(editor, caret, count - 1, true) + 1, editor.fileSize().toInt()) |  | ||||||
|  |  | ||||||
|       if (end == -1) continue |       if (end == -1) continue | ||||||
|  |  | ||||||
|       caretToRange[caret] = TextRange(start, end) to SelectionType.LINE_WISE |       ranges.add(start to end) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return yankRange(editor, context, caretToRange, null) |     val range = getTextRange(ranges, SelectionType.LINE_WISE) ?: return false | ||||||
|   } |     return yankRange(editor, context, range, SelectionType.LINE_WISE, null) | ||||||
|  |  | ||||||
|   @Deprecated("Please use the same method, but with ExecutionContext") |  | ||||||
|   override fun yankRange(editor: VimEditor, range: TextRange?, type: SelectionType, moveCursor: Boolean): Boolean { |  | ||||||
|     val context = injector.executionContextManager.getEditorExecutionContext(editor) |  | ||||||
|     return yankRange(editor, context, range, type, moveCursor) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -177,15 +143,8 @@ open class YankGroupBase : VimYankGroup { | |||||||
|    * @param type   The type of yank |    * @param type   The type of yank | ||||||
|    * @return true if able to yank the range, false if not |    * @return true if able to yank the range, false if not | ||||||
|    */ |    */ | ||||||
|   override fun yankRange( |   override fun yankRange(editor: VimEditor, context: ExecutionContext, range: TextRange?, type: SelectionType, moveCursor: Boolean): Boolean { | ||||||
|     editor: VimEditor, |  | ||||||
|     context: ExecutionContext, |  | ||||||
|     range: TextRange?, |  | ||||||
|     type: SelectionType, |  | ||||||
|     moveCursor: Boolean, |  | ||||||
|   ): Boolean { |  | ||||||
|     range ?: return false |     range ?: return false | ||||||
|     val caretToRange = HashMap<ImmutableVimCaret, Pair<TextRange, SelectionType>>() |  | ||||||
|  |  | ||||||
|     if (type == SelectionType.LINE_WISE) { |     if (type == SelectionType.LINE_WISE) { | ||||||
|       for (i in 0 until range.size()) { |       for (i in 0 until range.size()) { | ||||||
| @@ -205,19 +164,17 @@ open class YankGroupBase : VimYankGroup { | |||||||
|     val startOffsets = HashMap<VimCaret, Int>(editor.nativeCarets().size) |     val startOffsets = HashMap<VimCaret, Int>(editor.nativeCarets().size) | ||||||
|     if (type == SelectionType.BLOCK_WISE) { |     if (type == SelectionType.BLOCK_WISE) { | ||||||
|       startOffsets[editor.primaryCaret()] = range.normalize().startOffset |       startOffsets[editor.primaryCaret()] = range.normalize().startOffset | ||||||
|       caretToRange[editor.primaryCaret()] = range to type |  | ||||||
|     } else { |     } else { | ||||||
|       for ((i, caret) in editor.nativeCarets().withIndex()) { |       for ((i, caret) in editor.nativeCarets().withIndex()) { | ||||||
|         val textRange = TextRange(rangeStartOffsets[i], rangeEndOffsets[i]) |         val textRange = TextRange(rangeStartOffsets[i], rangeEndOffsets[i]) | ||||||
|         startOffsets[caret] = textRange.normalize().startOffset |         startOffsets[caret] = textRange.normalize().startOffset | ||||||
|         caretToRange[caret] = textRange to type |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return if (moveCursor) { |     return if (moveCursor) { | ||||||
|       yankRange(editor, context, caretToRange, startOffsets) |       yankRange(editor, context, range, type, startOffsets) | ||||||
|     } else { |     } else { | ||||||
|       yankRange(editor, context, caretToRange, null) |       yankRange(editor, context, range, type, null) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -417,7 +417,7 @@ | |||||||
|     { |     { | ||||||
|         "keys": "<C-R>", |         "keys": "<C-R>", | ||||||
|         "class": "com.maddyhome.idea.vim.action.change.RedoAction", |         "class": "com.maddyhome.idea.vim.action.change.RedoAction", | ||||||
|         "modes": "N" |         "modes": "NX" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         "keys": "<C-R>", |         "keys": "<C-R>", | ||||||
| @@ -957,7 +957,7 @@ | |||||||
|     { |     { | ||||||
|         "keys": "<Undo>", |         "keys": "<Undo>", | ||||||
|         "class": "com.maddyhome.idea.vim.action.change.UndoAction", |         "class": "com.maddyhome.idea.vim.action.change.UndoAction", | ||||||
|         "modes": "N" |         "modes": "NX" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         "keys": "<Up>", |         "keys": "<Up>", | ||||||
| @@ -1176,8 +1176,8 @@ | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         "keys": "U", |         "keys": "U", | ||||||
|         "class": "com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction", |         "class": "com.maddyhome.idea.vim.action.change.RedoAction", | ||||||
|         "modes": "X" |         "modes": "NX" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         "keys": "V", |         "keys": "V", | ||||||
| @@ -1962,12 +1962,7 @@ | |||||||
|     { |     { | ||||||
|         "keys": "u", |         "keys": "u", | ||||||
|         "class": "com.maddyhome.idea.vim.action.change.UndoAction", |         "class": "com.maddyhome.idea.vim.action.change.UndoAction", | ||||||
|         "modes": "N" |         "modes": "NX" | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "keys": "u", |  | ||||||
|         "class": "com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction", |  | ||||||
|         "modes": "X" |  | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         "keys": "v", |         "keys": "v", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user