void (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); }); }