diff --git a/src/main/java/com/maddyhome/idea/vim/extension/surround/RepeatedCharSequence.kt b/src/main/java/com/maddyhome/idea/vim/extension/surround/RepeatedCharSequence.kt new file mode 100644 index 000000000..01b1cd413 --- /dev/null +++ b/src/main/java/com/maddyhome/idea/vim/extension/surround/RepeatedCharSequence.kt @@ -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) + } + } + } +} diff --git a/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt b/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt index 2ed9f9d12..177bac911 100644 --- a/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt +++ b/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt @@ -83,7 +83,7 @@ internal class VimSurroundExtension : VimExtension { putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true) } - VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false)) // TODO + VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO } private class YSurroundHandler : ExtensionHandler { @@ -111,7 +111,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) } @@ -135,7 +135,7 @@ internal class VimSurroundExtension : VimExtension { private class VSurroundHandler : ExtensionHandler { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { // NB: Operator ignores SelectionType anyway - if (!Operator(supportsMultipleCursors = true).apply(editor, context, editor.mode.selectionType)) { + if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) { return } runWriteAction { @@ -278,7 +278,7 @@ internal class VimSurroundExtension : VimExtension { } } - private class Operator(private val supportsMultipleCursors: Boolean) : OperatorFunction { + private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction { override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { val ijEditor = vimEditor.ij val c = getChar(ijEditor) @@ -290,11 +290,11 @@ internal class VimSurroundExtension : VimExtension { val change = VimPlugin.getChange() if (supportsMultipleCursors) { ijEditor.runWithEveryCaretAndRestore { - applyOnce(ijEditor, change, pair) + applyOnce(ijEditor, change, pair, count) } } else { - applyOnce(ijEditor, change, pair) + applyOnce(ijEditor, change, pair, count) // Jump back to start executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) } @@ -302,18 +302,15 @@ internal class VimSurroundExtension : VimExtension { return true } - private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: SurroundPair) { + private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: SurroundPair, count: Int) { // XXX: Will it work with line-wise or block-wise selections? val primaryCaret = editor.caretModel.primaryCaret val range = getSurroundRange(primaryCaret.vim) if (range != null) { - change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, pair.first) - change.insertText( - IjVimEditor(editor), - IjVimCaret(primaryCaret), - range.endOffset + pair.first.length, - pair.second - ) + 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) } } @@ -416,15 +413,15 @@ private fun getChar(editor: Editor): Char { return res } -private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) { +private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) { runWriteAction { 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 { @@ -432,7 +429,7 @@ private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCare } } else { pair.second - } + }).let { RepeatedCharSequence.of(it, count) } change.insertText(editor, caret, range.startOffset, leftSurround) change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt index b0d6091f8..894e68a82 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt @@ -271,7 +271,7 @@ interface VimChangeGroup { operatorArguments: OperatorArguments, ) - fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret + fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt index 5781cfa97..21d2a7711 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt @@ -216,7 +216,7 @@ 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 { injector.application.runWriteAction { (editor as MutableVimEditor).insertText(caret, offset, str) }