override fun processCommand()

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
  }