blob: 574780a661c28c327f02dfb607144715be926b24 [file] [log] [blame]
/*
* 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.
*/
/*
* Created by IntelliJ IDEA.
* User: max
* Date: Apr 19, 2002
* Time: 1:51:41 PM
* To change template for new class use
* Code Style | Class Templates options (Tools | IDE Options).
*/
package com.intellij.openapi.editor.impl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.SelectionEvent;
import com.intellij.openapi.editor.event.SelectionListener;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class SelectionModelImpl implements SelectionModel, PrioritizedDocumentListener {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.SelectionModelImpl");
private final List<SelectionListener> mySelectionListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final EditorImpl myEditor;
private TextAttributes myTextAttributes;
private LogicalPosition myBlockStart;
private LogicalPosition myBlockEnd;
private int[] myBlockSelectionStarts;
private int[] myBlockSelectionEnds;
private DocumentEvent myIsInUpdate;
public SelectionModelImpl(EditorImpl editor) {
myEditor = editor;
}
@Override
public void beforeDocumentChange(DocumentEvent event) {
myIsInUpdate = event;
for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
((CaretImpl)caret).beforeDocumentChange();
}
}
@Override
public void documentChanged(DocumentEvent event) {
if (myIsInUpdate == event) {
myIsInUpdate = null;
myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
public void run() {
for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
((CaretImpl)caret).documentChanged();
}
}
});
}
}
@Override
public int getPriority() {
return EditorDocumentPriorities.SELECTION_MODEL;
}
/**
* @see CaretImpl#setUnknownDirection(boolean)
*/
public boolean isUnknownDirection() {
return myEditor.getCaretModel().getCurrentCaret().isUnknownDirection();
}
/**
* @see CaretImpl#setUnknownDirection(boolean)
*/
public void setUnknownDirection(boolean unknownDirection) {
myEditor.getCaretModel().getCurrentCaret().setUnknownDirection(unknownDirection);
}
@Override
public int getSelectionStart() {
return myEditor.getCaretModel().getCurrentCaret().getSelectionStart();
}
@NotNull
@Override
public VisualPosition getSelectionStartPosition() {
return myEditor.getCaretModel().getCurrentCaret().getSelectionStartPosition();
}
@Override
public int getSelectionEnd() {
return myEditor.getCaretModel().getCurrentCaret().getSelectionEnd();
}
@NotNull
@Override
public VisualPosition getSelectionEndPosition() {
return myEditor.getCaretModel().getCurrentCaret().getSelectionEndPosition();
}
@Override
public boolean hasSelection() {
return hasSelection(false);
}
@Override
public boolean hasSelection(boolean anyCaret) {
if (!anyCaret) {
return myEditor.getCaretModel().getCurrentCaret().hasSelection();
}
for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
if (caret.hasSelection()) {
return true;
}
}
return false;
}
@Override
public void setSelection(int startOffset, int endOffset) {
myEditor.getCaretModel().getCurrentCaret().setSelection(startOffset, endOffset);
}
@Override
public void setSelection(int startOffset, @Nullable VisualPosition endPosition, int endOffset) {
myEditor.getCaretModel().getCurrentCaret().setSelection(startOffset, endPosition, endOffset);
}
@Override
public void setSelection(@Nullable VisualPosition startPosition, int startOffset, @Nullable VisualPosition endPosition, int endOffset) {
myEditor.getCaretModel().getCurrentCaret().setSelection(startPosition, startOffset, endPosition, endOffset);
}
void fireSelectionChanged(int oldSelectionStart, int oldSelectionEnd, int startOffset, int endOffset) {
repaintBySelectionChange(oldSelectionStart, startOffset, oldSelectionEnd, endOffset);
SelectionEvent event = new SelectionEvent(myEditor,
oldSelectionStart, oldSelectionEnd,
startOffset, endOffset);
broadcastSelectionEvent(event);
}
private void broadcastSelectionEvent(SelectionEvent event) {
for (SelectionListener listener : mySelectionListeners) {
try {
listener.selectionChanged(event);
}
catch (Exception e) {
LOG.error(e);
}
}
}
private void repaintBySelectionChange(int oldSelectionStart, int startOffset, int oldSelectionEnd, int endOffset) {
myEditor.repaint(Math.min(oldSelectionStart, startOffset), Math.max(oldSelectionStart, startOffset));
myEditor.repaint(Math.min(oldSelectionEnd, endOffset), Math.max(oldSelectionEnd, endOffset));
}
@Override
public void removeSelection() {
removeSelection(false);
}
@Override
public void removeSelection(boolean allCarets) {
if (!allCarets) {
myEditor.getCaretModel().getCurrentCaret().removeSelection();
}
else {
for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
caret.removeSelection();
}
}
}
@Override
public void setBlockSelection(@NotNull LogicalPosition blockStart, @NotNull LogicalPosition blockEnd) {
if (myEditor.getCaretModel().supportsMultipleCarets()) {
int startLine = Math.max(Math.min(blockStart.line, myEditor.getDocument().getLineCount() - 1), 0);
int endLine = Math.max(Math.min(blockEnd.line, myEditor.getDocument().getLineCount() - 1), 0);
int step = endLine < startLine ? -1 : 1;
int count = 1 + Math.abs(endLine - startLine);
List<CaretState> caretStates = new LinkedList<CaretState>();
boolean hasSelection = false;
for (int line = startLine, i = 0; i < count; i++, line += step) {
int startColumn = blockStart.column;
int endColumn = blockEnd.column;
int lineEndOffset = myEditor.getDocument().getLineEndOffset(line);
LogicalPosition lineEndPosition = myEditor.offsetToLogicalPosition(lineEndOffset);
int lineWidth = lineEndPosition.column;
if (startColumn > lineWidth && endColumn > lineWidth && !myEditor.isColumnMode()) {
LogicalPosition caretPos = new LogicalPosition(line, Math.min(startColumn, endColumn));
caretStates.add(new CaretState(caretPos,
lineEndPosition,
lineEndPosition));
}
else {
LogicalPosition startPos = new LogicalPosition(line, myEditor.isColumnMode() ? startColumn : Math.min(startColumn, lineWidth));
LogicalPosition endPos = new LogicalPosition(line, myEditor.isColumnMode() ? endColumn : Math.min(endColumn, lineWidth));
int startOffset = myEditor.logicalPositionToOffset(startPos);
int endOffset = myEditor.logicalPositionToOffset(endPos);
caretStates.add(new CaretState(endPos, startPos, endPos));
hasSelection |= startOffset != endOffset;
}
}
if (hasSelection && !myEditor.isColumnMode()) { // filtering out lines without selection
Iterator<CaretState> caretStateIterator = caretStates.iterator();
while(caretStateIterator.hasNext()) {
CaretState state = caretStateIterator.next();
//noinspection ConstantConditions
if (state.getSelectionStart().equals(state.getSelectionEnd())) {
caretStateIterator.remove();
}
}
}
myEditor.getCaretModel().setCaretsAndSelections(caretStates);
}
else {
removeSelection();
int oldStartLine = 0;
int oldEndLine = 0;
if (hasBlockSelection()) {
oldStartLine = myBlockStart.line;
oldEndLine = myBlockEnd.line;
if (oldStartLine > oldEndLine) {
int t = oldStartLine;
oldStartLine = oldEndLine;
oldEndLine = t;
}
}
int newStartLine = blockStart.line;
int newEndLine = blockEnd.line;
if (newStartLine > newEndLine) {
int t = newStartLine;
newStartLine = newEndLine;
newEndLine = t;
}
myEditor.repaintLines(Math.min(oldStartLine, newStartLine), Math.max(newEndLine, oldEndLine));
final int[] oldStarts = getBlockSelectionStarts();
final int[] oldEnds = getBlockSelectionEnds();
myBlockStart = blockStart;
myBlockEnd = blockEnd;
recalculateBlockOffsets();
final int[] newStarts = getBlockSelectionStarts();
final int[] newEnds = getBlockSelectionEnds();
broadcastSelectionEvent(new SelectionEvent(myEditor, oldStarts, oldEnds, newStarts, newEnds));
}
}
@Override
public void removeBlockSelection() {
if (!myEditor.getCaretModel().supportsMultipleCarets()) {
myEditor.getCaretModel().getCurrentCaret().setUnknownDirection(false);
if (hasBlockSelection()) {
myEditor.repaint(0, myEditor.getDocument().getTextLength());
final int[] oldStarts = getBlockSelectionStarts();
final int[] oldEnds = getBlockSelectionEnds();
myBlockStart = null;
myBlockEnd = null;
final int[] newStarts = getBlockSelectionStarts();
final int[] newEnds = getBlockSelectionEnds();
broadcastSelectionEvent(new SelectionEvent(myEditor, oldStarts, oldEnds, newStarts, newEnds));
}
}
}
@Override
public boolean hasBlockSelection() {
return myBlockStart != null;
}
@Override
public LogicalPosition getBlockStart() {
return myBlockStart;
}
@Override
public LogicalPosition getBlockEnd() {
return myBlockEnd;
}
@Override
public boolean isBlockSelectionGuarded() {
if (!hasBlockSelection()) return false;
int[] starts = getBlockSelectionStarts();
int[] ends = getBlockSelectionEnds();
Document doc = myEditor.getDocument();
for (int i = 0; i < starts.length; i++) {
int start = starts[i];
int end = ends[i];
if (start == end && doc.getOffsetGuard(start) != null || start != end && doc.getRangeGuard(start, end) != null) {
return true;
}
}
return false;
}
@Override
public RangeMarker getBlockSelectionGuard() {
if (!hasBlockSelection()) return null;
int[] starts = getBlockSelectionStarts();
int[] ends = getBlockSelectionEnds();
Document doc = myEditor.getDocument();
for (int i = 0; i < starts.length; i++) {
int start = starts[i];
int end = ends[i];
if (start == end) {
RangeMarker guard = doc.getOffsetGuard(start);
if (guard != null) return guard;
}
if (start != end) {
RangeMarker guard = doc.getRangeGuard(start, end);
if (guard != null) return guard;
}
}
return null;
}
private void recalculateBlockOffsets() {
TIntArrayList startOffsets = new TIntArrayList();
TIntArrayList endOffsets = new TIntArrayList();
final int startLine = Math.min(myBlockStart.line, myBlockEnd.line);
final int endLine = Math.max(myBlockStart.line, myBlockEnd.line);
final int startColumn = Math.min(myBlockStart.column, myBlockEnd.column);
final int endColumn = Math.max(myBlockStart.column, myBlockEnd.column);
FoldingModelImpl foldingModel = myEditor.getFoldingModel();
DocumentEx document = myEditor.getDocument();
boolean insideFoldRegion = false;
for (int line = startLine; line <= endLine; line++) {
int startOffset = myEditor.logicalPositionToOffset(new LogicalPosition(line, startColumn));
FoldRegion startRegion = foldingModel.getCollapsedRegionAtOffset(startOffset);
boolean startInsideFold = startRegion != null && startRegion.getStartOffset() < startOffset;
int endOffset = myEditor.logicalPositionToOffset(new LogicalPosition(line, endColumn));
FoldRegion endRegion = foldingModel.getCollapsedRegionAtOffset(endOffset);
boolean endInsideFold = endRegion != null && endRegion.getStartOffset() < endOffset;
if (!startInsideFold && !endInsideFold) {
startOffsets.add(startOffset);
endOffsets.add(endOffset);
}
else if (startInsideFold && endInsideFold) {
if (insideFoldRegion) {
startOffsets.add(Math.max(document.getLineStartOffset(line), startRegion.getStartOffset()));
endOffsets.add(Math.min(document.getLineEndOffset(line), endRegion.getEndOffset()));
}
}
else if (startInsideFold && !endInsideFold) {
if (startRegion.getEndOffset() < endOffset) {
startOffsets.add(Math.max(document.getLineStartOffset(line), startRegion.getStartOffset()));
endOffsets.add(endOffset);
}
insideFoldRegion = false;
}
else {
startOffsets.add(startOffset);
endOffsets.add(Math.min(document.getLineEndOffset(line), endRegion.getEndOffset()));
insideFoldRegion = true;
}
}
myBlockSelectionStarts = startOffsets.toNativeArray();
myBlockSelectionEnds = endOffsets.toNativeArray();
}
@Override
@NotNull
public int[] getBlockSelectionStarts() {
if (myEditor.getCaretModel().supportsMultipleCarets()) {
Collection<Caret> carets = myEditor.getCaretModel().getAllCarets();
int[] result = new int[carets.size()];
int i = 0;
for (Caret caret : carets) {
result[i++] = caret.getSelectionStart();
}
return result;
} else {
if (hasSelection()) {
return new int[]{getSelectionStart()};
}
else if (!hasBlockSelection() || myBlockSelectionStarts == null) {
return ArrayUtil.EMPTY_INT_ARRAY;
}
else {
return myBlockSelectionStarts;
}
}
}
@Override
@NotNull
public int[] getBlockSelectionEnds() {
if (myEditor.getCaretModel().supportsMultipleCarets()) {
Collection<Caret> carets = myEditor.getCaretModel().getAllCarets();
int[] result = new int[carets.size()];
int i = 0;
for (Caret caret : carets) {
result[i++] = caret.getSelectionEnd();
}
return result;
} else {
if (hasSelection()) {
return new int[]{getSelectionEnd()};
}
else if (!hasBlockSelection() || myBlockSelectionEnds == null) {
return ArrayUtil.EMPTY_INT_ARRAY;
}
else {
return myBlockSelectionEnds;
}
}
}
@Override
public void addSelectionListener(SelectionListener listener) {
mySelectionListeners.add(listener);
}
public void addSelectionListener(final SelectionListener listener, Disposable parent) {
mySelectionListeners.add(listener);
Disposer.register(parent, new Disposable() {
@Override
public void dispose() {
mySelectionListeners.remove(listener);
}
});
}
@Override
public void removeSelectionListener(SelectionListener listener) {
boolean success = mySelectionListeners.remove(listener);
LOG.assertTrue(success);
}
@Override
public String getSelectedText() {
return getSelectedText(false);
}
@Override
public String getSelectedText(boolean allCarets) {
validateContext(false);
if (hasBlockSelection()) {
CharSequence text = myEditor.getDocument().getCharsSequence();
int[] starts = getBlockSelectionStarts();
int[] ends = getBlockSelectionEnds();
int width = myEditor.getCaretModel().supportsMultipleCarets() ? 0 : Math.abs(myBlockEnd.column - myBlockStart.column);
final StringBuilder buf = new StringBuilder();
for (int i = 0; i < starts.length; i++) {
if (i > 0) buf.append('\n');
final int len = ends[i] - starts[i];
appendCharSequence(buf, text, starts[i], len);
for (int j = len; j < width; j++) buf.append(' ');
}
return buf.toString();
}
else if (myEditor.getCaretModel().supportsMultipleCarets() && allCarets) {
final StringBuilder buf = new StringBuilder();
String separator = "";
for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
buf.append(separator);
String caretSelectedText = caret.getSelectedText();
if (caretSelectedText != null) {
buf.append(caretSelectedText);
}
separator = "\n";
}
return buf.toString();
}
else {
return myEditor.getCaretModel().getCurrentCaret().getSelectedText();
}
}
private static void appendCharSequence(@NotNull StringBuilder buf, @NotNull CharSequence s, int srcOffset, int len) {
if (srcOffset < 0 || len < 0 || srcOffset > s.length() - len) {
throw new IndexOutOfBoundsException("srcOffset " + srcOffset + ", len " + len + ", s.length() " + s.length());
}
if (len == 0) {
return;
}
final int limit = srcOffset + len;
for (int i = srcOffset; i < limit; i++) {
buf.append(s.charAt(i));
}
}
public static void doSelectLineAtCaret(Editor editor) {
int lineNumber = editor.getCaretModel().getLogicalPosition().line;
Document document = editor.getDocument();
if (lineNumber >= document.getLineCount()) {
return;
}
Pair<LogicalPosition, LogicalPosition> lines = EditorUtil.calcCaretLineRange(editor);
LogicalPosition lineStart = lines.first;
LogicalPosition nextLineStart = lines.second;
int start = editor.logicalPositionToOffset(lineStart);
int end = editor.logicalPositionToOffset(nextLineStart);
//myEditor.getCaretModel().moveToOffset(start);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().removeSelection();
editor.getSelectionModel().setSelection(start, end);
}
@Override
public int getLeadSelectionOffset() {
return myEditor.getCaretModel().getCurrentCaret().getLeadSelectionOffset();
}
@NotNull
@Override
public VisualPosition getLeadSelectionPosition() {
return myEditor.getCaretModel().getCurrentCaret().getLeadSelectionPosition();
}
@Override
public void selectLineAtCaret() {
myEditor.getCaretModel().getCurrentCaret().selectLineAtCaret();
}
@Override
public void selectWordAtCaret(boolean honorCamelWordsSettings) {
myEditor.getCaretModel().getCurrentCaret().selectWordAtCaret(honorCamelWordsSettings);
}
@Override
public void copySelectionToClipboard() {
EditorCopyPasteHelper.getInstance().copySelectionToClipboard(myEditor);
}
@Override
public TextAttributes getTextAttributes() {
if (myTextAttributes == null) {
TextAttributes textAttributes = new TextAttributes();
EditorColorsScheme scheme = myEditor.getColorsScheme();
textAttributes.setForegroundColor(scheme.getColor(EditorColors.SELECTION_FOREGROUND_COLOR));
textAttributes.setBackgroundColor(scheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR));
myTextAttributes = textAttributes;
}
return myTextAttributes;
}
public void reinitSettings() {
myTextAttributes = null;
}
private void validateContext(boolean isWrite) {
if (!myEditor.getComponent().isShowing()) return;
if (isWrite) {
ApplicationManager.getApplication().assertIsDispatchThread();
}
else {
ApplicationManager.getApplication().assertReadAccessAllowed();
}
}
}