/* * 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.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.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 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 caretStates = new LinkedList(); 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 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 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 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); } @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 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() { CopyPasteSupport.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(); } } }