mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-10-30 17:17:15 +01:00 
			
		
		
		
	Compare commits
	
		
			20 Commits
		
	
	
		
			0f0a73c139
			...
			customized
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 07659cc64d | |||
| c089bd4bd3 | |||
| b96708fdb8 | |||
| e3bed58a29 | |||
| b8cf11257c | |||
| 755f05791a | |||
| 2d170dd15b | |||
| 943dffd43a | |||
| f17e99dd46 | |||
| aa4caaa722 | |||
| 4380b88cbd | |||
| 55ce038d51 | |||
| deec7eef2e | |||
| 2feffa9ff4 | |||
| f7f663f29a | |||
| badbcd83d6 | |||
| d978901edf | |||
| 08940fdaba | |||
| 3a11fb9bd3 | |||
| fa9bb6adf4 | 
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| * text=auto eol=lf | ||||
							
								
								
									
										1
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
								
							| @@ -6,6 +6,7 @@ | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="4" /> | ||||
|       </value> | ||||
|     </option> | ||||
|     <option name="LINE_SEPARATOR" value="
" /> | ||||
|     <JavaCodeStyleSettings> | ||||
|       <option name="FIELD_NAME_PREFIX" value="my" /> | ||||
|       <option name="STATIC_FIELD_NAME_PREFIX" value="our" /> | ||||
|   | ||||
| @@ -353,8 +353,6 @@ tasks { | ||||
|     val pluginVersion = version | ||||
|     // Don't forget to update plugin.xml | ||||
|     patchPluginXml { | ||||
|         sinceBuild.set("233.11799.30") | ||||
|  | ||||
|         // Get the latest available change notes from the changelog file | ||||
|         changeNotes.set( | ||||
|             provider { | ||||
|   | ||||
| @@ -11,11 +11,12 @@ | ||||
| ideaVersion=2023.3.2 | ||||
| downloadIdeaSources=true | ||||
| instrumentPluginCode=true | ||||
| version=SNAPSHOT | ||||
| version=chylex-24 | ||||
| javaVersion=17 | ||||
| remoteRobotVersion=0.11.21 | ||||
| antlrVersion=4.10.1 | ||||
|  | ||||
| kotlin.incremental.useClasspathSnapshot=false | ||||
|  | ||||
| # Please don't forget to update kotlin version in buildscript section | ||||
| # Also update kotlinxSerializationVersion version | ||||
|   | ||||
| @@ -217,6 +217,8 @@ private object FileTypePatterns { | ||||
|  | ||||
|     return if (fileTypeName in htmlLikeFileTypes) { | ||||
|       this.htmlPatterns | ||||
|     } else if (fileTypeName == "JAVA" || fileExtension == "java") { | ||||
|       this.javaPatterns | ||||
|     } else if (fileTypeName == "Ruby" || fileExtension == "rb") { | ||||
|       this.rubyPatterns | ||||
|     } else if (fileTypeName == "RHTML" || fileExtension == "erb") { | ||||
| @@ -231,7 +233,7 @@ private object FileTypePatterns { | ||||
|     } else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") { | ||||
|       this.cMakePatterns | ||||
|     } else { | ||||
|       return null | ||||
|       this.htmlPatterns | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -242,6 +244,7 @@ private object FileTypePatterns { | ||||
|   ) | ||||
|  | ||||
|   private val htmlPatterns = createHtmlPatterns() | ||||
|   private val javaPatterns = createJavaPatterns() | ||||
|   private val rubyPatterns = createRubyPatterns() | ||||
|   private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns | ||||
|   private val phpPatterns = createPhpPatterns() | ||||
| @@ -271,6 +274,14 @@ private object FileTypePatterns { | ||||
|       ) | ||||
|   } | ||||
|    | ||||
|   private fun createJavaPatterns(): LanguagePatterns { | ||||
|     return ( | ||||
|         LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") + | ||||
|           LanguagePatterns("\\bdo\\b", "\\bwhile\\b") + | ||||
|           LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b") | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   private fun createRubyPatterns(): LanguagePatterns { | ||||
|     // Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim | ||||
|     // We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries. | ||||
|   | ||||
| @@ -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) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -13,6 +13,7 @@ import com.intellij.openapi.editor.Editor | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.api.ExecutionContext | ||||
| import com.maddyhome.idea.vim.api.VimCaret | ||||
| import com.maddyhome.idea.vim.api.VimChangeGroup | ||||
| import com.maddyhome.idea.vim.api.VimEditor | ||||
| import com.maddyhome.idea.vim.api.endsWithNewLine | ||||
| import com.maddyhome.idea.vim.api.getLeadingCharacterOffset | ||||
| @@ -31,7 +32,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa | ||||
| import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing | ||||
| import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction | ||||
| import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret | ||||
| import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore | ||||
| import com.maddyhome.idea.vim.key.OperatorFunction | ||||
| import com.maddyhome.idea.vim.newapi.IjVimCaret | ||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor | ||||
| import com.maddyhome.idea.vim.newapi.ij | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper | ||||
| @@ -80,7 +84,7 @@ internal class VimSurroundExtension : VimExtension { | ||||
|     override val isRepeatable = true | ||||
|  | ||||
|     override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { | ||||
|       setOperatorFunction(Operator()) | ||||
|       setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO | ||||
|       executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) | ||||
|     } | ||||
|   } | ||||
| @@ -101,7 +105,7 @@ internal class VimSurroundExtension : VimExtension { | ||||
|         val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset) | ||||
|         if (lastNonWhiteSpaceOffset != null) { | ||||
|           val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1) | ||||
|           performSurround(pair, range, it) | ||||
|           performSurround(pair, range, it, count = operatorArguments.count1) | ||||
|         } | ||||
| //        it.moveToOffset(lineStartOffset) | ||||
|       } | ||||
| @@ -121,15 +125,13 @@ internal class VimSurroundExtension : VimExtension { | ||||
|  | ||||
|   private class VSurroundHandler : ExtensionHandler { | ||||
|     override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { | ||||
|       val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart | ||||
|       // NB: Operator ignores SelectionType anyway | ||||
|       if (!Operator().apply(editor, context, editor.mode.selectionType)) { | ||||
|       if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) { | ||||
|         return | ||||
|       } | ||||
|       runWriteAction { | ||||
|         // Leave visual mode | ||||
|         executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) | ||||
|         editor.ij.caretModel.moveToOffset(selectionStart) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @@ -150,6 +152,10 @@ internal class VimSurroundExtension : VimExtension { | ||||
|  | ||||
|     companion object { | ||||
|       fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) { | ||||
|         editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) } | ||||
|       } | ||||
|        | ||||
|       fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) { | ||||
|         // Save old register values for carets | ||||
|         val surroundings = editor.sortedCarets() | ||||
|           .map { | ||||
| @@ -257,21 +263,42 @@ internal class VimSurroundExtension : VimExtension { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private class Operator : OperatorFunction { | ||||
|     override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { | ||||
|       val ijEditor = editor.ij | ||||
|       val c = getChar(ijEditor) | ||||
|   private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction { | ||||
|     override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { | ||||
|       val editor = vimEditor.ij | ||||
|       val c = getChar(editor) | ||||
|       if (c.code == 0) return true | ||||
|  | ||||
|       val pair = getOrInputPair(c, ijEditor) ?: return false | ||||
|       // XXX: Will it work with line-wise or block-wise selections? | ||||
|       val range = getSurroundRange(editor.currentCaret()) ?: return false | ||||
|       performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE) | ||||
|       // Jump back to start | ||||
|       executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) | ||||
|       val pair = getOrInputPair(c, editor) ?: return false | ||||
|  | ||||
|       runWriteAction { | ||||
|         val change = VimPlugin.getChange() | ||||
|         if (supportsMultipleCursors) { | ||||
|           editor.runWithEveryCaretAndRestore { | ||||
|             applyOnce(editor, change, pair, count) | ||||
|           } | ||||
|         } | ||||
|         else { | ||||
|           applyOnce(editor, change, pair, count) | ||||
|           // Jump back to start | ||||
|           executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor) | ||||
|         } | ||||
|       } | ||||
|       return true | ||||
|     } | ||||
|      | ||||
|     private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) { | ||||
|       // XXX: Will it work with line-wise or block-wise selections? | ||||
|       val primaryCaret = editor.caretModel.primaryCaret | ||||
|       val range = getSurroundRange(primaryCaret.vim) | ||||
|       if (range != null) { | ||||
|         val start = RepeatedCharSequence.of(pair.first, count) | ||||
|         val end = RepeatedCharSequence.of(pair.second, count) | ||||
|         change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start) | ||||
|         change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end) | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     private fun getSurroundRange(caret: VimCaret): TextRange? { | ||||
|       val editor = caret.editor | ||||
|       val ijEditor = editor.ij | ||||
| @@ -354,15 +381,15 @@ private fun getChar(editor: Editor): Char { | ||||
|   return res | ||||
| } | ||||
|  | ||||
| private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) { | ||||
| private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) { | ||||
|   runWriteAction { | ||||
|     val editor = caret.editor | ||||
|     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 hasNewLine = editor.endsWithNewLine() | ||||
|     val rightSurround = if (tagsOnNewLines) { | ||||
|     val rightSurround = (if (tagsOnNewLines) { | ||||
|       if (isEOF && !hasNewLine) { | ||||
|         "\n" + pair.second | ||||
|       } else { | ||||
| @@ -370,7 +397,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: | ||||
|       } | ||||
|     } else { | ||||
|       pair.second | ||||
|     } | ||||
|     }).let { RepeatedCharSequence.of(it, count) } | ||||
|  | ||||
|     change.insertText(editor, caret, range.startOffset, leftSurround) | ||||
|     change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround) | ||||
|   | ||||
| @@ -78,7 +78,6 @@ import java.math.BigInteger | ||||
| import java.util.* | ||||
| import java.util.function.Consumer | ||||
| import kotlin.math.max | ||||
| import kotlin.math.min | ||||
|  | ||||
| /** | ||||
|  * Provides all the insert/replace related functionality | ||||
| @@ -395,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() { | ||||
|     context: ExecutionContext, | ||||
|     range: TextRange, | ||||
|   ) { | ||||
|     val startPos = editor.offsetToBufferPosition(caret.offset.point) | ||||
|     val startOffset = editor.getLineStartForOffset(range.startOffset) | ||||
|     val endOffset = editor.getLineEndForOffset(range.endOffset) | ||||
|     val ijEditor = (editor as IjVimEditor).editor | ||||
| @@ -419,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() { | ||||
|       } | ||||
|     } | ||||
|     val afterAction = { | ||||
|       val firstLine = editor.offsetToBufferPosition( | ||||
|         min(startOffset.toDouble(), endOffset.toDouble()).toInt() | ||||
|       ).line | ||||
|       val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine) | ||||
|       caret.moveToOffset(newOffset) | ||||
|       caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line)) | ||||
|       restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line) | ||||
|     } | ||||
|     if (project != null) { | ||||
|   | ||||
| @@ -83,7 +83,7 @@ public object IjOptions { | ||||
|   public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false)) | ||||
|   public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true)) | ||||
|   public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100)) | ||||
|   public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true)) | ||||
|   public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true)) | ||||
|   public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true)) | ||||
|   public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true)) | ||||
|   public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = 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.macro.VimMacroBase | ||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor | ||||
| import com.maddyhome.idea.vim.newapi.ij | ||||
|  | ||||
| /** | ||||
|  * Used to handle playback of macros | ||||
| @@ -90,6 +91,9 @@ internal class MacroGroup : VimMacroBase() { | ||||
|         } finally { | ||||
|           keyStack.removeFirst() | ||||
|         } | ||||
|         if (!isInternalMacro) { | ||||
|           MacroAutoImport.run(editor.ij, context.ij) | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (isInternalMacro) { | ||||
|   | ||||
| @@ -192,8 +192,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo | ||||
|    * @param patternOffset   The pattern offset, e.g. `/{pattern}/{offset}` | ||||
|    * @param direction       The direction to search | ||||
|    */ | ||||
|   @TestOnly | ||||
|   public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern, | ||||
|   @Override | ||||
|   public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern, | ||||
|                                  @NotNull String patternOffset, Direction direction) { | ||||
|     setLastUsedPattern(pattern, RE_SEARCH, true); | ||||
|     lastIgnoreSmartCase = false; | ||||
|   | ||||
| @@ -335,7 +335,7 @@ public class EditorHelper { | ||||
|  | ||||
|     final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight); | ||||
|     @NotNull final VimEditor editor1 = new IjVimEditor(editor); | ||||
|     final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1; | ||||
|     final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount(); | ||||
|     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. | ||||
|   | ||||
| @@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper | ||||
|  | ||||
| import com.intellij.codeWithMe.ClientId | ||||
| import com.intellij.openapi.editor.Caret | ||||
| import com.intellij.openapi.editor.CaretState | ||||
| import com.intellij.openapi.editor.Editor | ||||
| import com.intellij.openapi.editor.ex.util.EditorUtil | ||||
| import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx | ||||
| @@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.group.IjOptionConstants | ||||
| import com.maddyhome.idea.vim.newapi.globalIjOptions | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.state.mode.inBlockSelection | ||||
| import java.awt.Component | ||||
| import javax.swing.JComponent | ||||
| import javax.swing.JTable | ||||
| @@ -93,3 +96,41 @@ internal val Caret.vimLine: Int | ||||
|  */ | ||||
| internal val Editor.vimLine: Int | ||||
|   get() = this.caretModel.currentCaret.vimLine | ||||
|  | ||||
| internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) { | ||||
|   val caretModel = this.caretModel | ||||
|   val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets | ||||
|   if (carets == null || carets.size == 1) { | ||||
|     action() | ||||
|   } | ||||
|   else { | ||||
|     var initialDocumentSize = this.document.textLength | ||||
|     var documentSizeDifference = 0 | ||||
|  | ||||
|     val caretOffsets = carets.map { it.selectionStart to it.selectionEnd } | ||||
|     val restoredCarets = mutableListOf<CaretState>() | ||||
|  | ||||
|     caretModel.removeSecondaryCarets() | ||||
|      | ||||
|     for ((selectionStart, selectionEnd) in caretOffsets) { | ||||
|       if (selectionStart == selectionEnd) { | ||||
|         caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference) | ||||
|       } | ||||
|       else { | ||||
|         caretModel.primaryCaret.setSelection( | ||||
|           selectionStart + documentSizeDifference, | ||||
|           selectionEnd + documentSizeDifference | ||||
|         ) | ||||
|       } | ||||
|        | ||||
|       action() | ||||
|       restoredCarets.add(caretModel.caretsAndSelections.single()) | ||||
|  | ||||
|       val documentLength = this.document.textLength | ||||
|       documentSizeDifference += documentLength - initialDocumentSize | ||||
|       initialDocumentSize = documentLength | ||||
|     } | ||||
|  | ||||
|     caretModel.caretsAndSelections = restoredCarets | ||||
|   }  | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.api.normalizeVisualColumn | ||||
| import com.maddyhome.idea.vim.api.options | ||||
| import com.maddyhome.idea.vim.command.CommandFlags | ||||
| import com.maddyhome.idea.vim.state.VimStateMachine | ||||
| import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight | ||||
| import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth | ||||
| import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen | ||||
| @@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre | ||||
| import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen | ||||
| import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.state.VimStateMachine | ||||
| import kotlin.math.max | ||||
| import kotlin.math.min | ||||
| import kotlin.math.roundToInt | ||||
| @@ -56,7 +56,7 @@ internal object ScrollViewHelper { | ||||
|     // that this needs to be replaced as a more or less dumb line for line rewrite. | ||||
|     val topLine = getVisualLineAtTopOfScreen(editor) | ||||
|     val bottomLine = getVisualLineAtBottomOfScreen(editor) | ||||
|     val lastLine = vimEditor.getVisualLineCount() - 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 | ||||
|     val scrollOffset = injector.options(vimEditor).scrolloff | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| package com.maddyhome.idea.vim.helper; | ||||
|  | ||||
| import com.google.common.collect.Lists; | ||||
| import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx; | ||||
| import com.intellij.lang.CodeDocumentationAwareCommenter; | ||||
| import com.intellij.lang.Commenter; | ||||
| import com.intellij.lang.Language; | ||||
| @@ -16,15 +17,15 @@ import com.intellij.lang.LanguageCommenters; | ||||
| import com.intellij.openapi.diagnostic.Logger; | ||||
| import com.intellij.openapi.editor.Caret; | ||||
| import com.intellij.openapi.editor.Editor; | ||||
| import com.intellij.openapi.project.Project; | ||||
| import com.intellij.psi.PsiComment; | ||||
| import com.intellij.psi.PsiElement; | ||||
| import com.intellij.psi.PsiFile; | ||||
| import com.intellij.psi.util.PsiTreeUtil; | ||||
| import com.intellij.spellchecker.SpellCheckerSeveritiesProvider; | ||||
| import com.maddyhome.idea.vim.VimPlugin; | ||||
| import com.maddyhome.idea.vim.api.EngineEditorHelperKt; | ||||
| import com.maddyhome.idea.vim.api.VimEditor; | ||||
| import com.maddyhome.idea.vim.state.mode.Mode; | ||||
| import com.maddyhome.idea.vim.state.VimStateMachine; | ||||
| import com.maddyhome.idea.vim.common.CharacterPosition; | ||||
| import com.maddyhome.idea.vim.common.Direction; | ||||
| import com.maddyhome.idea.vim.common.TextRange; | ||||
| @@ -32,6 +33,12 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret; | ||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor; | ||||
| import com.maddyhome.idea.vim.regexp.CharPointer; | ||||
| import com.maddyhome.idea.vim.regexp.RegExp; | ||||
| import com.maddyhome.idea.vim.state.VimStateMachine; | ||||
| import com.maddyhome.idea.vim.state.mode.Mode; | ||||
| import it.unimi.dsi.fastutil.ints.IntComparator; | ||||
| import it.unimi.dsi.fastutil.ints.IntIterator; | ||||
| import it.unimi.dsi.fastutil.ints.IntRBTreeSet; | ||||
| import it.unimi.dsi.fastutil.ints.IntSortedSet; | ||||
| import kotlin.Pair; | ||||
| import org.jetbrains.annotations.Contract; | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| @@ -1523,6 +1530,42 @@ public class SearchHelper { | ||||
|     return PsiHelper.findMethodEnd(editor, caret.getOffset(), count); | ||||
|   } | ||||
|  | ||||
|   public static int findMisspelledWords(@NotNull Editor editor, | ||||
|                                        int startOffset, | ||||
|                                        int endOffset, | ||||
|                                        int skipCount, | ||||
|                                        IntComparator offsetOrdering) { | ||||
|     Project project = editor.getProject(); | ||||
|     if (project == null) { | ||||
|       return -1; | ||||
|     } | ||||
|  | ||||
|     IntSortedSet offsets = new IntRBTreeSet(offsetOrdering); | ||||
|     DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO, | ||||
|                                            startOffset, endOffset, highlight -> { | ||||
|         if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) { | ||||
|           int offset = highlight.getStartOffset(); | ||||
|           if (offset >= startOffset && offset <= endOffset) { | ||||
|             offsets.add(offset); | ||||
|           } | ||||
|         } | ||||
|         return true; | ||||
|       }); | ||||
|      | ||||
|     if (offsets.isEmpty()) { | ||||
|       return -1; | ||||
|     } | ||||
|      | ||||
|     if (skipCount >= offsets.size()) { | ||||
|       return offsets.lastInt(); | ||||
|     } | ||||
|     else { | ||||
|       IntIterator offsetIterator = offsets.iterator(); | ||||
|       offsetIterator.skip(skipCount); | ||||
|       return offsetIterator.nextInt(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) { | ||||
|     List<String> pairs = options(injector, vimEditor).getMatchpairs(); | ||||
|     StringBuilder res = new StringBuilder(); | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import com.intellij.openapi.command.CommandProcessor | ||||
| import com.intellij.openapi.command.undo.UndoManager | ||||
| import com.intellij.openapi.components.Service | ||||
| import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.api.ExecutionContext | ||||
| import com.maddyhome.idea.vim.api.VimEditor | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| @@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.common.ChangesListener | ||||
| import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor | ||||
| import com.maddyhome.idea.vim.newapi.globalIjOptions | ||||
| import com.maddyhome.idea.vim.newapi.ij | ||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | ||||
| import com.maddyhome.idea.vim.state.mode.inVisualMode | ||||
| import com.maddyhome.idea.vim.undo.UndoRedoBase | ||||
|  | ||||
| /** | ||||
| @@ -39,6 +42,7 @@ internal class UndoRedoHelper : UndoRedoBase() { | ||||
|  | ||||
|       if (injector.globalIjOptions().oldundo) { | ||||
|         SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) } | ||||
|         restoreVisualMode(editor) | ||||
|       } else { | ||||
|         // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo | ||||
|         editor.runWithChangeTracking { | ||||
| @@ -74,6 +78,7 @@ internal class UndoRedoHelper : UndoRedoBase() { | ||||
|     if (undoManager.isRedoAvailable(fileEditor)) { | ||||
|       if (injector.globalIjOptions().oldundo) { | ||||
|         SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) } | ||||
|         restoreVisualMode(editor) | ||||
|       } else { | ||||
|         undoManager.redo(fileEditor) | ||||
|         CommandProcessor.getInstance().runUndoTransparentAction { | ||||
| @@ -131,4 +136,21 @@ internal class UndoRedoHelper : UndoRedoBase() { | ||||
|     val hasChanges: Boolean | ||||
|       get() = changeListener.hasChanged || initialPath != editor.getPath() | ||||
|   } | ||||
|  | ||||
|   private fun restoreVisualMode(editor: VimEditor) { | ||||
|     if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) { | ||||
|       val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor) | ||||
|        | ||||
|       // Visual block selection is restored into multiple carets, so multi-carets that form a block are always | ||||
|       // identified as visual block mode, leading to false positives. | ||||
|       // Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore | ||||
|       // visual block mode. | ||||
|       val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE) | ||||
|         SelectionType.CHARACTER_WISE | ||||
|       else | ||||
|         detectedMode | ||||
|        | ||||
|       VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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" | ||||
|     val instance: VimStandalonePluginUpdateChecker = service() | ||||
|   } | ||||
| } | ||||
| @@ -95,41 +95,43 @@ internal object IdeaSpecifics { | ||||
|       if (VimPlugin.isNotEnabled()) return | ||||
|  | ||||
|       val editor = editor | ||||
|       if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) { | ||||
|         val prevDocumentLength = completionPrevDocumentLength | ||||
|         val prevDocumentOffset = completionPrevDocumentOffset | ||||
|       if (editor != null) { | ||||
|         if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) { | ||||
|           val prevDocumentLength = completionPrevDocumentLength | ||||
|           val prevDocumentOffset = completionPrevDocumentOffset | ||||
|  | ||||
|         if (prevDocumentLength != null && prevDocumentOffset != null) { | ||||
|           val register = VimPlugin.getRegister() | ||||
|           val addedTextLength = editor.document.textLength - prevDocumentLength | ||||
|           val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset) | ||||
|           val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0) | ||||
|           if (prevDocumentLength != null && prevDocumentOffset != null) { | ||||
|             val register = VimPlugin.getRegister() | ||||
|             val addedTextLength = editor.document.textLength - prevDocumentLength | ||||
|             val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset) | ||||
|             val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0) | ||||
|  | ||||
|           register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength))) | ||||
|           repeat(caretShift.coerceAtLeast(0)) { | ||||
|             register.recordKeyStroke(leftArrow) | ||||
|             register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength))) | ||||
|             repeat(caretShift.coerceAtLeast(0)) { | ||||
|               register.recordKeyStroke(leftArrow) | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           this.completionPrevDocumentLength = null | ||||
|           this.completionPrevDocumentOffset = null | ||||
|         } | ||||
|          | ||||
|         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 { | ||||
|           val commandState = it.vim.vimStateMachine | ||||
|         //region Enter insert mode after surround with if | ||||
|         if (surrounderAction == action.javaClass.name && surrounderItems.any { | ||||
|             action.templatePresentation.text.endsWith( | ||||
|               it, | ||||
|             ) | ||||
|           } | ||||
|         ) { | ||||
|           val commandState = editor.vim.vimStateMachine | ||||
|           commandState.mode = Mode.NORMAL() | ||||
|           VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim) | ||||
|           KeyHandler.getInstance().reset(it.vim) | ||||
|           VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim) | ||||
|           KeyHandler.getInstance().reset(editor.vim) | ||||
|         } | ||||
|         //endregion | ||||
|  | ||||
|         injector.scroll.scrollCaretIntoView(editor.vim) | ||||
|       } | ||||
|       //endregion | ||||
|  | ||||
|       this.editor = null | ||||
|     } | ||||
|   | ||||
| @@ -74,7 +74,6 @@ import com.maddyhome.idea.vim.handler.correctorRequester | ||||
| import com.maddyhome.idea.vim.handler.keyCheckRequests | ||||
| import com.maddyhome.idea.vim.helper.GuicursorChangeListener | ||||
| import com.maddyhome.idea.vim.helper.StrictMode | ||||
| import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker | ||||
| import com.maddyhome.idea.vim.helper.exitSelectMode | ||||
| import com.maddyhome.idea.vim.helper.exitVisualMode | ||||
| import com.maddyhome.idea.vim.helper.forceBarCursor | ||||
| @@ -91,6 +90,8 @@ import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents | ||||
| import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add | ||||
| import com.maddyhome.idea.vim.newapi.IjVimEditor | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.state.VimStateMachine | ||||
| import com.maddyhome.idea.vim.state.mode.Mode | ||||
| import com.maddyhome.idea.vim.state.mode.inSelectMode | ||||
| import com.maddyhome.idea.vim.state.mode.mode | ||||
| import com.maddyhome.idea.vim.state.mode.selectionType | ||||
| @@ -304,6 +305,16 @@ internal object VimListenerManager { | ||||
|   class VimFileEditorManagerListener : FileEditorManagerListener { | ||||
|     override fun selectionChanged(event: FileEditorManagerEvent) { | ||||
|       if (VimPlugin.isNotEnabled()) return | ||||
|        | ||||
|       val newEditor = event.newEditor | ||||
|       if (newEditor is TextEditor) { | ||||
|         val editor = newEditor.editor | ||||
|         if (editor.isInsertMode) { | ||||
|           VimStateMachine.getInstance(editor).mode = Mode.NORMAL() | ||||
|           KeyHandler.getInstance().reset(editor.vim) | ||||
|         } | ||||
|       } | ||||
|        | ||||
|       MotionGroup.fileEditorManagerSelectionChangedCallback(event) | ||||
|       FileGroup.fileEditorManagerSelectionChangedCallback(event) | ||||
|       SearchGroup.fileEditorManagerSelectionChangedCallback(event) | ||||
| @@ -368,8 +379,6 @@ internal object VimListenerManager { | ||||
|  | ||||
|         event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused)) | ||||
|       } | ||||
|  | ||||
|       VimStandalonePluginUpdateChecker.instance.pluginUsed() | ||||
|     } | ||||
|  | ||||
|     override fun editorReleased(event: EditorFactoryEvent) { | ||||
|   | ||||
| @@ -15,6 +15,8 @@ import com.maddyhome.idea.vim.api.VimSearchHelperBase | ||||
| import com.maddyhome.idea.vim.common.TextRange | ||||
| import com.maddyhome.idea.vim.helper.SearchHelper | ||||
| import com.maddyhome.idea.vim.helper.SearchOptions | ||||
| import it.unimi.dsi.fastutil.ints.IntComparator | ||||
| import it.unimi.dsi.fastutil.ints.IntComparators | ||||
| import java.util.* | ||||
|  | ||||
| @Service | ||||
| @@ -93,4 +95,26 @@ internal class IjVimSearchHelper : VimSearchHelperBase() { | ||||
|   ): TextRange? { | ||||
|     return SearchHelper.findBlockRange(editor.ij, caret.ij, type, count, isOuter) | ||||
|   } | ||||
|  | ||||
|   override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int { | ||||
|     val startOffset: Int | ||||
|     val endOffset: Int | ||||
|     val skipCount: Int | ||||
|     val offsetOrdering: IntComparator | ||||
|      | ||||
|     if (count < 0) { | ||||
|       startOffset = 0 | ||||
|       endOffset = caret.offset.point - 1 | ||||
|       skipCount = -count - 1 | ||||
|       offsetOrdering = IntComparators.OPPOSITE_COMPARATOR | ||||
|     } | ||||
|     else { | ||||
|       startOffset = caret.offset.point + 1 | ||||
|       endOffset = editor.ij.document.textLength | ||||
|       skipCount = count - 1 | ||||
|       offsetOrdering = IntComparators.NATURAL_COMPARATOR | ||||
|     } | ||||
|  | ||||
|     return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -333,7 +333,7 @@ | ||||
|  * |[m|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction} | ||||
|  * |[p|                   {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction} | ||||
|  * |[p|                   {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction} | ||||
|  * |[s|                   TO BE IMPLEMENTED | ||||
|  * |[s|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction} | ||||
|  * |[z|                   TO BE IMPLEMENTED | ||||
|  * |[{|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction} | ||||
|  * |]_CTRL-D|             TO BE IMPLEMENTED | ||||
| @@ -358,7 +358,7 @@ | ||||
|  * |]m|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction} | ||||
|  * |]p|                   {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction} | ||||
|  * |]p|                   {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction} | ||||
|  * |]s|                   TO BE IMPLEMENTED | ||||
|  * |]s|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction} | ||||
|  * |]z|                   TO BE IMPLEMENTED | ||||
|  * |]}|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction} | ||||
|  * | ||||
|   | ||||
| @@ -94,6 +94,8 @@ | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction" mappingModes="NXO" keys="[m"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextEndAction" mappingModes="NXO" keys="]M"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction" mappingModes="NXO" keys="]m"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction" mappingModes="NXO" keys="[s"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction" mappingModes="NXO" keys="]s"/> | ||||
|     <!-- Text Objects --> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterWordAction" mappingModes="XO" keys="aw"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterBigWordAction" mappingModes="XO" keys="aW"/> | ||||
| @@ -226,12 +228,12 @@ | ||||
|  | ||||
|     <!-- Change --> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerMotionAction" mappingModes="N" keys="gu"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/> | ||||
| <!--    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/>--> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleCharacterAction" mappingModes="N" keys="~"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleMotionAction" mappingModes="N" keys="g~"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleVisualAction" mappingModes="X" keys="~"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperMotionAction" mappingModes="N" keys="gU"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/> | ||||
| <!--    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/>--> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction" mappingModes="N" keys="r"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharactersAction" mappingModes="N" keys="s"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeEndOfLineAction" mappingModes="N" keys="C"/> | ||||
| @@ -329,8 +331,8 @@ | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="N" keys="«C-R»"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="N"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="NX" keys="U,«C-R»"/> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="NX"/> | ||||
|  | ||||
|     <!-- Keys --> | ||||
|     <vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/> | ||||
|   | ||||
| @@ -1,12 +1,4 @@ | ||||
| <!-- | ||||
|   ~ 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"> | ||||
| <idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude"> | ||||
|   <name>IdeaVim</name> | ||||
|   <id>IdeaVIM</id> | ||||
|   <description><![CDATA[ | ||||
| @@ -21,13 +13,13 @@ | ||||
|         <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li> | ||||
|       </ul> | ||||
|     ]]></description> | ||||
|   <version>SNAPSHOT</version> | ||||
|   <version>chylex</version> | ||||
|   <vendor>JetBrains</vendor> | ||||
|  | ||||
|   <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version --> | ||||
|   <!-- Check for [Version Update] tag in YouTrack as well --> | ||||
|   <!-- Also, please update the value in build.gradle.kts file--> | ||||
|   <idea-version since-build="233.11799.30"/> | ||||
|   <idea-version since-build="232"/> | ||||
|  | ||||
|   <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) --> | ||||
|   <depends>com.intellij.modules.platform</depends> | ||||
| @@ -169,5 +161,6 @@ | ||||
|     </group> | ||||
|  | ||||
|     <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/> | ||||
|     <action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" /> | ||||
|   </actions> | ||||
| </idea-plugin> | ||||
|   | ||||
| @@ -102,7 +102,7 @@ import kotlin.test.assertTrue | ||||
|  * This is done as we have no mechanism to guarantee compatibility as we update this test case. | ||||
|  * Feel free to copy this class into your plugin, or copy just needed functions. | ||||
|  */ | ||||
| @RunInEdt(writeIntent = true) | ||||
| @RunInEdt | ||||
| @ApiStatus.Internal | ||||
| abstract class VimTestCase { | ||||
|   protected lateinit var fixture: CodeInsightTestFixture | ||||
| @@ -448,6 +448,11 @@ abstract class VimTestCase { | ||||
|     return NeovimTesting.getMark(char) | ||||
|   } | ||||
|  | ||||
|   protected fun assertRegister(char: Char, expected: String?) { | ||||
|     val actual = injector.registerGroup.getRegister(char)?.keys?.let(injector.parser::toKeyNotation) | ||||
|     assertEquals(expected, actual, "Wrong register contents") | ||||
|   } | ||||
|    | ||||
|   protected fun assertState(modeAfter: Mode) { | ||||
|     assertMode(modeAfter) | ||||
|     assertCaretsVisualAttributes() | ||||
|   | ||||
| @@ -47,10 +47,7 @@ class MacroActionTest : VimTestCase() { | ||||
|     val editor = typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "on<caret>e two three\n") | ||||
|     val commandState = editor.vim.vimStateMachine | ||||
|     kotlin.test.assertFalse(commandState.isRecording) | ||||
|     val registerGroup = VimPlugin.getRegister() | ||||
|     val register = registerGroup.getRegister('a') | ||||
|     assertNotNull<Any>(register) | ||||
|     kotlin.test.assertEquals("3l", register.text) | ||||
|     assertRegister('a', "3l") | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -58,9 +55,7 @@ class MacroActionTest : VimTestCase() { | ||||
|     configureByText("") | ||||
|     enterCommand("imap pp hello") | ||||
|     typeText(injector.parser.parseKeys("qa" + "i" + "pp<Esc>" + "q")) | ||||
|     val register = VimPlugin.getRegister().getRegister('a') | ||||
|     assertNotNull<Any>(register) | ||||
|     kotlin.test.assertEquals("ipp<Esc>", injector.parser.toKeyNotation(register.keys)) | ||||
|     assertRegister('a', "ipp<Esc>") | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -68,7 +63,7 @@ class MacroActionTest : VimTestCase() { | ||||
|     typeTextInFile(injector.parser.parseKeys("qa" + "i" + "<C-K>OK<Esc>" + "q"), "") | ||||
|     val register = VimPlugin.getRegister().getRegister('a') | ||||
|     assertNotNull<Any>(register) | ||||
|     kotlin.test.assertEquals("i<C-K>OK<Esc>", injector.parser.toKeyNotation(register.keys)) | ||||
|     assertRegister('a', "i<C-K>OK<Esc>") | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
| @@ -141,8 +136,8 @@ class MacroActionTest : VimTestCase() { | ||||
|     assertState("4\n5\n") | ||||
|   } | ||||
|  | ||||
|   // Broken, see the resulting text | ||||
|   fun `ignore test macro with macro`() { | ||||
|   @Test | ||||
|   fun `test macro with macro`() { | ||||
|     val content = """ | ||||
|             Lorem Ipsum | ||||
|  | ||||
| @@ -152,16 +147,55 @@ class MacroActionTest : VimTestCase() { | ||||
|             Cras id tellus in ex imperdiet egestas. | ||||
|     """.trimIndent() | ||||
|     configureByText(content) | ||||
|     typeText(injector.parser.parseKeys("qa" + "l" + "q" + "qb" + "10@a" + "q" + "2@b")) | ||||
|     typeText( | ||||
|       injector.parser.parseKeys( | ||||
|         "qa" + "l" + "q" + | ||||
|           "qb" + "6@a" + "q" + | ||||
|           "^" + "3@b" | ||||
|       ) | ||||
|     ) | ||||
|  | ||||
|     val startOffset = content.rangeOf("rocks").startOffset | ||||
|     assertRegister('b', "6@a") | ||||
|     assertState(""" | ||||
|             Lorem Ipsum | ||||
|  | ||||
|     waitAndAssert { | ||||
|       println(fixture.editor.caretModel.offset) | ||||
|       println(startOffset) | ||||
|       println() | ||||
|       startOffset == fixture.editor.caretModel.offset | ||||
|     } | ||||
|             Lorem ipsum dolor ${c}sit amet, | ||||
|             consectetur adipiscing elit | ||||
|             Sed in orci mauris. | ||||
|             Cras id tellus in ex imperdiet egestas. | ||||
|     """.trimIndent()) | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   fun `test macro with macro with macro`() { | ||||
|     val content = """ | ||||
|             Lorem Ipsum | ||||
|  | ||||
|             ${c}Lorem ipsum dolor sit amet, | ||||
|             consectetur adipiscing elit | ||||
|             Sed in orci mauris. | ||||
|             Cras id tellus in ex imperdiet egestas. | ||||
|     """.trimIndent() | ||||
|     configureByText(content) | ||||
|     typeText( | ||||
|       injector.parser.parseKeys( | ||||
|         "qa" + "l" + "q" + | ||||
|           "qb" + "3@a" + "q" + | ||||
|           "qc" + "2@b" + "q" + | ||||
|           "^" + "3@c" | ||||
|       ) | ||||
|     ) | ||||
|  | ||||
|     assertRegister('b', "3@a") | ||||
|     assertRegister('c', "2@b") | ||||
|     assertState(""" | ||||
|             Lorem Ipsum | ||||
|  | ||||
|             Lorem ipsum dolor ${c}sit amet, | ||||
|             consectetur adipiscing elit | ||||
|             Sed in orci mauris. | ||||
|             Cras id tellus in ex imperdiet egestas. | ||||
|     """.trimIndent()) | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   | ||||
| @@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn | ||||
|  | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.state.mode.Mode | ||||
| import com.maddyhome.idea.vim.common.Direction | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.state.mode.Mode | ||||
| import org.jetbrains.plugins.ideavim.SkipNeovimReason | ||||
| import org.jetbrains.plugins.ideavim.TestWithoutNeovim | ||||
| import org.jetbrains.plugins.ideavim.VimTestCase | ||||
| @@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() { | ||||
|  | ||||
|   private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) { | ||||
|     configureByText(before) | ||||
|     VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS) | ||||
|     VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS) | ||||
|     typeText(keys) | ||||
|     assertState(after) | ||||
|     assertState(Mode.NORMAL()) | ||||
|   | ||||
| @@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn | ||||
|  | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.state.mode.Mode | ||||
| import com.maddyhome.idea.vim.common.Direction | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.state.mode.Mode | ||||
| import org.jetbrains.plugins.ideavim.SkipNeovimReason | ||||
| import org.jetbrains.plugins.ideavim.TestWithoutNeovim | ||||
| import org.jetbrains.plugins.ideavim.VimTestCase | ||||
| @@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() { | ||||
|  | ||||
|   private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) { | ||||
|     configureByText(before) | ||||
|     VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS) | ||||
|     VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS) | ||||
|     typeText(keys) | ||||
|     assertState(after) | ||||
|     assertState(Mode.NORMAL()) | ||||
|   | ||||
| @@ -11,9 +11,10 @@ import com.intellij.idea.TestFor | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.common.Direction | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.state.mode.Mode | ||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | ||||
| import com.maddyhome.idea.vim.common.Direction | ||||
| import org.jetbrains.plugins.ideavim.SkipNeovimReason | ||||
| import org.jetbrains.plugins.ideavim.TestWithoutNeovim | ||||
| import org.jetbrains.plugins.ideavim.VimTestCase | ||||
| @@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() { | ||||
|   @Test | ||||
|   fun testWithoutSpaces() { | ||||
|     configureByText("test<caret>test") | ||||
|     VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS) | ||||
|     VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS) | ||||
|     typeText(injector.parser.parseKeys("gn")) | ||||
|     assertOffset(7) | ||||
|     assertSelection("test") | ||||
|   | ||||
| @@ -11,9 +11,10 @@ import com.intellij.idea.TestFor | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.common.Direction | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import com.maddyhome.idea.vim.state.mode.Mode | ||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | ||||
| import com.maddyhome.idea.vim.common.Direction | ||||
| import org.jetbrains.plugins.ideavim.SkipNeovimReason | ||||
| import org.jetbrains.plugins.ideavim.TestWithoutNeovim | ||||
| import org.jetbrains.plugins.ideavim.VimTestCase | ||||
| @@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() { | ||||
|   @Test | ||||
|   fun testWithoutSpaces() { | ||||
|     configureByText("tes<caret>ttest") | ||||
|     VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS) | ||||
|     VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS) | ||||
|     typeText(injector.parser.parseKeys("gN")) | ||||
|     assertOffset(0) | ||||
|     assertSelection("test") | ||||
|   | ||||
| @@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action.motion.search | ||||
|  | ||||
| import com.maddyhome.idea.vim.VimPlugin | ||||
| import com.maddyhome.idea.vim.common.Direction | ||||
| import com.maddyhome.idea.vim.newapi.vim | ||||
| import org.jetbrains.plugins.ideavim.SkipNeovimReason | ||||
| import org.jetbrains.plugins.ideavim.TestWithoutNeovim | ||||
| import org.jetbrains.plugins.ideavim.VimTestCase | ||||
| @@ -167,7 +168,7 @@ class SearchAgainPreviousActionTest : VimTestCase() { | ||||
|  | ||||
|   private fun doTestWithSearch(keys: String, before: String, after: String) { | ||||
|     doTest(keys, before, after) { | ||||
|       VimPlugin.getSearch().setLastSearchState(it, "all", "", Direction.FORWARDS) | ||||
|       VimPlugin.getSearch().setLastSearchState(it.vim, "all", "", Direction.FORWARDS) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -102,8 +102,9 @@ public class KeyHandler { | ||||
|     // If this is a "regular" character keystroke, get the character | ||||
|     val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar | ||||
|  | ||||
|     // We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything. | ||||
|     var shouldRecord = handleKeyRecursionCount == 0 && editorState.isRecording | ||||
|     // We only record unmapped keystrokes. | ||||
|     // If we've recursed to handle mapping, or executing a macro, don't record anything. | ||||
|     var shouldRecord = handleKeyRecursionCount == 0 && editorState.isRecording && !injector.macro.isExecutingMacro | ||||
|     handleKeyRecursionCount++ | ||||
|     try { | ||||
|       LOG.trace("Start key processing...") | ||||
|   | ||||
| @@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command | ||||
| import com.maddyhome.idea.vim.command.OperatorArguments | ||||
| import com.maddyhome.idea.vim.handler.VimActionHandler | ||||
|  | ||||
| @CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL]) | ||||
| @CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL]) | ||||
| public class RedoAction : VimActionHandler.SingleExecution() { | ||||
|   override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.VimActionHandler | ||||
| import java.awt.event.KeyEvent | ||||
| import javax.swing.KeyStroke | ||||
|  | ||||
| @CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL]) | ||||
| @CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL]) | ||||
| public class UndoAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction { | ||||
|   override val keyStrokesSet: Set<List<KeyStroke>> = setOf( | ||||
|     injector.parser.parseKeys("u"), | ||||
|   | ||||
| @@ -8,7 +8,6 @@ | ||||
| package com.maddyhome.idea.vim.action.change.change | ||||
|  | ||||
| import com.intellij.vim.annotations.CommandOrMotion | ||||
| import com.intellij.vim.annotations.Mode | ||||
| import com.maddyhome.idea.vim.api.ExecutionContext | ||||
| import com.maddyhome.idea.vim.api.VimCaret | ||||
| import com.maddyhome.idea.vim.api.VimEditor | ||||
| @@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper | ||||
| /** | ||||
|  * @author vlan | ||||
|  */ | ||||
| @CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL]) | ||||
| @CommandOrMotion(keys = [], modes = []) | ||||
| public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() { | ||||
|   override val type: Command.Type = Command.Type.CHANGE | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,6 @@ | ||||
| package com.maddyhome.idea.vim.action.change.change | ||||
|  | ||||
| import com.intellij.vim.annotations.CommandOrMotion | ||||
| import com.intellij.vim.annotations.Mode | ||||
| import com.maddyhome.idea.vim.api.ExecutionContext | ||||
| import com.maddyhome.idea.vim.api.VimCaret | ||||
| import com.maddyhome.idea.vim.api.VimEditor | ||||
| @@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper | ||||
| /** | ||||
|  * @author vlan | ||||
|  */ | ||||
| @CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL]) | ||||
| @CommandOrMotion(keys = [], modes = []) | ||||
| public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() { | ||||
|   override val type: Command.Type = Command.Type.CHANGE | ||||
|  | ||||
|   | ||||
| @@ -30,6 +30,7 @@ public class FilterVisualLinesAction : VimActionHandler.SingleExecution() { | ||||
|   override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE) | ||||
|  | ||||
|   override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { | ||||
|     injector.markService.setVisualSelectionMarks(editor) | ||||
|     injector.processGroup.startFilterCommand(editor, context, cmd) | ||||
|     editor.exitVisualMode() | ||||
|     return true | ||||
|   | ||||
| @@ -82,6 +82,13 @@ public sealed class TillCharacterMotion( | ||||
|       ) | ||||
|     } | ||||
|     injector.motion.setLastFTCmd(tillCharacterMotionType, argument.character) | ||||
|      | ||||
|     val offset = if (!finishBeforeCharacter) "" | ||||
|     else if (direction == Direction.FORWARDS) "s-1" | ||||
|     else "s+1" | ||||
|      | ||||
|     injector.searchGroup.setLastSearchState(editor, argument.character.let { if (it == '.') "\\." else it.toString() }, offset, direction) | ||||
|      | ||||
|     return res.toMotionOrError() | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,58 @@ | ||||
| /* | ||||
|  * 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.action.motion.text | ||||
|  | ||||
| import com.intellij.vim.annotations.CommandOrMotion | ||||
| import com.intellij.vim.annotations.Mode | ||||
| import com.maddyhome.idea.vim.api.ExecutionContext | ||||
| import com.maddyhome.idea.vim.api.ImmutableVimCaret | ||||
| import com.maddyhome.idea.vim.api.VimEditor | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.command.Argument | ||||
| import com.maddyhome.idea.vim.command.CommandFlags | ||||
| import com.maddyhome.idea.vim.command.MotionType | ||||
| import com.maddyhome.idea.vim.command.OperatorArguments | ||||
| import com.maddyhome.idea.vim.handler.Motion | ||||
| import com.maddyhome.idea.vim.handler.MotionActionHandler | ||||
| import com.maddyhome.idea.vim.handler.toMotionOrError | ||||
| import com.maddyhome.idea.vim.helper.enumSetOf | ||||
| import java.util.* | ||||
|  | ||||
| @CommandOrMotion(keys = ["]s"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING]) | ||||
| public class MotionMisspelledWordNextAction : MotionActionHandler.ForEachCaret() { | ||||
|   override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_JUMP) | ||||
|  | ||||
|   override fun getOffset( | ||||
|     editor: VimEditor, | ||||
|     caret: ImmutableVimCaret, | ||||
|     context: ExecutionContext, | ||||
|     argument: Argument?, | ||||
|     operatorArguments: OperatorArguments, | ||||
|   ): Motion { | ||||
|     return injector.searchHelper.findMisspelledWord(editor, caret, operatorArguments.count1).toMotionOrError() | ||||
|   } | ||||
|  | ||||
|   override val motionType: MotionType = MotionType.EXCLUSIVE | ||||
| } | ||||
|  | ||||
| @CommandOrMotion(keys = ["[s"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING]) | ||||
| public class MotionMisspelledWordPreviousAction : MotionActionHandler.ForEachCaret() { | ||||
|   override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_JUMP) | ||||
|  | ||||
|   override fun getOffset( | ||||
|     editor: VimEditor, | ||||
|     caret: ImmutableVimCaret, | ||||
|     context: ExecutionContext, | ||||
|     argument: Argument?, | ||||
|     operatorArguments: OperatorArguments, | ||||
|   ): Motion { | ||||
|     return injector.searchHelper.findMisspelledWord(editor, caret, -operatorArguments.count1).toMotionOrError() | ||||
|   } | ||||
|  | ||||
|   override val motionType: MotionType = MotionType.EXCLUSIVE | ||||
| } | ||||
| @@ -144,7 +144,7 @@ public interface VimChangeGroup { | ||||
|     operatorArguments: OperatorArguments, | ||||
|   ) | ||||
|  | ||||
|   public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret | ||||
|   public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret | ||||
|  | ||||
|   public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret | ||||
|  | ||||
|   | ||||
| @@ -203,7 +203,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup { | ||||
|    * @param caret  The caret to start insertion in | ||||
|    * @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 { | ||||
|     (editor as MutableVimEditor).insertText(Offset(offset), str) | ||||
|     val newCaret = caret.moveToInlayAwareOffset(offset + str.length) | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import com.maddyhome.idea.vim.vimscript.model.VimLContext | ||||
| public interface VimSearchGroup { | ||||
|   public var lastSearchPattern: String? | ||||
|   public var lastSubstitutePattern: String? | ||||
|   public fun setLastSearchState(editor: VimEditor, pattern: String, patternOffset: String, direction: Direction?) | ||||
|   public fun findUnderCaret(editor: VimEditor): TextRange? | ||||
|   public fun searchBackward(editor: VimEditor, offset: Int, count: Int): TextRange? | ||||
|   public fun getNextSearchRange(editor: VimEditor, count: Int, forwards: Boolean): TextRange? | ||||
|   | ||||
| @@ -197,4 +197,10 @@ public interface VimSearchHelper { | ||||
|     count: Int, | ||||
|     isOuter: Boolean, | ||||
|   ): TextRange? | ||||
|  | ||||
|   public fun findMisspelledWord( | ||||
|     editor: VimEditor, | ||||
|     caret: ImmutableVimCaret, | ||||
|     count: Int, | ||||
|   ): Int | ||||
| } | ||||
|   | ||||
| @@ -255,7 +255,12 @@ public class ToActionMappingInfo( | ||||
|     LOG.debug("Executing 'ToAction' mapping...") | ||||
|     val editorDataContext = injector.executionContextManager.onEditor(editor, context) | ||||
|     val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext) | ||||
|     injector.actionExecutor.executeAction(action, dataContext) | ||||
|  | ||||
|     val commandBuilder = editor.vimStateMachine.commandBuilder | ||||
|     for (i in 0 until commandBuilder.count.coerceAtLeast(1)) { | ||||
|       injector.actionExecutor.executeAction(action, dataContext) | ||||
|     } | ||||
|     commandBuilder.resetCount() | ||||
|   } | ||||
|  | ||||
|   public companion object { | ||||
|   | ||||
| @@ -19,7 +19,6 @@ import com.maddyhome.idea.vim.api.VimMarkService | ||||
| import com.maddyhome.idea.vim.api.getText | ||||
| import com.maddyhome.idea.vim.api.injector | ||||
| import com.maddyhome.idea.vim.command.OperatorArguments | ||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | ||||
| import com.maddyhome.idea.vim.common.TextRange | ||||
| import com.maddyhome.idea.vim.ex.ExException | ||||
| import com.maddyhome.idea.vim.ex.InvalidRangeException | ||||
| @@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.Msg | ||||
| import com.maddyhome.idea.vim.mark.Mark | ||||
| import com.maddyhome.idea.vim.mark.VimMark | ||||
| import com.maddyhome.idea.vim.put.PutData | ||||
| import com.maddyhome.idea.vim.state.mode.SelectionType | ||||
| import com.maddyhome.idea.vim.vimscript.model.ExecutionResult | ||||
| import kotlin.math.min | ||||
|  | ||||
| @@ -49,23 +49,14 @@ public data class MoveTextCommand(val ranges: Ranges, val argument: String) : Co | ||||
|     val caretPosition = caret.getBufferPosition() | ||||
|  | ||||
|     val goToLineCommand = injector.vimscriptParser.parseCommand(argument) ?: throw ExException("E16: Invalid range") | ||||
|  | ||||
|     val range = getTextRange(editor, caret, false) | ||||
|  | ||||
|     /* | ||||
|     FIXME: see VIM-2884. It's absolutely not the best way to resolve this bug | ||||
|      */ | ||||
|     caret.moveToOffset(range.startOffset) | ||||
|  | ||||
|     val lineRange = getLineRange(editor, caret) | ||||
|  | ||||
|     val line = min(editor.fileSize().toInt(), normalizeLine(editor, caret, goToLineCommand, lineRange)) | ||||
|     val linesMoved = lineRange.endLine - lineRange.startLine + 1 | ||||
|     if (line < -1 || line + linesMoved >= editor.lineCount()) { | ||||
|       caret.moveToBufferPosition(caretPosition) | ||||
|       throw ExException("E16: Invalid range") | ||||
|     } | ||||
|     val range = getTextRange(editor, caret, false) | ||||
|     val shift = line + 1 - editor.offsetToBufferPosition(range.startOffset).line | ||||
|  | ||||
|     val text = editor.getText(range) | ||||
|  | ||||
|     val localMarks = injector.markService.getAllLocalMarks(caret) | ||||
|       .filter { range.contains(it.offset(editor)) } | ||||
|       .filter { it.key != VimMarkService.SELECTION_START_MARK && it.key != VimMarkService.SELECTION_END_MARK } | ||||
| @@ -78,33 +69,24 @@ public data class MoveTextCommand(val ranges: Ranges, val argument: String) : Co | ||||
|     val selectionStartOffset = lastSelectionInfo.start?.let { editor.bufferPositionToOffset(it) } | ||||
|     val selectionEndOffset = lastSelectionInfo.end?.let { editor.bufferPositionToOffset(it) } | ||||
|  | ||||
|     val text = editor.getText(range) | ||||
|     val textData = PutData.TextData(text, SelectionType.LINE_WISE, emptyList(), null) | ||||
|  | ||||
|     val dropNewLineInEnd = (line + linesMoved == editor.lineCount() - 1 && text.last() == '\n') || | ||||
|       (lineRange.endLine == editor.lineCount() - 1) | ||||
|  | ||||
|     editor.deleteString(range) | ||||
|     val putData = if (line == -1) { | ||||
|       caret.moveToOffset(0) | ||||
|       PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false) | ||||
|     } else { | ||||
|       PutData(textData, null, 1, insertTextBeforeCaret = false, rawIndent = true, caretAfterInsertedText = false, putToLine = line) | ||||
|     } | ||||
|     injector.put.putTextForCaret(editor, caret, context, putData) | ||||
|  | ||||
|     if (dropNewLineInEnd) { | ||||
|       assert(editor.text().last() == '\n') | ||||
|       editor.deleteString(TextRange(editor.text().length - 1, editor.text().length)) | ||||
|     } | ||||
|     val textData = PutData.TextData(text, SelectionType.LINE_WISE, emptyList(), null) | ||||
|     val putData = PutData( | ||||
|       textData, | ||||
|       null, | ||||
|       1, | ||||
|       insertTextBeforeCaret = false, | ||||
|       rawIndent = true, | ||||
|       caretAfterInsertedText = false, | ||||
|       putToLine = line | ||||
|     ) | ||||
|     injector.put.putTextForCaret(editor, caret, context, putData) | ||||
|  | ||||
|     globalMarks.forEach { shiftGlobalMark(editor, it, shift) } | ||||
|     localMarks.forEach { shiftLocalMark(caret, it, shift) } | ||||
|     shiftSelectionInfo(caret, selectionStartOffset, selectionEndOffset, lastSelectionInfo, shift, range) | ||||
|  | ||||
|     val newCaretPosition = shiftBufferPosition(caretPosition, shift) | ||||
|     caret.moveToBufferPosition(newCaretPosition) | ||||
|  | ||||
|     return ExecutionResult.Success | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user