mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-03-06 00:32:52 +01:00
Fixed review issues
This commit is contained in:
parent
77bd800d95
commit
d46cab6fc8
src/com/maddyhome/idea/vim/extension/multiplecursors
test/org/jetbrains/plugins/ideavim/extension/multiplecursors
@ -6,7 +6,8 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ScrollType
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.command.CommandState.*
|
||||
import com.maddyhome.idea.vim.command.CommandState.Mode
|
||||
import com.maddyhome.idea.vim.command.CommandState.SubMode
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
|
||||
@ -17,143 +18,121 @@ import com.maddyhome.idea.vim.group.MotionGroup
|
||||
import com.maddyhome.idea.vim.helper.CaretData
|
||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||
|
||||
private const val NEXT_WHOLE_OCCURRENCE = "<Plug>NextWholeOccurrence"
|
||||
private const val NEXT_OCCURRENCE = "<Plug>NextOccurrence"
|
||||
private const val NOT_WHOLE_OCCURRENCE = "<Plug>NotWholeOccurrence"
|
||||
private const val SKIP_OCCURRENCE = "<Plug>SkipOccurrence"
|
||||
private const val REMOVE_OCCURRENCE = "<Plug>RemoveOccurrence"
|
||||
private const val ALL_WHOLE_OCCURRENCES = "<Plug>AllWholeOccurrences"
|
||||
private const val ALL_OCCURRENCES = "<Plug>AllOccurrences"
|
||||
private const val NOT_WHOLE_ALL_OCCURRENCES = "<Plug>NotWholeAllOccurrences"
|
||||
|
||||
private class State private constructor() {
|
||||
var nextOffset = -1
|
||||
lateinit var firstRange: TextRange
|
||||
|
||||
private object Holder {
|
||||
val INSTANCE = State()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val instance: State by lazy { Holder.INSTANCE }
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectRange(editor: Editor, caret: Caret, startOffset: Int, endOffset: Int) {
|
||||
CaretData.setVisualStart(caret, startOffset)
|
||||
VimPlugin.getMotion().updateSelection(editor, caret, endOffset)
|
||||
editor.scrollingModel.scrollToCaret(ScrollType.CENTER)
|
||||
}
|
||||
|
||||
private fun handleFirstSelection(editor: Editor, whole: Boolean) {
|
||||
val commandState = CommandState.getInstance(editor)
|
||||
val state = State.instance
|
||||
|
||||
val caret = editor.caretModel.primaryCaret
|
||||
state.firstRange = VimPlugin.getMotion().getWordRange(editor, caret, 1, false, false)
|
||||
|
||||
val firstRange = state.firstRange
|
||||
val startOffset = firstRange.startOffset
|
||||
val endOffset = firstRange.endOffset
|
||||
|
||||
state.nextOffset = VimPlugin.getSearch().searchWord(editor, caret, 1, whole, 1)
|
||||
|
||||
commandState.pushState(Mode.VISUAL, SubMode.VISUAL_CHARACTER, MappingMode.VISUAL)
|
||||
selectRange(editor, caret, startOffset, endOffset)
|
||||
MotionGroup.moveCaret(editor, caret, endOffset, true)
|
||||
}
|
||||
|
||||
private fun handleNextSelection(editor: Editor): Boolean {
|
||||
val state = State.instance
|
||||
|
||||
val caret = editor.caretModel.addCaret(editor.offsetToVisualPosition(state.nextOffset), true)
|
||||
?: throw IllegalStateException("Multiple carets are not supported")
|
||||
|
||||
val range = VimPlugin.getMotion().getWordRange(editor, caret, 1, false, false)
|
||||
|
||||
val firstRange = state.firstRange
|
||||
val startOffset = range.startOffset
|
||||
val endOffset = range.startOffset + firstRange.endOffset - firstRange.startOffset
|
||||
|
||||
if (startOffset == firstRange.startOffset && endOffset == firstRange.endOffset) {
|
||||
editor.caretModel.removeCaret(caret)
|
||||
VimPlugin.showMessage("No more matches")
|
||||
return false
|
||||
}
|
||||
|
||||
state.nextOffset = VimPlugin.getSearch().searchNext(editor, caret, 1)
|
||||
|
||||
selectRange(editor, caret, startOffset, endOffset)
|
||||
MotionGroup.moveCaret(editor, caret, endOffset, true)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
class VimMultipleCursorsExtension : VimNonDisposableExtension() {
|
||||
private var nextOffset = -1
|
||||
private lateinit var firstRange: TextRange
|
||||
|
||||
private val hasNext: Boolean get() = nextOffset != firstRange.startOffset
|
||||
|
||||
override fun getName() = "multiple-cursors"
|
||||
|
||||
override fun initOnce() {
|
||||
putExtensionHandlerMapping(MappingMode.NVO, parseKeys(NEXT_OCCURRENCE), NextOccurrenceHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.NVO, parseKeys(NOT_WHOLE_OCCURRENCE), NextOccurrenceHandler(false), false)
|
||||
|
||||
putExtensionHandlerMapping(MappingMode.NO, parseKeys(ALL_OCCURRENCES), AllOccurrencesHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.NO, parseKeys(NOT_WHOLE_ALL_OCCURRENCES), AllOccurrencesHandler(false),
|
||||
false)
|
||||
|
||||
putExtensionHandlerMapping(MappingMode.NVO, parseKeys(NEXT_WHOLE_OCCURRENCE), NextOccurrenceHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.NVO, parseKeys(NEXT_OCCURRENCE), NextOccurrenceHandler(whole = false), false)
|
||||
putExtensionHandlerMapping(MappingMode.NO, parseKeys(ALL_WHOLE_OCCURRENCES), AllOccurrencesHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.NO, parseKeys(ALL_OCCURRENCES), AllOccurrencesHandler(whole = false), false)
|
||||
putExtensionHandlerMapping(MappingMode.V, parseKeys(SKIP_OCCURRENCE), SkipOccurrenceHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.V, parseKeys(REMOVE_OCCURRENCE), RemoveOccurrenceHandler(), false)
|
||||
|
||||
putKeyMapping(MappingMode.NVO, parseKeys("<A-n>"), parseKeys(NEXT_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.NVO, parseKeys("g<A-n>"), parseKeys(NOT_WHOLE_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.NVO, parseKeys("<A-n>"), parseKeys(NEXT_WHOLE_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.NVO, parseKeys("g<A-n>"), parseKeys(NEXT_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.V, parseKeys("<A-x>"), parseKeys(SKIP_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.V, parseKeys("<A-p>"), parseKeys(REMOVE_OCCURRENCE), true)
|
||||
}
|
||||
|
||||
private class NextOccurrenceHandler(val whole: Boolean = true) : VimExtensionHandler {
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
inner class NextOccurrenceHandler(val whole: Boolean = true) : VimExtensionHandler {
|
||||
override fun execute(editor: Editor, context: DataContext) =
|
||||
if (editor.caretModel.caretCount == 1 && CommandState.getInstance(editor).mode != Mode.VISUAL) {
|
||||
handleFirstSelection(editor, whole)
|
||||
}
|
||||
else {
|
||||
handleNextSelection(editor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AllOccurrencesHandler(val whole: Boolean = true) : VimExtensionHandler {
|
||||
inner class AllOccurrencesHandler(val whole: Boolean = true) : VimExtensionHandler {
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
handleFirstSelection(editor, whole)
|
||||
while (handleNextSelection(editor)) {}
|
||||
while (hasNext) handleNextSelection(editor)
|
||||
}
|
||||
}
|
||||
|
||||
private class SkipOccurrenceHandler : VimExtensionHandler {
|
||||
inner class SkipOccurrenceHandler : VimExtensionHandler {
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
val caret = editor.caretModel.primaryCaret
|
||||
val offset = VimPlugin.getSearch().searchWord(editor, caret, 1, true, 1)
|
||||
if (offset == -1) return
|
||||
|
||||
caret.moveToOffset(offset)
|
||||
val state = State.instance
|
||||
val range = VimPlugin.getMotion().getWordRange(editor, caret, 1, false, false)
|
||||
editor.selectionModel.removeSelection()
|
||||
MotionGroup.moveCaret(editor, caret, nextOffset)
|
||||
if (editor.caretModel.caretCount == 1) {
|
||||
state.firstRange = range
|
||||
val firstLength = firstRange.length
|
||||
firstRange = VimPlugin.getMotion().getWordRange(editor, caret, 1, false, false)
|
||||
if (firstRange.length != firstLength) {
|
||||
firstRange = TextRange(firstRange.startOffset, firstRange.startOffset + firstLength)
|
||||
}
|
||||
}
|
||||
val startOffset = range.startOffset
|
||||
val endOffset = range.endOffset
|
||||
|
||||
state.nextOffset = VimPlugin.getSearch().searchWord(editor, caret, 1, true, 1)
|
||||
|
||||
selectRange(editor, caret, startOffset, endOffset)
|
||||
val endOffset = nextOffset + firstRange.length
|
||||
selectRange(editor, caret, nextOffset, endOffset)
|
||||
MotionGroup.moveCaret(editor, caret, endOffset, true)
|
||||
|
||||
nextOffset = VimPlugin.getSearch().searchNext(editor, caret, 1)
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoveOccurrenceHandler : VimExtensionHandler {
|
||||
inner class RemoveOccurrenceHandler : VimExtensionHandler {
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
State.instance.nextOffset = CaretData.getVisualStart(editor.caretModel.primaryCaret)
|
||||
nextOffset = CaretData.getVisualStart(editor.caretModel.primaryCaret)
|
||||
editor.selectionModel.removeSelection()
|
||||
if (!editor.caretModel.removeCaret(editor.caretModel.primaryCaret)) {
|
||||
getInstance(editor).popState()
|
||||
if (CommandState.getInstance(editor).mode == Mode.VISUAL) {
|
||||
CommandState.getInstance(editor).popState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectRange(editor: Editor, caret: Caret, startOffset: Int, endOffset: Int) {
|
||||
CaretData.setVisualStart(caret, startOffset)
|
||||
VimPlugin.getMotion().updateSelection(editor, caret, endOffset)
|
||||
editor.scrollingModel.scrollToCaret(ScrollType.CENTER)
|
||||
}
|
||||
|
||||
private fun handleFirstSelection(editor: Editor, whole: Boolean) {
|
||||
val caret = editor.caretModel.primaryCaret
|
||||
firstRange = VimPlugin.getMotion().getWordRange(editor, caret, 1, false, false)
|
||||
nextOffset = VimPlugin.getSearch().searchWord(editor, caret, 1, whole, 1)
|
||||
|
||||
CommandState.getInstance(editor).pushState(Mode.VISUAL, SubMode.VISUAL_CHARACTER, MappingMode.VISUAL)
|
||||
selectRange(editor, caret, firstRange.startOffset, firstRange.endOffset)
|
||||
MotionGroup.moveCaret(editor, caret, firstRange.endOffset, true)
|
||||
}
|
||||
|
||||
private fun handleNextSelection(editor: Editor) {
|
||||
if (!hasNext) {
|
||||
VimPlugin.showMessage("No more matches")
|
||||
return
|
||||
}
|
||||
|
||||
val caret = editor.caretModel.addCaret(editor.offsetToVisualPosition(nextOffset), true)
|
||||
?: throw IllegalStateException("Multiple carets are not supported")
|
||||
|
||||
val endOffset = nextOffset + firstRange.length
|
||||
selectRange(editor, caret, nextOffset, endOffset)
|
||||
MotionGroup.moveCaret(editor, caret, endOffset, true)
|
||||
|
||||
nextOffset = VimPlugin.getSearch().searchNext(editor, caret, 1)
|
||||
}
|
||||
}
|
||||
|
||||
private val TextRange.length: Int
|
||||
get() {
|
||||
if (isMultiple) throw IllegalStateException("Multiple range found")
|
||||
return maxLength
|
||||
}
|
@ -270,7 +270,7 @@ class VimMultipleCursorsExtensionTest : VimTestCase() {
|
||||
""".trimMargin()
|
||||
configureByText(before)
|
||||
|
||||
typeText(parseKeys("<Plug>AllOccurrences"))
|
||||
typeText(parseKeys("<Plug>AllWholeOccurrences"))
|
||||
|
||||
val after = """<selection>qwe</selection>
|
||||
|asd
|
||||
@ -292,7 +292,7 @@ class VimMultipleCursorsExtensionTest : VimTestCase() {
|
||||
""".trimMargin()
|
||||
configureByText(before)
|
||||
|
||||
typeText(parseKeys("<Plug>NotWholeAllOccurrences"))
|
||||
typeText(parseKeys("<Plug>AllOccurrences"))
|
||||
val after = """<selection>Int</selection>
|
||||
|<selection>Int</selection>eger
|
||||
|<selection>Int</selection>
|
||||
@ -303,4 +303,99 @@ class VimMultipleCursorsExtensionTest : VimTestCase() {
|
||||
""".trimMargin()
|
||||
myFixture.checkResult(after)
|
||||
}
|
||||
|
||||
fun testSelectSubstring() {
|
||||
val before = """q<caret>we
|
||||
|asdqweasd
|
||||
|qwe
|
||||
|asd
|
||||
""".trimMargin()
|
||||
configureByText(before)
|
||||
|
||||
typeText(parseKeys("g<A-n>".repeat(3)))
|
||||
|
||||
val after = """<selection>qwe</selection>
|
||||
|asd<selection>qwe</selection>asd
|
||||
|<selection>qwe</selection>
|
||||
|asd
|
||||
""".trimMargin()
|
||||
myFixture.checkResult(after)
|
||||
}
|
||||
|
||||
fun testSkipSelectionSubstring() {
|
||||
val before = """qw<caret>e
|
||||
|asdqweasd
|
||||
|ads
|
||||
|asdqweasd
|
||||
|qwe
|
||||
""".trimMargin()
|
||||
configureByText(before)
|
||||
|
||||
typeText(parseKeys("g<A-n>", "<A-x>", "<A-n>".repeat(2)))
|
||||
|
||||
val after = """qwe
|
||||
|asd<selection>qwe</selection>asd
|
||||
|ads
|
||||
|asd<selection>qwe</selection>asd
|
||||
|<selection>qwe</selection>
|
||||
""".trimMargin()
|
||||
myFixture.checkResult(after)
|
||||
}
|
||||
|
||||
fun testSelectSingleOccurrence() {
|
||||
val before = """q<caret>we
|
||||
|asd
|
||||
|zxc
|
||||
|cvb
|
||||
|dfg
|
||||
|rty
|
||||
""".trimMargin()
|
||||
configureByText(before)
|
||||
|
||||
typeText(parseKeys("<A-n>".repeat(4)))
|
||||
|
||||
val after = """<selection>qwe</selection>
|
||||
|asd
|
||||
|zxc
|
||||
|cvb
|
||||
|dfg
|
||||
|rty
|
||||
""".trimMargin()
|
||||
myFixture.checkResult(after)
|
||||
}
|
||||
|
||||
fun testSelectAllSingleOccurrence() {
|
||||
val before = """qwe
|
||||
|asd
|
||||
|z<caret>xc
|
||||
|adgf
|
||||
|dfgh
|
||||
|awe
|
||||
|td
|
||||
|gfhsd
|
||||
|fg
|
||||
""".trimMargin()
|
||||
configureByText(before)
|
||||
|
||||
typeText(parseKeys("<Plug>AllOccurrences"))
|
||||
|
||||
val after = before.replace("z<caret>xc", "<selection>zxc</selection>")
|
||||
myFixture.checkResult(after)
|
||||
}
|
||||
|
||||
fun testRemoveSubSelection() {
|
||||
val before = """Int
|
||||
|kekInteger
|
||||
|lolInteger
|
||||
""".trimMargin()
|
||||
configureByText(before)
|
||||
|
||||
typeText(parseKeys("g<A-n>", "<A-n>".repeat(2), "<A-p>"))
|
||||
|
||||
val after = """<selection>Int</selection>
|
||||
|kek<selection>Int</selection>eger
|
||||
|lolInteger
|
||||
""".trimMargin()
|
||||
myFixture.checkResult(after)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user