@Override
  public void moveCaretRelatively(final int _columnShift, final int lineShift, final boolean withSelection, final boolean scrollToCaret) {
    assertIsDispatchThread();
    if (mySkipChangeRequests) {
      return;
    }
    stopKillRings();
    myCaretModel.doWithCaretMerging(() -> {
      updateCachedStateIfNeeded();

      int oldOffset = getOffset();
      int columnShift = _columnShift;
      if (withSelection && lineShift == 0) {
        if (columnShift == -1) {
          int column;
          while ((column = myVisibleCaret.column + columnShift - (hasSelection() && oldOffset == getSelectionEnd() ? 1 : 0)) >= 0 &&
                 myEditor.getInlayModel().hasInlineElementAt(new VisualPosition(myVisibleCaret.line, column))) {
            columnShift--;
          }
        }
        else if (columnShift == 1) {
          while (myEditor.getInlayModel().hasInlineElementAt(
            new VisualPosition(myVisibleCaret.line,
                               myVisibleCaret.column + columnShift - (hasSelection() && oldOffset == getSelectionStart() ? 0 : 1)))) {
            columnShift++;
          }
        }
      }
      final int leadSelectionOffset = getLeadSelectionOffset();
      final VisualPosition leadSelectionPosition = getLeadSelectionPosition();
      EditorSettings editorSettings = myEditor.getSettings();
      VisualPosition visualCaret = getVisualPosition();

      int desiredX = myDesiredX;
      if (columnShift == 0) {
        if (myDesiredX < 0) {
          desiredX = getCurrentX();
        }
      }
      else {
        myDesiredX = desiredX = -1;
      }

      int newLineNumber = visualCaret.line + lineShift;
      int newColumnNumber = visualCaret.column + columnShift;
      boolean newLeansRight = lineShift == 0 && columnShift != 0 ? columnShift < 0 : visualCaret.leansRight;

      if (desiredX >= 0) {
        newColumnNumber = myEditor.xyToVisualPosition(new Point(desiredX, myEditor.visualLineToY(newLineNumber))).column;
      }

      Document document = myEditor.getDocument();
      if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == 1) {
        int lastLine = document.getLineCount() - 1;
        if (lastLine < 0) lastLine = 0;
        if (newColumnNumber > EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber) &&
            newLineNumber < myEditor.logicalToVisualPosition(new LogicalPosition(lastLine, 0)).line) {
          newColumnNumber = 0;
          newLineNumber++;
        }
      }
      else if (lineShift == 0 && columnShift == -1) {
        if (newColumnNumber < 0 && newLineNumber > 0) {
          newLineNumber--;
          if (editorSettings.isVirtualSpace()) {
            newColumnNumber = myEditor.offsetToVisualPosition(Math.max(0, oldOffset - 1)).column;
          } else {
            newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber);
          }
        }
      }

      if (newColumnNumber < 0) newColumnNumber = 0;

      // There is a possible case that caret is located at the first line and user presses 'Shift+Up'. We want to select all text
      // from the document start to the current caret position then. So, we have a dedicated flag for tracking that.
      boolean selectToDocumentStart = false;
      if (newLineNumber < 0) {
        selectToDocumentStart = true;
        newLineNumber = 0;

        // We want to move caret to the first column if it's already located at the first line and 'Up' is pressed.
        newColumnNumber = 0;
      }

      VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber);
      if (!myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
        int offset = myEditor.visualPositionToOffset(new VisualPosition(newLineNumber, newColumnNumber, newLeansRight));
        if (offset >= document.getTextLength() && columnShift == 0) {
          int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength(), true, false).column;
          // We want to move caret to the last column if it's located at the last line and 'Down' is pressed.
          if (lastOffsetColumn > newColumnNumber) {
            newColumnNumber = lastOffsetColumn;
            newLeansRight = true;
          }
        }
        if (!editorSettings.isCaretInsideTabs()) {
          CharSequence text = document.getCharsSequence();
          if (offset >= 0 && offset < document.getTextLength()) {
            if (text.charAt(offset) == '\t' && (columnShift <= 0 || offset == oldOffset) && !isAtRtlLocation()) {
              if (columnShift <= 0) {
                newColumnNumber = myEditor.offsetToVisualPosition(offset, true, false).column;
              }
              else {
                SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1);
                // There is a possible case that tabulation symbol is the last document symbol represented on a visual line before
                // soft wrap. We can't just use column from 'offset + 1' because it would point on a next visual line.
                if (softWrap == null) {
                  newColumnNumber = myEditor.offsetToVisualPosition(offset + 1).column;
                }
                else {
                  newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber);
                }
              }
            }
          }
        }
      }

      pos = new VisualPosition(newLineNumber, newColumnNumber, newLeansRight);
      if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
        int softWrapOffset = myEditor.visualPositionToOffset(pos);
        if (columnShift >= 0) {
          moveToOffset(softWrapOffset);
        }
        else {
          int line = myEditor.offsetToVisualLine(softWrapOffset - 1);
          moveToVisualPosition(new VisualPosition(line, EditorUtil.getLastVisualLineColumnNumber(myEditor, line)));
        }
      }
      else {
        moveToVisualPosition(pos);
      }

      if (withSelection) {
        if (selectToDocumentStart) {
          setSelection(leadSelectionPosition, leadSelectionOffset, myEditor.offsetToVisualPosition(0), 0);
        }
        else if (pos.line >= myEditor.getVisibleLineCount()) {
          int endOffset = document.getTextLength();
          if (leadSelectionOffset < endOffset) {
            setSelection(leadSelectionPosition, leadSelectionOffset, myEditor.offsetToVisualPosition(endOffset), endOffset);
          }
        }
        else {
          int selectionStartToUse = leadSelectionOffset;
          VisualPosition selectionStartPositionToUse = leadSelectionPosition;
          if (isUnknownDirection() || oldOffset > getSelectionStart() && oldOffset < getSelectionEnd()) {
            if (getOffset() > leadSelectionOffset ^ getSelectionStart() < getSelectionEnd()) {
              selectionStartToUse = getSelectionEnd();
              selectionStartPositionToUse = getSelectionEndPosition();
            }
            else {
              selectionStartToUse = getSelectionStart();
              selectionStartPositionToUse = getSelectionStartPosition();
            }
          }
          setSelection(selectionStartPositionToUse, selectionStartToUse, getVisualPosition(), getOffset());
        }
      }
      else {
        removeSelection();
      }

      if (scrollToCaret) {
        myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
      }

      if (desiredX >= 0) {
        myDesiredX = desiredX;
      }

      EditorActionUtil.selectNonexpandableFold(myEditor);
    });
  }
@Override
  public void moveCaretRelatively(final int _columnShift, final int lineShift, final boolean withSelection, final boolean scrollToCaret)