1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-05-31 07:34:06 +02:00

Reduce multiple implementations to one

This commit is contained in:
Matt Ellis 2025-02-13 00:13:18 +00:00 committed by Alex Pláte
parent c4f56c40fd
commit 43a2bd98db
5 changed files with 68 additions and 124 deletions
src/main/java/com/maddyhome/idea/vim
vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api

View File

@ -34,7 +34,6 @@ import com.maddyhome.idea.vim.helper.SearchOptions
import com.maddyhome.idea.vim.helper.endOffsetInclusive import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.findWordUnderCursor
import com.maddyhome.idea.vim.helper.inVisualMode import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.userData import com.maddyhome.idea.vim.helper.userData
@ -235,7 +234,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
val text = if (editor.inVisualMode) { val text = if (editor.inVisualMode) {
primaryCaret.selectedText ?: return primaryCaret.selectedText ?: return
} else { } else {
val range = findWordUnderCursor(editor, primaryCaret) ?: return val range = injector.searchHelper.findWordNearestCursor(editor.vim, primaryCaret.vim) ?: return
if (range.startOffset > primaryCaret.offset) return if (range.startOffset > primaryCaret.offset) return
IjVimEditor(editor).getText(range) IjVimEditor(editor).getText(range)
} }
@ -300,7 +299,8 @@ internal class VimMultipleCursorsExtension : VimExtension {
} }
private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? { private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? {
val range = findWordUnderCursor(editor, caret) ?: return null // TODO: I think vim-multiple-cursors uses a text object rather than the star operator
val range = injector.searchHelper.findWordNearestCursor(editor.vim, caret.vim) ?: return null
if (range.startOffset > caret.offset) return null if (range.startOffset > caret.offset) return null
enterVisualMode(editor.vim) enterVisualMode(editor.vim)

View File

@ -10,15 +10,10 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
import com.maddyhome.idea.vim.api.getLineEndOffset
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
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.newapi.IjVimEditor
import it.unimi.dsi.fastutil.ints.IntComparator import it.unimi.dsi.fastutil.ints.IntComparator
import it.unimi.dsi.fastutil.ints.IntIterator import it.unimi.dsi.fastutil.ints.IntIterator
import it.unimi.dsi.fastutil.ints.IntRBTreeSet import it.unimi.dsi.fastutil.ints.IntRBTreeSet
@ -50,64 +45,6 @@ private fun containsUpperCase(pattern: String): Boolean {
return false return false
} }
/**
* Find the word under the cursor or the next word to the right of the cursor on the current line.
*
* @param editor The editor to find the word in
* @param caret The caret to find word under
* @return The text range of the found word or null if there is no word under/after the cursor on the line
*/
fun findWordUnderCursor(editor: Editor, caret: Caret): TextRange? {
val vimEditor = IjVimEditor(editor)
val chars = editor.document.charsSequence
val stop = vimEditor.getLineEndOffset(caret.logicalPosition.line, true)
val pos = caret.offset
// Technically the first condition is covered by the second one, but let it be
if (chars.length == 0 || chars.length <= pos) return null
//if (pos == chars.length() - 1) return new TextRange(chars.length() - 1, chars.length());
var start = pos
val types = arrayOf(
CharacterHelper.CharacterType.KEYWORD,
CharacterHelper.CharacterType.PUNCTUATION
)
for (i in 0..1) {
start = pos
val type = charType(vimEditor, chars[start], false)
if (type == types[i]) {
// Search back for start of word
while (start > 0 && charType(vimEditor, chars[start - 1], false) == types[i]) {
start--
}
} else {
// Search forward for start of word
while (start < stop && charType(vimEditor, chars[start], false) != types[i]) {
start++
}
}
if (start != stop) {
break
}
}
if (start == stop) {
return null
}
// Special case 1 character words because 'findNextWordEnd' returns one to many chars
val end = if (start < stop &&
(start >= chars.length - 1 ||
charType(vimEditor, chars[start + 1], false) != CharacterHelper.CharacterType.KEYWORD)
) {
start + 1
} else {
injector.searchHelper.findNextWordEnd(vimEditor, start, 1, false, false) + 1
}
return TextRange(start, end)
}
fun findMisspelledWords( fun findMisspelledWords(
editor: Editor, editor: Editor,
startOffset: Int, startOffset: Int,

View File

@ -13,7 +13,6 @@ import com.maddyhome.idea.vim.common.Direction
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExException import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.ranges.LineRange import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.Msg import com.maddyhome.idea.vim.helper.Msg
import com.maddyhome.idea.vim.helper.SearchOptions import com.maddyhome.idea.vim.helper.SearchOptions
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
@ -427,7 +426,7 @@ abstract class VimSearchGroupBase : VimSearchGroup {
whole: Boolean, whole: Boolean,
dir: Direction, dir: Direction,
): Int { ): Int {
val range: TextRange = findWordUnderCursor(editor, caret) ?: return -1 val range = injector.searchHelper.findWordNearestCursor(editor, caret) ?: return -1
val start = range.startOffset val start = range.startOffset
val end = range.endOffset val end = range.endOffset
@ -571,62 +570,6 @@ abstract class VimSearchGroupBase : VimSearchGroup {
} }
/**
* Find the word under the cursor or the next word to the right of the cursor on the current line.
*
* @param editor The editor to find the word in
* @param caret The caret to find word under
* @return The text range of the found word or null if there is no word under/after the cursor on the line
*/
private fun findWordUnderCursor(
editor: VimEditor,
caret: ImmutableVimCaret,
): TextRange? {
val stop: Int = editor.getLineEndOffset(caret.getBufferPosition().line, true)
val pos: Int = caret.offset
// Technically the first condition is covered by the second one, but let it be
if (editor.text().isEmpty() || editor.text().length <= pos) return null
//if (pos == chars.length() - 1) return new TextRange(chars.length() - 1, chars.length());
var start = pos
val types = arrayOf(
CharacterHelper.CharacterType.KEYWORD,
CharacterHelper.CharacterType.PUNCTUATION
)
for (i in 0..1) {
start = pos
val type = CharacterHelper.charType(editor, editor.text()[start], false)
if (type === types[i]) {
// Search back for start of word
while (start > 0 && CharacterHelper.charType(editor, editor.text()[start - 1], false) === types[i]) {
start--
}
} else {
// Search forward for start of word
while (start < stop && CharacterHelper.charType(editor, editor.text()[start], false) !== types[i]) {
start++
}
}
if (start != stop) {
break
}
}
if (start == stop) {
return null
}
// Special case 1 character words because 'findNextWordEnd' returns one to many chars
val end: Int = if (start < stop &&
(start >= editor.text().length - 1 ||
CharacterHelper.charType(editor, editor.text()[start + 1], false) !== CharacterHelper.CharacterType.KEYWORD)
) {
start + 1
} else {
injector.searchHelper.findNextWordEnd(editor, start, 1, bigWord = false) + 1
}
return TextRange(start, end)
}
/****************************************************************************/ /****************************************************************************/
/* Substitute related methods */ /* Substitute related methods */
/****************************************************************************/ /****************************************************************************/

View File

@ -195,6 +195,20 @@ interface VimSearchHelper {
ch: Char, ch: Char,
): Int ): Int
/**
* Find the word at or nearest to the current caret offset
*
* Note that this is not a word text object!
*
* This function is used to get the word to search for using the `*`/`#` and `g*`/`g#` operators. It will return:
* * the range of the keyword under the cursor
* * or the first keyword after the cursor on the current line,
* * or the non-blank word under the cursor,
* * or the first non-blank word after the cursor on the current line
* * or null, if none of the above are found
*/
fun findWordNearestCursor(editor: VimEditor, caret: ImmutableVimCaret): TextRange?
/** /**
* Find the range of the word text object at the location of the caret * Find the range of the word text object at the location of the caret
*/ */

View File

@ -94,6 +94,56 @@ abstract class VimSearchHelperBase : VimSearchHelper {
} }
} }
override fun findWordNearestCursor(editor: VimEditor, caret: ImmutableVimCaret): TextRange? {
val chars = editor.text()
val stop = editor.getLineEndOffset(caret.getBufferPosition().line, true)
val pos = caret.offset
if (chars.isEmpty() || chars.length <= pos) return null
var start = pos
val types = arrayOf(
CharacterHelper.CharacterType.KEYWORD,
CharacterHelper.CharacterType.PUNCTUATION
)
for (i in 0..1) {
start = pos
val type = charType(editor, chars[start], false)
if (type == types[i]) {
// Search back for start of word
while (start > 0 && charType(editor, chars[start - 1], false) == types[i]) {
start--
}
} else {
// Search forward for start of word
while (start < stop && charType(editor, chars[start], false) != types[i]) {
start++
}
}
if (start != stop) {
break
}
}
if (start == stop) {
return null
}
// Special case 1 character words because 'findNextWordEnd' returns one to many chars
val end = if (start < stop &&
(start >= chars.length - 1 ||
charType(editor, chars[start + 1], false) != CharacterHelper.CharacterType.KEYWORD)
) {
start + 1
} else {
injector.searchHelper.findNextWordEnd(editor, start, 1, false, false) + 1
}
return TextRange(start, end)
}
override fun findNextWord( override fun findNextWord(
editor: VimEditor, editor: VimEditor,
searchFrom: Int, searchFrom: Int,