fun setVisualSelection()

in vim-engine/src/main/kotlin/com/maddyhome/idea/vim/group/visual/EngineVisualGroup.kt [28:93]


fun setVisualSelection(selectionStart: Int, selectionEnd: Int, caret: VimCaret) {
  val (start, end) = if (selectionStart > selectionEnd) selectionEnd to selectionStart else selectionStart to selectionEnd
  val editor = caret.editor
  val selectionType = editor.mode.selectionType ?: SelectionType.CHARACTER_WISE
  val mode = editor.mode
  when (selectionType) {
    SelectionType.CHARACTER_WISE -> {
      val (nativeStart, nativeEnd) = charToNativeSelection(editor, start, end, mode)
      caret.vimSetSystemSelectionSilently(nativeStart, nativeEnd)
    }

    SelectionType.LINE_WISE -> {
      val (nativeStart, nativeEnd) = lineToNativeSelection(editor, start, end)
      caret.vimSetSystemSelectionSilently(nativeStart, nativeEnd)
    }

    SelectionType.BLOCK_WISE -> {
      // This will invalidate any secondary carets, but we shouldn't have any of these cached in local variables, etc.
      editor.removeSecondaryCarets()

      // Set system selection
      val (blockStart, blockEnd) = blockToNativeSelection(editor, selectionStart, selectionEnd, mode)
      val lastColumn = editor.primaryCaret().vimLastColumn

      // WARNING! This can invalidate the primary caret! I.e. the `caret` parameter will no longer be the primary caret.
      // Given an existing visual block selection, moving the caret will first remove all secondary carets (above) then
      // this method will ask IntelliJ to create a new multi-caret block selection. If we're moving up (`k`) a new caret
      // is added, and becomes the new primary caret. The current `caret` parameter remains valid, but is no longer the
      // primary caret. Make sure to fetch the new primary caret if necessary.
      editor.vimSetSystemBlockSelectionSilently(blockStart, blockEnd)

      // We've just added secondary carets again, hide them to better emulate block selection
      injector.editorGroup.updateCaretsVisualAttributes(editor)

      for (aCaret in editor.nativeCarets()) {
        if (!aCaret.isValid) continue
        val line = aCaret.getBufferPosition().line
        val lineEndOffset = editor.getLineEndOffset(line, true)
        val lineStartOffset = editor.getLineStartOffset(line)

        // Extend selection to line end if it was made with `$` command
        if (lastColumn >= VimMotionGroupBase.LAST_COLUMN) {
          aCaret.vimSetSystemSelectionSilently(aCaret.selectionStart, lineEndOffset)
          val newOffset = (lineEndOffset - injector.visualMotionGroup.selectionAdj).coerceAtLeast(lineStartOffset)
          aCaret.moveToInlayAwareOffset(newOffset)
        }
        val visualPosition = editor.offsetToVisualPosition(aCaret.selectionEnd)
        if (aCaret.offset == aCaret.selectionEnd && visualPosition != aCaret.getVisualPosition()) {
          // Put right caret position for tab character
          aCaret.moveToVisualPosition(visualPosition)
        }
        if (mode !is Mode.SELECT &&
          !editor.isLineEmpty(line, false) &&
          aCaret.offset == aCaret.selectionEnd &&
          aCaret.selectionEnd - 1 >= lineStartOffset &&
          aCaret.selectionEnd - aCaret.selectionStart != 0
        ) {
          // Move all carets one char left in case if it's on selection end
          aCaret.moveToVisualPosition(VimVisualPosition(visualPosition.line, visualPosition.column - 1))
        }
      }

      editor.primaryCaret().moveToInlayAwareOffset(selectionEnd)
    }
  }
}