in vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt [49:151]
override fun processCommand(
editor: VimEditor,
context: ExecutionContext,
operatorArguments: OperatorArguments,
): ExecutionResult {
val caretCount = editor.nativeCarets().size
if (caretCount > 1) {
throw ExException("Move command supported only for one caret at the moment")
}
val caret = editor.primaryCaret()
// Move is defined as:
// :[range]m[ove] {address}
// Move the given [range] to below the line given by {address}. Address can be a range, but only the first address
// is used. The rest is ignored with no errors. Note that address is one-based, and 0 means move the text to below
// the line _before_ the first line (i.e., move to above the first line).
val sourceLineRange = getLineRange(editor, caret)
val sourceRange = sourceLineRange.toTextRange(editor)
val targetAddressLine1 = getAddressFromArgument(editor)
// Convert target one-based line to zero-based line. This means our special case of 0 will be represented by -1
// We'll move the range to _after_ this target line
val targetLineAfterDeletion = min(editor.fileSize().toInt(), normalizeAddress(targetAddressLine1 - 1, sourceLineRange))
val linesMoved = sourceLineRange.size
if (targetLineAfterDeletion < -1 || targetLineAfterDeletion + linesMoved >= editor.lineCount()) {
throw exExceptionMessage("E16")
}
val shift = targetLineAfterDeletion - editor.offsetToBufferPosition(sourceRange.startOffset).line + 1
val localMarks = injector.markService.getAllLocalMarks(caret)
.filter { sourceRange.contains(it.offset(editor)) }
.filter { it.key != VimMarkService.SELECTION_START_MARK && it.key != VimMarkService.SELECTION_END_MARK }
.toSet()
val globalMarks = injector.markService.getGlobalMarks(editor)
.filter { sourceRange.contains(it.offset(editor)) }
.map { Pair(it, it.line) } // we save logical line because it will be cleared by Platform after text deletion
.toSet()
val lastSelectionInfo = caret.lastSelectionInfo
val selectionStartOffset = lastSelectionInfo.start?.let { editor.bufferPositionToOffset(it) }
val selectionEndOffset = lastSelectionInfo.end?.let { editor.bufferPositionToOffset(it) }
val text = editor.getText(sourceRange)
val textData = PutData.TextData(null, injector.clipboardManager.dumbCopiedText(text), SelectionType.LINE_WISE)
val dropNewLineInEnd = (targetLineAfterDeletion + linesMoved == editor.lineCount() - 1 && text.last() == '\n') ||
(sourceLineRange.endLine == editor.lineCount() - 1)
// The caret will be moved as part of deleting/putting the text, so remember the current column so we can reset it
// if the 'nostartofline' is active
val caretColumn = caret.getBufferPosition().column
injector.application.runWriteAction {
editor.deleteString(sourceRange)
}
val putData = if (targetLineAfterDeletion == -1) {
// Special case. Move text to below the line before the first line
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 = targetLineAfterDeletion
)
}
injector.put.putTextForCaret(editor, caret, context, putData)
if (dropNewLineInEnd) {
assert(editor.text().last() == '\n')
injector.application.runWriteAction {
editor.deleteString(TextRange(editor.text().length - 1, editor.text().length))
}
}
globalMarks.forEach { shiftGlobalMark(editor, it, shift) }
localMarks.forEach { shiftLocalMark(caret, it, shift) }
shiftSelectionInfo(caret, selectionStartOffset, selectionEndOffset, lastSelectionInfo, shift, sourceRange)
// Move the caret to the end of the moved range, obeying 'startofline'. We've already moved the caret, so we can't
// use moveCaretToLineWithStartOfLineOption. We have to do it manually
val caretLine = if (targetAddressLine1 >= sourceLineRange.startLine1) {
targetAddressLine1 - 1
}
else {
targetAddressLine1 + (sourceLineRange.size) - 1
}
val caretOffset = if (!injector.options(editor).startofline) {
val column = editor.normalizeColumn(caretLine, caretColumn, allowEnd = false)
editor.bufferPositionToOffset(BufferPosition(caretLine, column))
}
else {
injector.motion.moveCaretToLineStartSkipLeading(editor, caretLine)
}
caret.moveToOffset(caretOffset)
return ExecutionResult.Success
}