From 120d889dfbb64918387a166d817025c7d157a075 Mon Sep 17 00:00:00 2001 From: chylex Date: Thu, 25 Jan 2024 00:34:36 +0100 Subject: [PATCH] Implement motions to go to next/previous misspelled word --- .../idea/vim/helper/SearchHelper.java | 51 ++++++++++++++-- .../idea/vim/newapi/IjVimSearchHelper.kt | 24 ++++++++ .../com/maddyhome/idea/vim/package-info.java | 4 +- .../motion/text/MotionMisspelledWord.kt | 58 +++++++++++++++++++ .../maddyhome/idea/vim/api/VimSearchHelper.kt | 6 ++ 5 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/text/MotionMisspelledWord.kt diff --git a/src/main/java/com/maddyhome/idea/vim/helper/SearchHelper.java b/src/main/java/com/maddyhome/idea/vim/helper/SearchHelper.java index 651bb12fbb..3f984ffb00 100644 --- a/src/main/java/com/maddyhome/idea/vim/helper/SearchHelper.java +++ b/src/main/java/com/maddyhome/idea/vim/helper/SearchHelper.java @@ -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,22 +17,28 @@ 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.regexp.*; -import com.maddyhome.idea.vim.regexp.match.VimMatchResult; -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; import com.maddyhome.idea.vim.newapi.IjVimCaret; import com.maddyhome.idea.vim.newapi.IjVimEditor; +import com.maddyhome.idea.vim.regexp.*; +import com.maddyhome.idea.vim.regexp.match.VimMatchResult; +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; @@ -1573,6 +1580,42 @@ public static int findMethodEnd(@NotNull Editor editor, @NotNull Caret caret, in 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 pairs = options(injector, vimEditor).getMatchpairs(); StringBuilder res = new StringBuilder(); diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchHelper.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchHelper.kt index 3cd3a9fe13..9235a8c6a5 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchHelper.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchHelper.kt @@ -29,6 +29,8 @@ import com.maddyhome.idea.vim.helper.checkInString import com.maddyhome.idea.vim.helper.fileSize import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance import com.maddyhome.idea.vim.state.mode.Mode.VISUAL +import it.unimi.dsi.fastutil.ints.IntComparator +import it.unimi.dsi.fastutil.ints.IntComparators import java.util.* import java.util.function.Function import java.util.regex.Pattern @@ -689,4 +691,26 @@ internal class IjVimSearchHelper : VimSearchHelperBase() { // End offset exclusive return TextRange(bstart, bend + 1) } + + 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) + } } diff --git a/src/main/java/com/maddyhome/idea/vim/package-info.java b/src/main/java/com/maddyhome/idea/vim/package-info.java index 513e286bcc..9849245c17 100644 --- a/src/main/java/com/maddyhome/idea/vim/package-info.java +++ b/src/main/java/com/maddyhome/idea/vim/package-info.java @@ -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} * diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/text/MotionMisspelledWord.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/text/MotionMisspelledWord.kt new file mode 100644 index 0000000000..18c8c341f5 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/text/MotionMisspelledWord.kt @@ -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 = 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 = 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 +} diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchHelper.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchHelper.kt index b64d036957..94748b42c7 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchHelper.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchHelper.kt @@ -239,4 +239,10 @@ public interface VimSearchHelper { count: Int, isOuter: Boolean, ): TextRange? + + public fun findMisspelledWord( + editor: VimEditor, + caret: ImmutableVimCaret, + count: Int, + ): Int }