/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.editor.impl;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.editor.ex.FoldingModelEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.diff.FilesTooBigForDiffException;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.ui.EmptyClipboardOwner;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.util.List;
public class CaretImpl extends UserDataHolderBase implements Caret {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.CaretImpl");
private final EditorImpl myEditor;
private boolean isValid = true;
private LogicalPosition myLogicalCaret;
private VerticalInfo myCaretInfo;
private VisualPosition myVisibleCaret;
private int myOffset;
private int myVirtualSpaceOffset;
private int myVisualLineStart;
private int myVisualLineEnd;
private RangeMarker savedBeforeBulkCaretMarker;
private boolean mySkipChangeRequests;
/**
* Initial horizontal caret position during vertical navigation.
* Similar to {@link #myDesiredX}, but represents logical caret position (getLogicalPosition().column
) rather than visual.
*/
private int myLastColumnNumber = 0;
private int myDesiredSelectionStartColumn = -1;
private int myDesiredSelectionEndColumn = -1;
/**
* We check that caret is located at the target offset at the end of {@link #moveToOffset(int, boolean)} method. However,
* it's possible that the following situation occurs:
*
** * This field serves as a flag that reports unexpected caret position change requests nested from {@link #moveToOffset(int, boolean)}. */ private boolean myReportCaretMoves; /** * This field holds initial horizontal caret position during vertical navigation. It's used to determine target position when * moving to the new line. It is stored in pixels, not in columns, to account for non-monospaced fonts as well. * * Negative value means no coordinate should be preserved. */ private int myDesiredX = -1; private volatile MyRangeMarker mySelectionMarker; private int startBefore; private int endBefore; boolean myUnknownDirection; // offsets of selection start/end position relative to end of line - can be non-zero in column selection mode // these are non-negative values, myStartVirtualOffset is always less or equal to myEndVirtualOffset private int myStartVirtualOffset; private int myEndVirtualOffset; CaretImpl(EditorImpl editor) { myEditor = editor; myLogicalCaret = new LogicalPosition(0, 0); myVisibleCaret = new VisualPosition(0, 0); myCaretInfo = new VerticalInfo(0, 0); myOffset = 0; myVisualLineStart = 0; Document doc = myEditor.getDocument(); myVisualLineEnd = doc.getLineCount() > 1 ? doc.getLineStartOffset(1) : doc.getLineCount() == 0 ? 0 : doc.getLineEndOffset(0); } void onBulkDocumentUpdateStarted(@NotNull Document doc) { if (doc != myEditor.getDocument() || myOffset > doc.getTextLength() || savedBeforeBulkCaretMarker != null) return; savedBeforeBulkCaretMarker = doc.createRangeMarker(myOffset, myOffset); } void onBulkDocumentUpdateFinished(@NotNull Document doc) { if (doc != myEditor.getDocument() || myEditor.getCaretModel().myIsInUpdate) return; LOG.assertTrue(!myReportCaretMoves); if (savedBeforeBulkCaretMarker != null) { if(savedBeforeBulkCaretMarker.isValid()) { if(savedBeforeBulkCaretMarker.getStartOffset() != myOffset) { moveToOffset(savedBeforeBulkCaretMarker.getStartOffset()); } } else if (myOffset > doc.getTextLength()) { moveToOffset(doc.getTextLength()); } releaseBulkCaretMarker(); } } public void beforeDocumentChange() { MyRangeMarker marker = mySelectionMarker; if (marker != null && marker.isValid()) { startBefore = marker.getStartOffset(); endBefore = marker.getEndOffset(); } } public void documentChanged() { MyRangeMarker marker = mySelectionMarker; if (marker != null) { int endAfter; int startAfter; if (marker.isValid()) { startAfter = marker.getStartOffset(); endAfter = marker.getEndOffset(); if (myEndVirtualOffset > 0 && (!isVirtualSelectionEnabled() || !EditorUtil.isAtLineEnd(myEditor, endAfter) || myEditor.getDocument().getLineNumber(startAfter) != myEditor.getDocument().getLineNumber(endAfter))) { myStartVirtualOffset = 0; myEndVirtualOffset = 0; } } else { startAfter = endAfter = getOffset(); marker.release(); myStartVirtualOffset = 0; myEndVirtualOffset = 0; mySelectionMarker = null; } if (startBefore != startAfter || endBefore != endAfter) { myEditor.getSelectionModel().fireSelectionChanged(startBefore, endBefore, startAfter, endAfter); } } } @Override public void moveToOffset(int offset) { moveToOffset(offset, false); } @Override public void moveToOffset(final int offset, final boolean locateBeforeSoftWrap) { assertIsDispatchThread(); validateCallContext(); if (mySkipChangeRequests) { return; } myEditor.getCaretModel().doWithCaretMerging(new Runnable() { public void run() { final LogicalPosition logicalPosition = myEditor.offsetToLogicalPosition(offset); CaretEvent event = moveToLogicalPosition(logicalPosition, locateBeforeSoftWrap, null, false); final LogicalPosition positionByOffsetAfterMove = myEditor.offsetToLogicalPosition(myOffset); if (!positionByOffsetAfterMove.equals(logicalPosition)) { StringBuilder debugBuffer = new StringBuilder(); moveToLogicalPosition(logicalPosition, locateBeforeSoftWrap, debugBuffer, true); int textStart = Math.max(0, Math.min(offset, myOffset) - 1); final DocumentEx document = myEditor.getDocument(); int textEnd = Math.min(document.getTextLength() - 1, Math.max(offset, myOffset) + 1); CharSequence text = document.getCharsSequence().subSequence(textStart, textEnd); StringBuilder positionToOffsetTrace = new StringBuilder(); int inverseOffset = myEditor.logicalPositionToOffset(logicalPosition, positionToOffsetTrace); LogMessageEx.error( LOG, "caret moved to wrong offset. Please submit a dedicated ticket and attach current editor's text to it.", String.format( "Requested: offset=%d, logical position='%s' but actual: offset=%d, logical position='%s' (%s). %s%n" + "interested text [%d;%d): '%s'%n debug trace: %s%nLogical position -> offset ('%s'->'%d') trace: %s", offset, logicalPosition, myOffset, myLogicalCaret, positionByOffsetAfterMove, myEditor.dumpState(), textStart, textEnd, text, debugBuffer, logicalPosition, inverseOffset, positionToOffsetTrace ) ); } if (event != null) { myEditor.getCaretModel().fireCaretPositionChanged(event); EditorActionUtil.selectNonexpandableFold(myEditor); } } }); } @NotNull @Override public CaretModel getCaretModel() { return myEditor.getCaretModel(); } @Override public boolean isValid() { return isValid; } @Override public void moveCaretRelatively(int columnShift, int lineShift, boolean withSelection, boolean scrollToCaret) { moveCaretRelatively(columnShift, lineShift, withSelection, false, scrollToCaret); } void moveCaretRelatively(final int columnShift, final int lineShift, final boolean withSelection, final boolean blockSelection, final boolean scrollToCaret) { assertIsDispatchThread(); if (mySkipChangeRequests) { return; } if (myReportCaretMoves) { LogMessageEx.error(LOG, "Unexpected caret move request"); } if (!myEditor.isStickySelection() && !myEditor.getCaretModel().isDocumentChanged) { CopyPasteManager.getInstance().stopKillRings(); } myEditor.getCaretModel().doWithCaretMerging(new Runnable() { public void run() { SelectionModelImpl selectionModel = myEditor.getSelectionModel(); final int leadSelectionOffset = getLeadSelectionOffset(); final VisualPosition leadSelectionPosition = getLeadSelectionPosition(); LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : getLogicalPosition(); EditorSettings editorSettings = myEditor.getSettings(); VisualPosition visualCaret = getVisualPosition(); int lastColumnNumber = myLastColumnNumber; 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; if (desiredX >= 0) { newColumnNumber = myEditor.xyToVisualPosition(new Point(desiredX, Math.max(0, newLineNumber) * myEditor.getLineHeight())).column; } Document document = myEditor.getDocument(); if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == 1) { int lastLine = document.getLineCount() - 1; if (lastLine < 0) lastLine = 0; if (EditorModificationUtil.calcAfterLineEnd(myEditor) >= 0 && newLineNumber < myEditor.logicalToVisualPosition(new LogicalPosition(lastLine, 0)).line) { newColumnNumber = 0; newLineNumber++; } } else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == -1) { if (newColumnNumber < 0 && newLineNumber > 0) { newLineNumber--; 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; desiredX = -1; lastColumnNumber = -1; } VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber); if (!myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) { LogicalPosition log = myEditor.visualToLogicalPosition(new VisualPosition(newLineNumber, newColumnNumber)); int offset = myEditor.logicalPositionToOffset(log); if (offset >= document.getTextLength()) { int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength()).column; // We want to move caret to the last column if if it's located at the last line and 'Down' is pressed. if (lastOffsetColumn > newColumnNumber) { newColumnNumber = lastOffsetColumn; desiredX = -1; lastColumnNumber = -1; } } if (!editorSettings.isCaretInsideTabs()) { CharSequence text = document.getCharsSequence(); if (offset >= 0 && offset < document.getTextLength()) { if (text.charAt(offset) == '\t' && (columnShift <= 0 || offset == myOffset)) { if (columnShift <= 0) { newColumnNumber = myEditor.offsetToVisualPosition(offset).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); if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) { LogicalPosition logical = myEditor.visualToLogicalPosition(pos); int softWrapOffset = myEditor.logicalPositionToOffset(logical); if (columnShift >= 0) { moveToOffset(softWrapOffset); } else { int line = myEditor.offsetToVisualLine(softWrapOffset - 1); moveToVisualPosition(new VisualPosition(line, EditorUtil.getLastVisualLineColumnNumber(myEditor, line))); } } else { moveToVisualPosition(pos); if (!editorSettings.isVirtualSpace() && columnShift == 0 && lastColumnNumber >=0) { setLastColumnNumber(lastColumnNumber); } } if (withSelection) { if (blockSelection && !supportsMultipleCarets()) { selectionModel.setBlockSelection(blockSelectionStart, getLogicalPosition()); } else { if (selectToDocumentStart) { if (supportsMultipleCarets()) { setSelection(leadSelectionPosition, leadSelectionOffset, myEditor.offsetToVisualPosition(0), 0); } else { setSelection(leadSelectionOffset, 0); } } else if (pos.line >= myEditor.getVisibleLineCount()) { int endOffset = document.getTextLength(); if (leadSelectionOffset < endOffset) { if (supportsMultipleCarets()) { setSelection(leadSelectionPosition, leadSelectionOffset, myEditor.offsetToVisualPosition(endOffset), endOffset); } else { setSelection(leadSelectionOffset, endOffset); } } } else { int selectionStartToUse = leadSelectionOffset; VisualPosition selectionStartPositionToUse = leadSelectionPosition; if (isUnknownDirection()) { if (getOffset() > leadSelectionOffset ^ getSelectionStart() < getSelectionEnd()) { selectionStartToUse = getSelectionEnd(); selectionStartPositionToUse = getSelectionEndPosition(); } else { selectionStartToUse = getSelectionStart(); selectionStartPositionToUse = getSelectionStartPosition(); } } if (supportsMultipleCarets()) { setSelection(selectionStartPositionToUse, selectionStartToUse, getVisualPosition(), getOffset()); } else { setSelection(selectionStartToUse, getVisualPosition(), getOffset()); } } } } else { removeSelection(); } if (scrollToCaret) { myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } if (desiredX >= 0) { myDesiredX = desiredX; } EditorActionUtil.selectNonexpandableFold(myEditor); } }); } @Override public void moveToLogicalPosition(@NotNull final LogicalPosition pos) { myEditor.getCaretModel().doWithCaretMerging(new Runnable() { public void run() { moveToLogicalPosition(pos, false, null, true); } }); } private CaretEvent doMoveToLogicalPosition(@NotNull LogicalPosition pos, boolean locateBeforeSoftWrap, @NonNls @Nullable StringBuilder debugBuffer, boolean fireListeners) { assertIsDispatchThread(); if (debugBuffer != null) { debugBuffer.append("Start moveToLogicalPosition(). Locate before soft wrap: ").append(locateBeforeSoftWrap).append(", position: ") .append(pos).append("\n"); } myDesiredX = -1; validateCallContext(); int column = pos.column; int line = pos.line; int softWrapLinesBefore = pos.softWrapLinesBeforeCurrentLogicalLine; int softWrapLinesCurrent = pos.softWrapLinesOnCurrentLogicalLine; int softWrapColumns = pos.softWrapColumnDiff; Document doc = myEditor.getDocument(); if (column < 0) { if (debugBuffer != null) { debugBuffer.append("Resetting target logical column to zero as it is negative (").append(column).append(")\n"); } column = 0; softWrapColumns = 0; } if (line < 0) { if (debugBuffer != null) { debugBuffer.append("Resetting target logical line to zero as it is negative (").append(line).append(")\n"); } line = 0; softWrapLinesBefore = 0; softWrapLinesCurrent = 0; } int lineCount = doc.getLineCount(); if (lineCount == 0) { if (debugBuffer != null) { debugBuffer.append("Resetting target logical line to zero as the document is empty\n"); } line = 0; } else if (line > lineCount - 1) { if (debugBuffer != null) { debugBuffer.append("Resetting target logical line (").append(line).append(") to ").append(lineCount - 1) .append(" as it is greater than total document lines number\n"); } line = lineCount - 1; softWrapLinesBefore = 0; softWrapLinesCurrent = 0; } EditorSettings editorSettings = myEditor.getSettings(); if (!editorSettings.isVirtualSpace() && line < lineCount && !myEditor.getSelectionModel().hasBlockSelection()) { int lineEndOffset = doc.getLineEndOffset(line); final LogicalPosition endLinePosition = myEditor.offsetToLogicalPosition(lineEndOffset); int lineEndColumnNumber = endLinePosition.column; if (column > lineEndColumnNumber) { int oldColumn = column; column = lineEndColumnNumber; if (softWrapColumns != 0) { softWrapColumns -= column - lineEndColumnNumber; } if (debugBuffer != null) { debugBuffer.append("Resetting target logical column (").append(oldColumn).append(") to ").append(lineEndColumnNumber) .append(" because caret is not allowed to be located after line end (offset: ").append(lineEndOffset).append(", ") .append("logical position: ").append(endLinePosition).append("). Current soft wrap columns value: ").append(softWrapColumns) .append("\n"); } } } myEditor.getFoldingModel().flushCaretPosition(); VerticalInfo oldInfo = myCaretInfo; LogicalPosition oldCaretPosition = myLogicalCaret; LogicalPosition logicalPositionToUse; if (pos.visualPositionAware) { logicalPositionToUse = new LogicalPosition( line, column, softWrapLinesBefore, softWrapLinesCurrent, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff ); } else { logicalPositionToUse = new LogicalPosition(line, column); } setCurrentLogicalCaret(logicalPositionToUse); final int offset = myEditor.logicalPositionToOffset(myLogicalCaret); if (debugBuffer != null) { debugBuffer.append("Resulting logical position to use: ").append(myLogicalCaret).append(". It's mapped to offset ").append(offset).append("\n"); } FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(offset); if (collapsedAt != null && offset > collapsedAt.getStartOffset()) { if (debugBuffer != null) { debugBuffer.append("Scheduling expansion of fold region ").append(collapsedAt).append("\n"); } Runnable runnable = new Runnable() { @Override public void run() { FoldRegion[] allCollapsedAt = myEditor.getFoldingModel().fetchCollapsedAt(offset); for (FoldRegion foldRange : allCollapsedAt) { foldRange.setExpanded(true); } } }; mySkipChangeRequests = true; try { myEditor.getFoldingModel().runBatchFoldingOperation(runnable, false); } finally { mySkipChangeRequests = false; } } setLastColumnNumber(myLogicalCaret.column); myDesiredSelectionStartColumn = myDesiredSelectionEndColumn = -1; myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret); updateOffsetsFromLogicalPosition(); if (debugBuffer != null) { debugBuffer.append("Storing offset ").append(myOffset).append(" (mapped from logical position ").append(myLogicalCaret).append(")\n"); } LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength()); updateVisualLineInfo(); myEditor.updateCaretCursor(); requestRepaint(oldInfo); if (locateBeforeSoftWrap && SoftWrapHelper.isCaretAfterSoftWrap(this)) { int lineToUse = myVisibleCaret.line - 1; if (lineToUse >= 0) { final VisualPosition visualPosition = new VisualPosition(lineToUse, EditorUtil.getLastVisualLineColumnNumber(myEditor, lineToUse)); if (debugBuffer != null) { debugBuffer.append("Adjusting caret position by moving it before soft wrap. Moving to visual position ").append(visualPosition).append("\n"); } final LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(visualPosition); final int tmpOffset = myEditor.logicalPositionToOffset(logicalPosition); if (tmpOffset == myOffset) { boolean restore = myReportCaretMoves; myReportCaretMoves = false; try { moveToVisualPosition(visualPosition); return null; } finally { myReportCaretMoves = restore; } } else { LogMessageEx.error(LOG, "Invalid editor dimension mapping", String.format( "Expected to map visual position '%s' to offset %d but got the following: -> logical position '%s'; -> offset %d. " + "State: %s", visualPosition, myOffset, logicalPosition, tmpOffset, myEditor.dumpState() )); } } } if (!oldCaretPosition.toVisualPosition().equals(myLogicalCaret.toVisualPosition())) { CaretEvent event = new CaretEvent(myEditor, supportsMultipleCarets() ? this : null, oldCaretPosition, myLogicalCaret); if (fireListeners) { myEditor.getCaretModel().fireCaretPositionChanged(event); } else { return event; } } return null; } private boolean supportsMultipleCarets() { return myEditor.getCaretModel().supportsMultipleCarets(); } private void updateOffsetsFromLogicalPosition() { myOffset = myEditor.logicalPositionToOffset(myLogicalCaret); myVirtualSpaceOffset = myLogicalCaret.column - myEditor.offsetToLogicalPosition(myOffset).column; } private void setLastColumnNumber(int lastColumnNumber) { myLastColumnNumber = lastColumnNumber; myEditor.setLastColumnNumber(lastColumnNumber); } private void requestRepaint(VerticalInfo oldCaretInfo) { int lineHeight = myEditor.getLineHeight(); Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea(); final EditorGutterComponentEx gutter = myEditor.getGutterComponentEx(); final EditorComponentImpl content = myEditor.getContentComponent(); int updateWidth = myEditor.getScrollPane().getHorizontalScrollBar().getValue() + visibleArea.width; if (Math.abs(myCaretInfo.y - oldCaretInfo.y) <= 2 * lineHeight) { int minY = Math.min(oldCaretInfo.y, myCaretInfo.y); int maxY = Math.max(oldCaretInfo.y + oldCaretInfo.height, myCaretInfo.y + myCaretInfo.height); content.repaintEditorComponent(0, minY, updateWidth, maxY - minY); gutter.repaint(0, minY, gutter.getWidth(), maxY - minY); } else { content.repaintEditorComponent(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight); gutter.repaint(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight); content.repaintEditorComponent(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight); gutter.repaint(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight); } } @Override public void moveToVisualPosition(@NotNull final VisualPosition pos) { myEditor.getCaretModel().doWithCaretMerging(new Runnable() { public void run() { moveToVisualPosition(pos, true); } }); } void moveToVisualPosition(@NotNull VisualPosition pos, boolean fireListeners) { assertIsDispatchThread(); validateCallContext(); if (mySkipChangeRequests) { return; } if (myReportCaretMoves) { LogMessageEx.error(LOG, "Unexpected caret move request"); } if (!myEditor.isStickySelection() && !myEditor.getCaretModel().isDocumentChanged && !pos.equals(myVisibleCaret)) { CopyPasteManager.getInstance().stopKillRings(); } myDesiredX = -1; int column = pos.column; int line = pos.line; if (column < 0) column = 0; if (line < 0) line = 0; int lastLine = myEditor.getVisibleLineCount() - 1; if (lastLine <= 0) { lastLine = 0; } if (line > lastLine) { line = lastLine; } EditorSettings editorSettings = myEditor.getSettings(); if (!editorSettings.isVirtualSpace() && line <= lastLine) { int lineEndColumn = EditorUtil.getLastVisualLineColumnNumber(myEditor, line); if (column > lineEndColumn) { column = lineEndColumn; } if (column < 0 && line > 0) { line--; column = EditorUtil.getLastVisualLineColumnNumber(myEditor, line); } } myVisibleCaret = new VisualPosition(line, column); VerticalInfo oldInfo = myCaretInfo; LogicalPosition oldPosition = myLogicalCaret; setCurrentLogicalCaret(myEditor.visualToLogicalPosition(myVisibleCaret)); updateOffsetsFromLogicalPosition(); LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength()); updateVisualLineInfo(); myEditor.getFoldingModel().flushCaretPosition(); setLastColumnNumber(myLogicalCaret.column); myDesiredSelectionStartColumn = myDesiredSelectionEndColumn = -1; myEditor.updateCaretCursor(); requestRepaint(oldInfo); if (fireListeners && !oldPosition.equals(myLogicalCaret)) { CaretEvent event = new CaretEvent(myEditor, supportsMultipleCarets() ? this : null, oldPosition, myLogicalCaret); myEditor.getCaretModel().fireCaretPositionChanged(event); } } @Nullable CaretEvent moveToLogicalPosition(@NotNull LogicalPosition pos, boolean locateBeforeSoftWrap, @Nullable StringBuilder debugBuffer, boolean fireListeners) { if (mySkipChangeRequests) { return null; } if (myReportCaretMoves) { LogMessageEx.error(LOG, "Unexpected caret move request"); } if (!myEditor.isStickySelection() && !myEditor.getCaretModel().isDocumentChanged && !pos.equals(myLogicalCaret)) { CopyPasteManager.getInstance().stopKillRings(); } myReportCaretMoves = true; try { return doMoveToLogicalPosition(pos, locateBeforeSoftWrap, debugBuffer, fireListeners); } finally { myReportCaretMoves = false; } } private void assertIsDispatchThread() { myEditor.assertIsDispatchThread(); } private void validateCallContext() { LOG.assertTrue(!myEditor.getCaretModel().myIsInUpdate, "Caret model is in its update process. All requests are illegal at this point."); } private void releaseBulkCaretMarker() { if (savedBeforeBulkCaretMarker != null) { savedBeforeBulkCaretMarker.dispose(); savedBeforeBulkCaretMarker = null; } } @Override public void dispose() { if (mySelectionMarker != null) { mySelectionMarker.release(); mySelectionMarker = null; } releaseBulkCaretMarker(); isValid = false; } @Override public boolean isUpToDate() { return !myEditor.getCaretModel().myIsInUpdate && !myReportCaretMoves; } @NotNull @Override public LogicalPosition getLogicalPosition() { validateCallContext(); return myLogicalCaret; } @NotNull @Override public VisualPosition getVisualPosition() { validateCallContext(); return myVisibleCaret; } @Override public int getOffset() { validateCallContext(); return myOffset; } @Override public int getVisualLineStart() { return myVisualLineStart; } @Override public int getVisualLineEnd() { return myVisualLineEnd; } @NotNull private VerticalInfo createVerticalInfo(LogicalPosition position) { Document document = myEditor.getDocument(); int logicalLine = position.line; if (logicalLine >= document.getLineCount()) { logicalLine = Math.max(0, document.getLineCount() - 1); } int startOffset = document.getLineStartOffset(logicalLine); int endOffset = document.getLineEndOffset(logicalLine); // There is a possible case that active logical line is represented on multiple lines due to soft wraps processing. // We want to highlight those visual lines as 'active' then, so, we calculate 'y' position for the logical line start // and height in accordance with the number of occupied visual lines. VisualPosition visualPosition = myEditor.offsetToVisualPosition(document.getLineStartOffset(logicalLine)); int y = myEditor.visualPositionToXY(visualPosition).y; int lineHeight = myEditor.getLineHeight(); int height = lineHeight; List extends SoftWrap> softWraps = myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffset); for (SoftWrap softWrap : softWraps) { height += StringUtil.countNewLines(softWrap.getText()) * lineHeight; } return new VerticalInfo(y, height); } /** * Recalculates caret visual position without changing its logical position (called when soft wraps are changing) */ public void updateVisualPosition() { VerticalInfo oldInfo = myCaretInfo; LogicalPosition visUnawarePos = new LogicalPosition(myLogicalCaret.line, myLogicalCaret.column); setCurrentLogicalCaret(visUnawarePos); myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret); updateVisualLineInfo(); myEditor.updateCaretCursor(); requestRepaint(oldInfo); } private void updateVisualLineInfo() { myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); } void updateCaretPosition(@NotNull final DocumentEventImpl event) { final DocumentEx document = myEditor.getDocument(); boolean performSoftWrapAdjustment = event.getNewLength() > 0 // We want to put caret just after the last added symbol // There is a possible case that the user removes text just before the soft wrap. We want to keep caret // on a visual line with soft wrap start then. || myEditor.getSoftWrapModel().getSoftWrap(event.getOffset()) != null; if (event.isWholeTextReplaced()) { int newLength = document.getTextLength(); if (myOffset == newLength - event.getNewLength() + event.getOldLength() || newLength == 0) { moveToOffset(newLength, performSoftWrapAdjustment); } else { try { final int line = event.translateLineViaDiff(myLogicalCaret.line); moveToLogicalPosition(new LogicalPosition(line, myLogicalCaret.column), performSoftWrapAdjustment, null, true); } catch (FilesTooBigForDiffException e1) { LOG.info(e1); moveToOffset(0); } } } else { if (document.isInBulkUpdate()) return; int startOffset = event.getOffset(); int oldEndOffset = startOffset + event.getOldLength(); int newOffset = myOffset; if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhiteSpaces(event)) { newOffset += event.getNewLength() - event.getOldLength(); } else if (myOffset >= startOffset && myOffset <= oldEndOffset) { newOffset = Math.min(newOffset, startOffset + event.getNewLength()); } newOffset = Math.min(newOffset, document.getTextLength()); if (supportsMultipleCarets() && myOffset != startOffset) { LogicalPosition pos = myEditor.offsetToLogicalPosition(newOffset); moveToLogicalPosition(new LogicalPosition(pos.line, pos.column + myVirtualSpaceOffset), // retain caret in the virtual space performSoftWrapAdjustment, null, true); } else { moveToOffset(newOffset, performSoftWrapAdjustment); } } updateVisualLineInfo(); } private boolean needToShiftWhiteSpaces(final DocumentEvent e) { if (!CharArrayUtil.containsOnlyWhiteSpaces(e.getNewFragment()) || CharArrayUtil.containLineBreaks(e.getNewFragment())) return e.getOldLength() > 0; if (e.getOffset() == 0) return false; final char charBefore = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() - 1); //final char charAfter = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() + e.getNewLength()); return Character.isWhitespace(charBefore)/* || !Character.isWhitespace(charAfter)*/; } private void setCurrentLogicalCaret(@NotNull LogicalPosition position) { myLogicalCaret = position; myCaretInfo = createVerticalInfo(position); } int getWordAtCaretStart() { Document document = myEditor.getDocument(); int offset = getOffset(); if (offset == 0) return 0; int lineNumber = getLogicalPosition().line; CharSequence text = document.getCharsSequence(); int newOffset = offset - 1; int minOffset = lineNumber > 0 ? document.getLineEndOffset(lineNumber - 1) : 0; boolean camel = myEditor.getSettings().isCamelWords(); for (; newOffset > minOffset; newOffset--) { if (EditorActionUtil.isWordStart(text, newOffset, camel)) break; } return newOffset; } int getWordAtCaretEnd() { Document document = myEditor.getDocument(); int offset = getOffset(); CharSequence text = document.getCharsSequence(); if (offset >= document.getTextLength() - 1 || document.getLineCount() == 0) return offset; int newOffset = offset + 1; int lineNumber = getLogicalPosition().line; int maxOffset = document.getLineEndOffset(lineNumber); if (newOffset > maxOffset) { if (lineNumber + 1 >= document.getLineCount()) return offset; maxOffset = document.getLineEndOffset(lineNumber + 1); } boolean camel = myEditor.getSettings().isCamelWords(); for (; newOffset < maxOffset; newOffset++) { if (EditorActionUtil.isWordEnd(text, newOffset, camel)) break; } return newOffset; } CaretImpl cloneWithoutSelection() { CaretImpl clone = new CaretImpl(myEditor); clone.myLogicalCaret = this.myLogicalCaret; clone.myCaretInfo = this.myCaretInfo; clone.myVisibleCaret = this.myVisibleCaret; clone.myOffset = this.myOffset; clone.myVirtualSpaceOffset = this.myVirtualSpaceOffset; clone.myVisualLineStart = this.myVisualLineStart; clone.myVisualLineEnd = this.myVisualLineEnd; clone.savedBeforeBulkCaretMarker = this.savedBeforeBulkCaretMarker; clone.mySkipChangeRequests = this.mySkipChangeRequests; clone.myLastColumnNumber = this.myLastColumnNumber; clone.myReportCaretMoves = this.myReportCaretMoves; clone.myDesiredX = this.myDesiredX; clone.myDesiredSelectionStartColumn = -1; clone.myDesiredSelectionEndColumn = -1; return clone; } @Nullable @Override public Caret clone(boolean above) { assertIsDispatchThread(); int lineShift = above ? -1 : 1; final CaretImpl clone = cloneWithoutSelection(); final int newSelectionStartOffset, newSelectionEndOffset, newSelectionStartColumn, newSelectionEndColumn; final VisualPosition newSelectionStartPosition, newSelectionEndPosition; final boolean hasNewSelection; if (hasSelection() || myDesiredSelectionStartColumn >=0 || myDesiredSelectionEndColumn >= 0) { VisualPosition startPosition = getSelectionStartPosition(); VisualPosition endPosition = getSelectionEndPosition(); VisualPosition leadPosition = getLeadSelectionPosition(); boolean leadIsStart = leadPosition.equals(startPosition); boolean leadIsEnd = leadPosition.equals(endPosition); LogicalPosition selectionStart = myEditor.visualToLogicalPosition(leadIsStart || leadIsEnd ? leadPosition : startPosition); LogicalPosition selectionEnd = myEditor.visualToLogicalPosition(leadIsEnd ? startPosition : endPosition); newSelectionStartColumn = myDesiredSelectionStartColumn < 0 ? selectionStart.column : myDesiredSelectionStartColumn; newSelectionEndColumn = myDesiredSelectionEndColumn < 0 ? selectionEnd.column : myDesiredSelectionEndColumn; LogicalPosition newSelectionStart = truncate(selectionStart.line + lineShift, newSelectionStartColumn); LogicalPosition newSelectionEnd = truncate(selectionEnd.line + lineShift, newSelectionEndColumn); newSelectionStartOffset = myEditor.logicalPositionToOffset(newSelectionStart); newSelectionEndOffset = myEditor.logicalPositionToOffset(newSelectionEnd); newSelectionStartPosition = myEditor.logicalToVisualPosition(newSelectionStart); newSelectionEndPosition = myEditor.logicalToVisualPosition(newSelectionEnd); hasNewSelection = !newSelectionStart.equals(newSelectionEnd); } else { newSelectionStartOffset = 0; newSelectionEndOffset = 0; newSelectionStartPosition = null; newSelectionEndPosition = null; hasNewSelection = false; newSelectionStartColumn = -1; newSelectionEndColumn = -1; } LogicalPosition oldPosition = getLogicalPosition(); int newLine = oldPosition.line + lineShift; if (newLine < 0 || newLine >= myEditor.getDocument().getLineCount()) { Disposer.dispose(clone); return null; } clone.moveToLogicalPosition(new LogicalPosition(newLine, myLastColumnNumber), false, null, false); clone.myLastColumnNumber = myLastColumnNumber; clone.myDesiredX = myDesiredX >= 0 ? myDesiredX : getCurrentX(); clone.myDesiredSelectionStartColumn = newSelectionStartColumn; clone.myDesiredSelectionEndColumn = newSelectionEndColumn; if (myEditor.getCaretModel().addCaret(clone)) { if (hasNewSelection) { myEditor.getCaretModel().doWithCaretMerging(new Runnable() { @Override public void run() { clone.setSelection(newSelectionStartPosition, newSelectionStartOffset, newSelectionEndPosition, newSelectionEndOffset); } }); if (!clone.isValid()) { return null; } } myEditor.getScrollingModel().scrollTo(clone.getLogicalPosition(), ScrollType.RELATIVE); return clone; } else { Disposer.dispose(clone); return null; } } private LogicalPosition truncate(int line, int column) { if (line < 0) { return new LogicalPosition(0, 0); } else if (line >= myEditor.getDocument().getLineCount()) { return myEditor.offsetToLogicalPosition(myEditor.getDocument().getTextLength()); } else { return new LogicalPosition(line, column); } } /** * @return information on whether current selection's direction in known * @see #setUnknownDirection(boolean) */ public boolean isUnknownDirection() { return myUnknownDirection; } /** * There is a possible case that we don't know selection's direction. For example, a user might triple-click editor (select the * whole line). We can't say what selection end is a {@link #getLeadSelectionOffset() leading end} then. However, that matters * in a situation when a user clicks before or after that line holding Shift key. It's expected that the selection is expanded * up to that point than. * * That's why we allow to specify that the direction is unknown and {@link #isUnknownDirection() expose this information} * later. * * Note: when this method is called with*
*- Some client subscribes to caret change events;
*- {@link #moveToLogicalPosition(LogicalPosition)} is called;
*- Caret position is changed during {@link #moveToLogicalPosition(LogicalPosition)} processing;
*- The client receives caret position change event and adjusts the position;
*- {@link #moveToLogicalPosition(LogicalPosition)} processing is finished;
*- {@link #moveToLogicalPosition(LogicalPosition)} reports an error because the caret is not located at the target offset;
*
'true'
, subsequent calls are guaranteed to return 'true'
* until selection is changed. 'Unknown direction' flag is automatically reset then.
*
* @param unknownDirection
*/
public void setUnknownDirection(boolean unknownDirection) {
myUnknownDirection = unknownDirection;
}
@Override
public int getSelectionStart() {
validateContext(false);
if (hasSelection()) {
MyRangeMarker marker = mySelectionMarker;
if (marker != null) {
return marker.getStartOffset();
}
}
return getOffset();
}
@NotNull
@Override
public VisualPosition getSelectionStartPosition() {
validateContext(false);
VisualPosition position;
if (hasSelection() && mySelectionMarker != null) {
position = mySelectionMarker.getStartPosition();
if (position == null) {
position = myEditor.offsetToVisualPosition(mySelectionMarker.getStartOffset());
}
}
else {
position = isVirtualSelectionEnabled() ? getVisualPosition() : myEditor.offsetToVisualPosition(getOffset());
}
if (hasVirtualSelection()) {
position = new VisualPosition(position.line, position.column + myStartVirtualOffset);
}
return position;
}
@Override
public int getSelectionEnd() {
validateContext(false);
if (hasSelection()) {
MyRangeMarker marker = mySelectionMarker;
if (marker != null) {
return marker.getEndOffset();
}
}
return getOffset();
}
@NotNull
@Override
public VisualPosition getSelectionEndPosition() {
validateContext(false);
VisualPosition position;
if (hasSelection() && mySelectionMarker != null) {
position = mySelectionMarker.getEndPosition();
if (position == null) {
position = myEditor.offsetToVisualPosition(mySelectionMarker.getEndOffset());
}
}
else {
position = isVirtualSelectionEnabled() ? getVisualPosition() : myEditor.offsetToVisualPosition(getOffset());
}
if (hasVirtualSelection()) {
position = new VisualPosition(position.line, position.column + myEndVirtualOffset);
}
return position;
}
@Override
public boolean hasSelection() {
validateContext(false);
MyRangeMarker marker = mySelectionMarker;
return marker != null && marker.isValid() && (marker.getEndOffset() > marker.getStartOffset()
|| isVirtualSelectionEnabled() && myEndVirtualOffset > myStartVirtualOffset);
}
@Override
public void setSelection(int startOffset, int endOffset) {
doSetSelection(myEditor.offsetToVisualPosition(startOffset), startOffset, myEditor.offsetToVisualPosition(endOffset), endOffset, false);
}
@Override
public void setSelection(int startOffset, @Nullable VisualPosition endPosition, int endOffset) {
VisualPosition startPosition;
if (hasSelection()) {
startPosition = getLeadSelectionPosition();
}
else {
startPosition = myEditor.offsetToVisualPosition(startOffset);
}
setSelection(startPosition, startOffset, endPosition, endOffset);
}
@Override
public void setSelection(@Nullable VisualPosition startPosition, int startOffset, @Nullable VisualPosition endPosition, int endOffset) {
VisualPosition startPositionToUse = startPosition == null ? myEditor.offsetToVisualPosition(startOffset) : startPosition;
VisualPosition endPositionToUse = endPosition == null ? myEditor.offsetToVisualPosition(endOffset) : endPosition;
doSetSelection(startPositionToUse, startOffset, endPositionToUse, endOffset, true);
}
private void doSetSelection(@NotNull final VisualPosition startPosition,
final int _startOffset,
@NotNull final VisualPosition endPosition,
final int _endOffset,
final boolean visualPositionAware)
{
myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
public void run() {
int startOffset = _startOffset;
int endOffset = _endOffset;
myUnknownDirection = false;
final Document doc = myEditor.getDocument();
validateContext(true);
myEditor.getSelectionModel().removeBlockSelection();
int textLength = doc.getTextLength();
if (startOffset < 0 || startOffset > textLength) {
LOG.error("Wrong startOffset: " + startOffset + ", textLength=" + textLength);
}
if (endOffset < 0 || endOffset > textLength) {
LOG.error("Wrong endOffset: " + endOffset + ", textLength=" + textLength);
}
if (!visualPositionAware && startOffset == endOffset) {
removeSelection();
return;
}
/* Normalize selection */
boolean switchedOffsets = false;
if (startOffset > endOffset) {
int tmp = startOffset;
startOffset = endOffset;
endOffset = tmp;
switchedOffsets = true;
}
FoldingModelEx foldingModel = myEditor.getFoldingModel();
FoldRegion startFold = foldingModel.getCollapsedRegionAtOffset(startOffset);
if (startFold != null && startFold.getStartOffset() < startOffset) {
startOffset = startFold.getStartOffset();
}
FoldRegion endFold = foldingModel.getCollapsedRegionAtOffset(endOffset);
if (endFold != null && endFold.getStartOffset() < endOffset) {
// All visual positions that lay at collapsed fold region placeholder are mapped to the same offset. Hence, there are
// at least two distinct situations - selection end is located inside collapsed fold region placeholder and just before it.
// We want to expand selection to the fold region end at the former case and keep selection as-is at the latest one.
endOffset = endFold.getEndOffset();
}
int oldSelectionStart;
int oldSelectionEnd;
if (hasSelection()) {
oldSelectionStart = getSelectionStart();
oldSelectionEnd = getSelectionEnd();
if (oldSelectionStart == startOffset && oldSelectionEnd == endOffset && !visualPositionAware) return;
}
else {
oldSelectionStart = oldSelectionEnd = getOffset();
}
MyRangeMarker marker = mySelectionMarker;
if (marker != null) {
marker.release();
}
marker = new MyRangeMarker((DocumentEx)doc, startOffset, endOffset);
myStartVirtualOffset = 0;
myEndVirtualOffset = 0;
if (visualPositionAware) {
if (endPosition.after(startPosition)) {
marker.setStartPosition(startPosition);
marker.setEndPosition(endPosition);
marker.setEndPositionIsLead(false);
}
else {
marker.setStartPosition(endPosition);
marker.setEndPosition(startPosition);
marker.setEndPositionIsLead(true);
}
if (isVirtualSelectionEnabled() &&
myEditor.getDocument().getLineNumber(startOffset) == myEditor.getDocument().getLineNumber(endOffset)) {
int endLineColumn = myEditor.offsetToVisualPosition(endOffset).column;
int startDiff =
EditorUtil.isAtLineEnd(myEditor, switchedOffsets ? endOffset : startOffset) ? startPosition.column - endLineColumn : 0;
int endDiff =
EditorUtil.isAtLineEnd(myEditor, switchedOffsets ? startOffset : endOffset) ? endPosition.column - endLineColumn : 0;
myStartVirtualOffset = Math.max(0, Math.min(startDiff, endDiff));
myEndVirtualOffset = Math.max(0, Math.max(startDiff, endDiff));
}
}
mySelectionMarker = marker;
myEditor.getSelectionModel().fireSelectionChanged(oldSelectionStart, oldSelectionEnd, startOffset, endOffset);
updateSystemSelection();
}
});
}
private void updateSystemSelection() {
if (GraphicsEnvironment.isHeadless()) return;
final Clipboard clip = myEditor.getComponent().getToolkit().getSystemSelection();
if (clip != null) {
clip.setContents(new StringSelection(myEditor.getSelectionModel().getSelectedText(true)), EmptyClipboardOwner.INSTANCE);
}
}
@Override
public void removeSelection() {
if (myEditor.isStickySelection()) {
// Most of our 'change caret position' actions (like move caret to word start/end etc) remove active selection.
// However, we don't want to do that for 'sticky selection'.
return;
}
myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
public void run() {
validateContext(true);
myEditor.getSelectionModel().removeBlockSelection();
int caretOffset = getOffset();
MyRangeMarker marker = mySelectionMarker;
if (marker != null) {
int startOffset = marker.getStartOffset();
int endOffset = marker.getEndOffset();
marker.release();
mySelectionMarker = null;
myStartVirtualOffset = 0;
myEndVirtualOffset = 0;
myEditor.getSelectionModel().fireSelectionChanged(startOffset, endOffset, caretOffset, caretOffset);
}
}
});
}
@Override
public int getLeadSelectionOffset() {
validateContext(false);
int caretOffset = getOffset();
if (hasSelection()) {
MyRangeMarker marker = mySelectionMarker;
if (marker != null) {
int startOffset = marker.getStartOffset();
int endOffset = marker.getEndOffset();
if (caretOffset != startOffset && caretOffset != endOffset) {
// Try to check if current selection is tweaked by fold region.
FoldingModelEx foldingModel = myEditor.getFoldingModel();
FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(caretOffset);
if (foldRegion != null) {
if (foldRegion.getStartOffset() == startOffset) {
return endOffset;
}
else if (foldRegion.getEndOffset() == endOffset) {
return startOffset;
}
}
}
if (caretOffset == endOffset) {
return startOffset;
}
else {
return endOffset;
}
}
}
return caretOffset;
}
@NotNull
@Override
public VisualPosition getLeadSelectionPosition() {
MyRangeMarker marker = mySelectionMarker;
VisualPosition caretPosition = getVisualPosition();
if (isVirtualSelectionEnabled() && !hasSelection()) {
return caretPosition;
}
if (marker == null) {
return caretPosition;
}
if (marker.isEndPositionIsLead()) {
VisualPosition result = marker.getEndPosition();
if (result == null) {
return getSelectionEndPosition();
}
else {
if (hasVirtualSelection()) {
result = new VisualPosition(result.line, result.column + myEndVirtualOffset);
}
return result;
}
}
else {
VisualPosition result = marker.getStartPosition();
if (result == null) {
return getSelectionStartPosition();
}
else {
if (hasVirtualSelection()) {
result = new VisualPosition(result.line, result.column + myStartVirtualOffset);
}
return result;
}
}
}
@Override
public void selectLineAtCaret() {
validateContext(true);
myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
public void run() {
SelectionModelImpl.doSelectLineAtCaret(myEditor);
}
});
}
@Override
public void selectWordAtCaret(final boolean honorCamelWordsSettings) {
validateContext(true);
myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
public void run() {
removeSelection();
final EditorSettings settings = myEditor.getSettings();
boolean camelTemp = settings.isCamelWords();
final boolean needOverrideSetting = camelTemp && !honorCamelWordsSettings;
if (needOverrideSetting) {
settings.setCamelWords(false);
}
try {
EditorActionHandler handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET);
handler.execute(myEditor, CaretImpl.this, myEditor.getDataContext());
}
finally {
if (needOverrideSetting) {
settings.resetCamelWords();
}
}
}
});
}
@Nullable
@Override
public String getSelectedText() {
if (!hasSelection()) {
return null;
}
CharSequence text = myEditor.getDocument().getCharsSequence();
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
String selectedText = text.subSequence(selectionStart, selectionEnd).toString();
if (isVirtualSelectionEnabled() && myEndVirtualOffset > myStartVirtualOffset) {
int padding = myEndVirtualOffset - myStartVirtualOffset;
StringBuilder builder = new StringBuilder(selectedText.length() + padding);
builder.append(selectedText);
for (int i = 0; i < padding; i++) {
builder.append(' ');
}
return builder.toString();
}
else {
return selectedText;
}
}
private void validateContext(boolean isWrite) {
if (!myEditor.getComponent().isShowing()) return;
if (isWrite) {
ApplicationManager.getApplication().assertIsDispatchThread();
}
else {
ApplicationManager.getApplication().assertReadAccessAllowed();
}
}
private boolean isVirtualSelectionEnabled() {
return myEditor.isColumnMode() && supportsMultipleCarets();
}
boolean hasVirtualSelection() {
validateContext(false);
MyRangeMarker marker = mySelectionMarker;
return marker != null && marker.isValid() && isVirtualSelectionEnabled() && myEndVirtualOffset > myStartVirtualOffset;
}
private int getCurrentX() {
return myEditor.visualPositionToXY(myVisibleCaret).x;
}
@Override
@NotNull
public EditorImpl getEditor() {
return myEditor;
}
@Override
public String toString() {
return "Caret at " + myVisibleCaret + (mySelectionMarker == null ? "" : (", selection marker: " + mySelectionMarker.toString()));
}
/**
* Encapsulates information about target vertical range info - its 'y'
coordinate and height in pixels.
*/
public static class VerticalInfo {
public final int y;
public final int height;
private VerticalInfo(int y, int height) {
this.y = y;
this.height = height;
}
}
private class MyRangeMarker extends RangeMarkerImpl {
private VisualPosition myStartPosition;
private VisualPosition myEndPosition;
private boolean myEndPositionIsLead;
private boolean myIsReleased;
MyRangeMarker(DocumentEx document, int start, int end) {
super(document, start, end, true);
myIsReleased = false;
}
public void release() {
myIsReleased = true;
dispose();
}
@Nullable
public VisualPosition getStartPosition() {
invalidateVisualPositions();
return myStartPosition;
}
public void setStartPosition(@NotNull VisualPosition startPosition) {
myStartPosition = startPosition;
}
@Nullable
public VisualPosition getEndPosition() {
invalidateVisualPositions();
return myEndPosition;
}
public void setEndPosition(@NotNull VisualPosition endPosition) {
myEndPosition = endPosition;
}
public boolean isEndPositionIsLead() {
return myEndPositionIsLead;
}
public void setEndPositionIsLead(boolean endPositionIsLead) {
myEndPositionIsLead = endPositionIsLead;
}
int startBefore;
int endBefore;
@Override
protected void changedUpdateImpl(DocumentEvent e) {
if (myIsReleased) return;
startBefore = getStartOffset();
endBefore = getEndOffset();
super.changedUpdateImpl(e);
}
private void invalidateVisualPositions() {
SoftWrapModelImpl model = myEditor.getSoftWrapModel();
if (!myEditor.offsetToVisualPosition(getStartOffset()).equals(myStartPosition) && model.getSoftWrap(getStartOffset()) == null
|| !myEditor.offsetToVisualPosition(getEndOffset()).equals(myEndPosition) && model.getSoftWrap(getEndOffset()) == null) {
myStartPosition = null;
myEndPosition = null;
}
}
}
}