Files
openide/java/java-tests/testData/codeInsight/enterAction/Performance.java
Daniil Ovchinnikov c973850ddc IJPL-255 replace Application.assertIsDispatchThread -> ThreadingAssertions.assertEventDispatchThread
GitOrigin-RevId: da80a0648ffce76e93f35f72afc93334b21fe9a2
2023-09-22 00:08:26 +00:00

3845 lines
128 KiB
Java

package com.intellij.openapi.editor.impl;
import com.intellij.Patches;
import com.intellij.codeInsight.hint.DocumentFragmentTooltipRenderer;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.hint.TooltipController;
import com.intellij.codeInsight.hint.TooltipGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.DataConstants;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.util.concurrency.ThreadingAssertions;
import com.intellij.util.concurrency.ThreadingAssertions;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.actionSystem.EditorAction;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter;
import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JScrollPane2;
import com.intellij.util.Alarm;
import com.intellij.util.IJSwingUtilities;
import gnu.trove.THashMap;
import gnu.trove.TIntArrayList;
import org.jdom.Element;
import javax.swing.plaf.ScrollBarUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.font.TextHitInfo;
import java.awt.im.InputMethodRequests;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
public final class EditorImpl implements EditorEx {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
private static final Key DND_COMMAND_KEY = Key.create("DndCommand");
private final DocumentImpl myDocument;
private JPanel myPanel;
private JScrollPane myScrollPane;
private EditorComponentImpl myEditorComponent;
private EditorGutterComponentImpl myGutterComponent;
private CommandProcessor myCommandProcessor;
private MyScrollBar myVerticalScrollBar;
private MyScrollBar myHorizontalScrollBar;
private ArrayList<EditorMouseListener> myMouseListeners = new ArrayList<EditorMouseListener>();
private ArrayList<EditorMouseMotionListener> myMouseMotionListeners = new ArrayList<EditorMouseMotionListener>();
private int myCharHeight = -1;
private int myLineHeight = -1;
private int myDescent = -1;
private boolean myIsInsertMode = true;
private final CaretCursor myCaretCursor;
private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
private final Key<Object> MOUSE_DRAGGED_GROUP = Key.create("MouseDraggedGroup");
private final Map<Object, Object> myUserDataMap = new THashMap<Object, Object>();
private final DocumentListener myEditorDocumentAdapter;
private SettingsImpl mySettings;
private boolean isReleased = false;
private MouseEvent myMousePressedEvent = null;
private int mySavedSelectionStart = -1;
private int mySavedSelectionEnd = -1;
private int myLastColumnNumber = 0;
private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
private MyEditable myEditable;
private EditorColorsScheme myScheme;
private final boolean myIsViewer;
private final SelectionModelImpl mySelectionModel;
private final EditorMarkupModelImpl myMarkupModel;
private final FoldingModelImpl myFoldingModel;
private final ScrollingModelImpl myScrollingModel;
private final CaretModelImpl myCaretModel;
private static final RepaintCursorThread ourCaretThread;
// private final BorderEffect myBorderEffect = new BorderEffect();
private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
private FoldRegion myMouseSelectedRegion = null;
private static final int MOUSE_SELECTION_STATE_NONE = 0;
private static final int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
private final MarkupModelListener myMarkupModelListener;
private EditorHighlighter myHighlighter;
private int myScrollbarOrientation;
private boolean myMousePressedInsideSelection;
private FontMetrics myPlainFontMetrics;
private FontMetrics myBoldFontMetrics;
private FontMetrics myItalicFontMetrics;
private FontMetrics myBoldItalicFontMetrics;
private static final int CACHED_CHARS_BUFFER_SIZE = 300;
private ArrayList<CachedFontContent> myFontCache = null;
private FontInfo myCurrentFontType = null;
private boolean myIsBlockSelectionMode;
private EditorSizeContainer mySizeContainer = new EditorSizeContainer();
private Runnable myCursorUpdater;
private int myCaretUpdateVShift;
Project myProject;
private long myMouseSelectionChangeTimestamp;
private int mySavedCaretOffsetForDNDUndoHack;
private ArrayList<FocusChangeListener> myFocusListeners = new ArrayList<FocusChangeListener>();
private MyInputMethodHandler myInputMethodRequestsHandler;
private InputMethodRequests myInputMethodRequestsSwingWrapper;
private boolean myIsOneLineMode;
private boolean myIsRendererMode;
private VirtualFile myVirtualFile;
private boolean myIsColumnMode = false;
private Color myForcedBackground = null;
private Dimension myPreferredSize;
private Runnable myGutterSizeUpdater = null;
private Alarm myAppleRepaintAlarm;
private boolean myEmbeddedIntoDialogWrapper;
static {
ourCaretThread = new RepaintCursorThread();
ourCaretThread.start();
}
public EditorImpl(Document document, boolean viewer, Project project) {
myProject = project;
myDocument = (DocumentImpl)document;
myScheme = new MyColorSchemeDelegate();
myIsViewer = viewer;
mySettings = new SettingsImpl(this);
mySelectionModel = new SelectionModelImpl(this);
myMarkupModel = new EditorMarkupModelImpl(this);
myFoldingModel = new FoldingModelImpl(this);
myCaretModel = new CaretModelImpl(this);
myIsBlockSelectionMode = false;
mySizeContainer.reset();
myCommandProcessor = CommandProcessor.getInstance();
myEditorDocumentAdapter = new EditorDocumentAdapter();
myMarkupModelListener = new MarkupModelListener() {
public void rangeHighlighterChanged(MarkupModelEvent event) {
RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl)event.getHighlighter();
if (rangeHighlighter.isValid()) {
int start = rangeHighlighter.getAffectedAreaStartOffset();
int end = rangeHighlighter.getAffectedAreaEndOffset();
int startLine = myDocument.getLineNumber(start);
int endLine = myDocument.getLineNumber(end);
repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount()));
}
else {
repaint(0, getDocument().getTextLength());
}
((EditorMarkupModelImpl)getMarkupModel()).repaint();
((EditorMarkupModelImpl)getMarkupModel()).markDirtied();
GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
if (renderer != null) {
updateGutterSize();
}
updateCaretCursor();
}
};
((MarkupModelImpl)myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
((MarkupModelImpl)getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
myDocument.addDocumentListener(myFoldingModel);
myDocument.addDocumentListener(myCaretModel);
myDocument.addDocumentListener(mySelectionModel);
myDocument.addDocumentListener(myEditorDocumentAdapter);
myCaretCursor = new CaretCursor();
myFoldingModel.flushCaretShift();
myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
setHighlighter(highlighter);
initComponent();
myScrollingModel = new ScrollingModelImpl(this);
myGutterComponent.updateSize();
myEditorComponent.setSize(getContentSize());
Dimension preferredSize = getPreferredSize();
myScrollPane.getViewport().setViewSize(preferredSize);
if (Patches.APPLE_BUG_ID_3716835) {
myScrollingModel.addVisibleAreaListener(new VisibleAreaListener() {
public void visibleAreaChanged(VisibleAreaEvent e) {
if (myAppleRepaintAlarm == null) {
myAppleRepaintAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
}
myAppleRepaintAlarm.cancelAllRequests();
myAppleRepaintAlarm.addRequest(new Runnable() {
public void run() {
repaint(0, myDocument.getTextLength());
}
}, 50, ModalityState.stateForComponent(myEditorComponent));
}
});
}
updateCaretCursor();
// This hacks context layout problem where editor appears scrolled to the right just after it is created.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myScrollingModel.disableAnimation();
myScrollingModel.scrollHorizontally(0);
myScrollingModel.enableAnimation();
}
});
}
public boolean isViewer() {
return myIsViewer || myIsRendererMode;
}
public boolean isRendererMode() {
return myIsRendererMode;
}
public void setRendererMode(boolean isRendererMode) {
myIsRendererMode = isRendererMode;
}
public void setFile(VirtualFile vFile) {
myVirtualFile = vFile;
reinitSettings();
}
public VirtualFile getVirtualFile() {
return myVirtualFile;
}
public SelectionModel getSelectionModel() {
return mySelectionModel;
}
public MarkupModel getMarkupModel() {
return myMarkupModel;
}
public FoldingModel getFoldingModel() {
return myFoldingModel;
}
public CaretModel getCaretModel() {
return myCaretModel;
}
public ScrollingModel getScrollingModel() {
return myScrollingModel;
}
public EditorSettings getSettings() {
ThreadingAssertions.assertEventDispatchThread();
return mySettings;
}
public void reinitSettings() {
ThreadingAssertions.assertEventDispatchThread();
myCharHeight = -1;
myLineHeight = -1;
myDescent = -1;
myPlainFontMetrics = null;
myCaretModel.reinitSettings();
mySelectionModel.reinitSettings();
mySettings.reinitSettings();
ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
mySizeContainer.reset();
myFoldingModel.refreshSettings();
myFoldingModel.rebuild();
myHighlighter.setColorScheme(myScheme);
myGutterComponent.reinitSettings();
myGutterComponent.revalidate();
myEditorComponent.repaint();
updateCaretCursor();
}
public void release() {
isReleased = true;
myDocument.removeDocumentListener(myHighlighter);
myDocument.removeDocumentListener(myEditorDocumentAdapter);
myDocument.removeDocumentListener(myFoldingModel);
myDocument.removeDocumentListener(myCaretModel);
myDocument.removeDocumentListener(mySelectionModel);
MarkupModelImpl markupModel = (MarkupModelImpl)myDocument.getMarkupModel(myProject, false);
if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
myMarkupModel.dispose();
myLineHeight = -1;
myCharHeight = -1;
myDescent = -1;
myPlainFontMetrics = null;
myScrollingModel.dispose();
myGutterComponent.dispose();
}
public <T> void putUserData(Key<T> key, T value) {
if (value != null) {
myUserDataMap.put(key, value);
}
else {
myUserDataMap.remove(key);
}
}
public <T> T getUserData(Key<T> key) {
return (T)myUserDataMap.get(key);
}
private void initComponent() {
myEditorComponent = new EditorComponentImpl(this);
// myStatusBar = new EditorStatusBarImpl();
myScrollPane = new JScrollPane2() {
protected void processMouseWheelEvent(MouseWheelEvent e) {
if (mySettings.isWheelFontChangeEnabled()) {
boolean changeFontSize = SystemInfo.isMac
? !e.isControlDown() && e.isMetaDown() && !e.isAltDown() && !e.isShiftDown()
: e.isControlDown() && !e.isMetaDown() && !e.isAltDown() && !e.isShiftDown();
if (changeFontSize) {
setFontSize(myScheme.getEditorFontSize() + e.getWheelRotation());
return;
}
}
super.processMouseWheelEvent(e);
}
};
myPanel = new JPanel() {
public void addNotify() {
super.addNotify();
if (((JComponent)getParent()).getBorder() != null) myScrollPane.setBorder(null);
}
};
//myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
myPanel.setLayout(new BorderLayout());
myVerticalScrollBar = new MyScrollBar(JScrollBar.VERTICAL);
myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
myGutterComponent = new EditorGutterComponentImpl(this);
myGutterComponent.setOpaque(true);
myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
myScrollPane.setViewportView(myEditorComponent);
//myScrollPane.setBorder(null);
myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
myScrollPane.setRowHeaderView(myGutterComponent);
stopOptimizedScrolling();
myEditorComponent.setTransferHandler(new MyTransferHandler());
myEditorComponent.setAutoscrolls(true);
/* Default mode till 1.4.0
* myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
*/
myPanel.add(myScrollPane);
myEditorComponent.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent event) {
if (Patches.APPLE_BUG_ID_3337563) return; // Everything is going through InputMethods under MacOS X in JDK releases earlier than 1.4.2_03-117.1
if (event.isConsumed()) {
return;
}
if (processKeyTyped(event)) {
event.consume();
}
}
});
MyMouseAdapter mouseAdapter = new MyMouseAdapter();
myEditorComponent.addMouseListener(mouseAdapter);
myGutterComponent.addMouseListener(mouseAdapter);
MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
myEditorComponent.addMouseMotionListener(mouseMotionListener);
myGutterComponent.addMouseMotionListener(mouseMotionListener);
myEditorComponent.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
myCaretCursor.activate();
int caretLine = getCaretModel().getLogicalPosition().line;
repaintLines(caretLine, caretLine);
fireFocusGained();
}
public void focusLost(FocusEvent e) {
synchronized (ourCaretThread) {
if (ourCaretThread.myEditor == EditorImpl.this) {
ourCaretThread.myEditor = null;
}
}
int caretLine = getCaretModel().getLogicalPosition().line;
repaintLines(caretLine, caretLine);
fireFocusLost();
}
});
// myBorderEffect.reset();
try {
myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
public void drop(DropTargetDropEvent dtde) {
}
public void dragOver(DropTargetDragEvent dtde) {
Point location = dtde.getLocation();
moveCaretToScreenPos(location.x, location.y);
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
});
}
catch (TooManyListenersException e) {
}
}
public void setFontSize(final int fontSize) {
int oldFontSize = myScheme.getEditorFontSize();
myScheme.setEditorFontSize(fontSize);
myPropertyChangeSupport.firePropertyChange(EditorEx.PROP_FONT_SIZE, oldFontSize, fontSize);
}
private void processKeyTyped(char c) {
// [vova] This is patch for Mac OS X. Under Mac "input methods"
// is handled before our EventQueue consume upcoming KeyEvents.
IdeEventQueue queue = IdeEventQueue.getInstance();
if (queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()) {
return;
}
ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
DataContext dataContext = getDataContext();
actionManager.fireBeforeEditorTyping(c, dataContext);
EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
}
private void fireFocusLost() {
FocusChangeListener[] listeners = getFocusListeners();
for (int i = 0; i < listeners.length; i++) {
FocusChangeListener listener = listeners[i];
listener.focusLost(this);
}
}
private FocusChangeListener[] getFocusListeners() {
return myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
}
private void fireFocusGained() {
FocusChangeListener[] listeners = getFocusListeners();
for (int i = 0; i < listeners.length; i++) {
FocusChangeListener listener = listeners[i];
listener.focusGained(this);
}
}
public void setHighlighter(EditorHighlighter highlighter) {
ThreadingAssertions.assertEventDispatchThread();
if (myHighlighter != null) {
getDocument().removeDocumentListener(myHighlighter);
}
getDocument().addDocumentListener(highlighter);
highlighter.setText(getDocument().getCharsSequence());
myHighlighter = highlighter;
myHighlighter.setEditor(this);
if (myPanel != null) {
reinitSettings();
}
}
public EditorHighlighter getHighlighter() {
ThreadingAssertions.assertEventDispatchThread();
return myHighlighter;
}
public JComponent getContentComponent() {
return myEditorComponent;
}
public EditorGutterComponentEx getGutterComponentEx() {
return myGutterComponent;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
myPropertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
myPropertyChangeSupport.removePropertyChangeListener(listener);
}
public void setInsertMode(boolean mode) {
ThreadingAssertions.assertEventDispatchThread();
boolean oldValue = myIsInsertMode;
myIsInsertMode = mode;
myPropertyChangeSupport.firePropertyChange(EditorEx.PROP_INSERT_MODE, oldValue, mode);
//Repaint the caret line by moving caret to the same place
LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
getCaretModel().moveToLogicalPosition(caretPosition);
}
public boolean isInsertMode() {
return myIsInsertMode;
}
public void setColumnMode(boolean mode) {
ThreadingAssertions.assertEventDispatchThread();
boolean oldValue = myIsColumnMode;
myIsColumnMode = mode;
myPropertyChangeSupport.firePropertyChange(PROP_COLUMN_MODE, oldValue, mode);
}
public boolean isColumnMode() {
return myIsColumnMode;
}
public void setBlockSelectionMode(boolean isBlockSelectionMode) {
myIsBlockSelectionMode = isBlockSelectionMode;
}
public boolean isBlockSelectionMode() {
return myIsBlockSelectionMode;
}
private int yPositionToVisibleLineNumber(int y) {
return y / getLineHeight();
}
public int getSpaceWidth(int fontType) {
int width = charWidth(' ', fontType);
return width > 0 ? width : 1;
}
public VisualPosition xyToVisualPosition(Point p) {
int line = yPositionToVisibleLineNumber(p.y);
int x = 0;
int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
int textLength = myDocument.getTextLength();
if (offset >= textLength) return new VisualPosition(line, 0);
int column = 0;
int prevX = 0;
CharSequence text = myDocument.getCharsNoThreadCheck();
char c = ' ';
IterationState state = new IterationState(this, offset, false);
int fontType = state.getMergedAttributes().getFontType();
int spaceSize = getSpaceWidth(fontType);
outer:
while (true) {
if (offset >= textLength) break;
if (offset >= state.getEndOffset()) {
state.advance();
fontType = state.getMergedAttributes().getFontType();
}
FoldRegion region = state.getCurrentFold();
if (region != null) {
char[] placeholder = region.getPlaceholderText().toCharArray();
for (int j = 0; j < placeholder.length; j++) {
c = placeholder[j];
x += charWidth(c, fontType);
if (x >= p.x) break outer;
column++;
}
offset = region.getEndOffset();
}
else {
prevX = x;
c = text.charAt(offset);
if (c == '\n') {
break;
}
if (c == '\t') {
x = nextTabStop(x);
}
else {
x += charWidth(c, fontType);
}
if (x >= p.x) break;
if (c == '\t') {
column += (x - prevX) / spaceSize;
}
else {
column++;
}
offset++;
}
}
int charWidth = charWidth(c, fontType);
if (x >= p.x && c == '\t') {
if (mySettings.isCaretInsideTabs()) {
column += (p.x - prevX) / spaceSize;
if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
}
else {
if ((x - p.x) * 2 < x - prevX) {
column += (x - prevX) / spaceSize;
}
}
}
else {
if (x >= p.x) {
if ((x - p.x) * 2 < charWidth) column++;
}
else {
column += (p.x - x) / getSpaceWidth(fontType);
}
}
return new VisualPosition(line, column);
}
public VisualPosition offsetToVisualPosition(int offset) {
return logicalToVisualPosition(offsetToLogicalPosition(offset));
}
public LogicalPosition offsetToLogicalPosition(int offset) {
int line = calcLogicalLineNumber(offset);
int column = calcColumnNumber(offset, line);
return new LogicalPosition(line, column);
}
public LogicalPosition xyToLogicalPosition(Point p) {
final Point pp;
if (p.x >= 0 && p.y >= 0) {
pp = p;
}
else {
pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
}
return visualToLogicalPosition(xyToVisualPosition(pp));
}
public Point logicalPositionToXY(LogicalPosition pos) {
VisualPosition visible = logicalToVisualPosition(pos);
int y = visibleLineNumberToYPosition(visible.line);
int lineStartOffset;
if (pos.line == 0) {
lineStartOffset = 0;
}
else {
if (pos.line >= myDocument.getLineCount()) {
lineStartOffset = myDocument.getTextLength();
}
else {
lineStartOffset = myDocument.getLineStartOffset(pos.line);
}
}
int x = getTabbedTextWidth(lineStartOffset, visible);
return new Point(x, y);
}
public Point visualPositionToXY(VisualPosition visible) {
int y = visibleLineNumberToYPosition(visible.line);
int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
int lineStartOffset;
if (logLine == 0) {
lineStartOffset = 0;
}
else {
if (logLine >= myDocument.getLineCount()) {
lineStartOffset = myDocument.getTextLength();
}
else {
lineStartOffset = myDocument.getLineStartOffset(logLine);
}
}
int x = getTabbedTextWidth(lineStartOffset, visible);
return new Point(x, y);
}
private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
if (pos.column == 0) return 0;
int x = 0;
int offset = lineStartOffset;
CharSequence text = myDocument.getCharsNoThreadCheck();
int textLength = myDocument.getTextLength();
IterationState state = new IterationState(this, offset, false);
int fontType = state.getMergedAttributes().getFontType();
int spaceSize = getSpaceWidth(fontType);
int column = 0;
outer:
while (column < pos.column) {
if (offset >= textLength) break;
if (offset >= state.getEndOffset()) {
state.advance();
fontType = state.getMergedAttributes().getFontType();
}
FoldRegion region = state.getCurrentFold();
if (region != null) {
char[] placeholder = region.getPlaceholderText().toCharArray();
for (int j = 0; j < placeholder.length; j++) {
x += charWidth(placeholder[j], fontType);
column++;
if (column >= pos.column) break outer;
}
offset = region.getEndOffset();
}
else {
char c = text.charAt(offset);
if (c == '\n') {
break;
}
if (c == '\t') {
int prevX = x;
x = nextTabStop(x);
column += (x - prevX) / spaceSize;
}
else {
x += charWidth(c, fontType);
column++;
}
offset++;
}
}
if (column != pos.column) {
x += getSpaceWidth(fontType) * (pos.column - column);
}
return x;
}
public int nextTabStop(int x) {
int tabSize = mySettings.getTabSize(myProject);
if (tabSize <= 0) {
tabSize = 1;
}
tabSize *= getSpaceWidth(Font.PLAIN);
int nTabs = x / tabSize;
return (nTabs + 1) * tabSize;
}
public int visibleLineNumberToYPosition(int lineNum) {
if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
return lineNum * getLineHeight();
}
public void repaint(int startOffset, int endOffset) {
ThreadingAssertions.assertEventDispatchThread();
if (myScrollPane == null) {
return;
}
if (endOffset > myDocument.getTextLength()) {
endOffset = myDocument.getTextLength();
}
if (startOffset < endOffset) {
int startLine = myDocument.getLineNumber(startOffset);
int endLine = myDocument.getLineNumber(endOffset);
repaintLines(startLine, endLine);
}
}
public void repaintLines(int startLine, int endLine) {
Rectangle visibleRect = getScrollingModel().getVisibleArea();
int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
myEditorComponent.repaintEditorComponent(visibleRect.x,
yStartLine,
visibleRect.x + visibleRect.width,
yEndLine - yStartLine);
myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
}
private void beforeChangedUpdate(DocumentEvent e) {
Rectangle viewRect = getScrollingModel().getVisibleArea();
Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
myCaretUpdateVShift = pos.y - viewRect.y;
mySizeContainer.beforeChange(e);
}
private void changedUpdate(DocumentEvent e) {
if (myScrollPane == null) return;
stopOptimizedScrolling();
mySelectionModel.removeBlockSelection();
mySizeContainer.changedUpdate(e);
int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
if (myDocument.getTextLength() > 0) {
int startDocLine = myDocument.getLineNumber(e.getOffset());
int endDocLine = myDocument.getLineNumber(e.getOffset() + e.getNewLength());
if (e.getOldLength() > e.getNewLength() || startDocLine != endDocLine) {
updateGutterSize();
}
}
updateCaretCursor();
repaintLines(startVisualLine, endVisualLine);
Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
int scrollOffset = caretLocation.y - myCaretUpdateVShift;
getScrollingModel().scrollVertically(scrollOffset);
}
private void updateGutterSize() {
if (myGutterSizeUpdater != null) return;
myGutterSizeUpdater = new Runnable() {
public void run() {
myGutterComponent.updateSize();
myGutterSizeUpdater = null;
}
};
}
void validateSize() {
if (myGutterSizeUpdater != null) {
SwingUtilities.invokeLater(myGutterSizeUpdater);
}
Dimension dim = getPreferredSize();
if (!dim.equals(myPreferredSize)) {
myPreferredSize = dim;
stopOptimizedScrolling();
myScrollPane.getViewport().setViewSize(myPreferredSize);
int lineNum = Math.max(1, getDocument().getLineCount());
myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth(Integer.toString(lineNum + 2)) + 6);
final JViewport rowHeader = myScrollPane.getRowHeader();
if (rowHeader != null) {
rowHeader.setViewSize(myGutterComponent.getPreferredSize());
}
getScrollPane().revalidate();
myEditorComponent.repaint();
myScrollPane.repaint();
myMarkupModel.repaint();
}
}
void recalcSizeAndRepaint() {
mySizeContainer.reset();
validateSize();
Dimension size = getPreferredSize();
myScrollPane.getVerticalScrollBar().setMaximum(size.height);
myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
getScrollPane().revalidate();
myMarkupModel.repaint();
stopOptimizedScrolling();
myEditorComponent.repaintEditorComponent();
myGutterComponent.repaint();
}
public Document getDocument() {
return myDocument;
}
public JComponent getComponent() {
return myPanel;
}
public void addEditorMouseListener(EditorMouseListener listener) {
myMouseListeners.add(listener);
}
public void removeEditorMouseListener(EditorMouseListener listener) {
boolean success = myMouseListeners.remove(listener);
LOG.assertTrue(success);
}
public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
myMouseMotionListeners.add(listener);
}
public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
boolean success = myMouseMotionListeners.remove(listener);
LOG.assertTrue(success);
}
public boolean isDisposed() {
return isReleased;
}
public void paint(Graphics g) {
validateSize();
startOptimizedScrolling();
if (myCursorUpdater != null) {
myCursorUpdater.run();
myCursorUpdater = null;
}
Rectangle clip = getClipBounds(g);
if (clip == null) {
return;
}
Rectangle viewRect = getScrollingModel().getVisibleArea();
if (viewRect == null) {
return;
}
if (isReleased) {
g.setColor(new Color(128, 255, 128));
g.fillRect(clip.x, clip.y, clip.width, clip.height);
return;
}
Color background = getBackroundColor();
g.setColor(background);
g.fillRect(clip.x, clip.y, clip.width, clip.height);
paintBackgrounds(g, clip);
paintRectangularSelection(g);
paintRightMargin(g, clip);
final MarkupModel docMarkup = myDocument.getMarkupModel(myProject);
if (docMarkup != null) {
paintLineMarkersSeparators(g, clip, docMarkup);
}
paintLineMarkersSeparators(g, clip, myMarkupModel);
paintText(g, clip);
paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
BorderEffect borderEffect = new BorderEffect(this, g);
borderEffect.paintHighlighters(getHighlighter());
if (docMarkup != null) {
borderEffect.paintHighlighters(docMarkup.getAllHighlighters());
}
borderEffect.paintHighlighters((getMarkupModel()).getAllHighlighters());
paintCaretCursor(g);
paintComposedTextDecoration((Graphics2D)g);
}
public void setBackgroundColor(Color color) {
myForcedBackground = color;
}
public void resetBackgourndColor() {
myForcedBackground = null;
}
public Color getForegroundColor() {
return myScheme.getDefaultForeground();
}
public Color getBackroundColor() {
if (myForcedBackground != null) return myForcedBackground;
Color color = myScheme.getDefaultBackground();
if (myDocument.isWritable()) {
return color;
}
Color readOnlyColor = myScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR);
return readOnlyColor != null ? readOnlyColor : color;
}
private void paintComposedTextDecoration(Graphics2D g) {
if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
VisualPosition visStart = offsetToVisualPosition(
Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
Point p1 = visualPositionToXY(visStart);
Point p2 = logicalPositionToXY(
offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
Stroke saved = g.getStroke();
BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2, 0, 2},
0);
g.setStroke(dotted);
g.drawLine(p1.x, y, p2.x, y);
g.setStroke(saved);
}
}
private static Rectangle getClipBounds(Graphics g) {
return g.getClipBounds();
}
private void paintRightMargin(Graphics g, Rectangle clip) {
Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
if (!mySettings.isRightMarginShown() || rightMargin == null) {
return;
}
int x = mySettings.getRightMargin(myProject) * getSpaceWidth(Font.PLAIN);
if (x >= clip.x && x < clip.x + clip.width) {
g.setColor(rightMargin);
g.drawLine(x, clip.y, x, clip.y + clip.height);
}
}
private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
if (myDocument.getLineCount() == 0) return;
int startLineNumber = yPositionToVisibleLineNumber(clip.y);
int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
RangeHighlighter[] segmentHighlighters;
final MarkupModel docMarkup = myDocument.getMarkupModel(myProject);
if (docMarkup != null) {
segmentHighlighters = docMarkup.getAllHighlighters();
for (int i = 0; i < segmentHighlighters.length; i++) {
paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighters[i], startLineNumber,
endLineNumber);
}
}
segmentHighlighters = getMarkupModel().getAllHighlighters();
for (int i = 0; i < segmentHighlighters.length; i++) {
RangeHighlighter segmentHighlighter = segmentHighlighters[i];
paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighter, startLineNumber, endLineNumber);
}
}
private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
int startLineNumber, int endLineNumber) {
if (!segmentHighlighter.isValid()) {
return;
}
if (segmentHighlighter.isAfterEndOfLine()) {
int startOffset = segmentHighlighter.getStartOffset();
int visibleStartLine = offsetToVisualPosition(startOffset).line;
if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
if (visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
int logStartLine = offsetToLogicalPosition(startOffset).line;
LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
Point end = logicalPositionToXY(logPosition);
int charWidth = getSpaceWidth(Font.PLAIN);
int lineHeight = getLineHeight();
TextAttributes attributes = segmentHighlighter.getTextAttributes();
if (attributes != null && attributes.getBackgroundColor() != null) {
g.setColor(attributes.getBackgroundColor());
g.fillRect(end.x, end.y, charWidth, lineHeight);
}
if (attributes != null && attributes.getEffectColor() != null) {
int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
g.setColor(attributes.getEffectColor());
if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
drawWave(g, end.x, end.x + charWidth - 1, y);
}
else if (attributes.getEffectType() != EffectType.BOXED) {
g.drawLine(end.x, y, end.x + charWidth - 1, y);
}
}
}
}
}
}
public int getMaxWidthInRange(int startOffset, int endOffset) {
int width = 0;
VisualPosition start = offsetToVisualPosition(startOffset);
VisualPosition end = offsetToVisualPosition(endOffset);
for (int i = start.line; i <= end.line; i++) {
int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
if (lineWidth > width) {
width = lineWidth;
}
}
return width;
}
private void paintBackgrounds(Graphics g, Rectangle clip) {
int lineHeight = getLineHeight();
int visibleLineNumber = clip.y / lineHeight;
int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
if (startLineNumber >= myDocument.getLineCount() || startLineNumber < 0) {
return;
}
int start = myDocument.getLineStartOffset(startLineNumber);
IterationState iterationState = new IterationState(this, start, true);
LineIterator lIterator = createLineIterator();
lIterator.start(start);
if (lIterator.atEnd()) {
return;
}
TextAttributes attributes = iterationState.getMergedAttributes();
Color backColor = attributes.getBackgroundColor();
Point position = new Point(0, visibleLineNumber * lineHeight);
int fontType = attributes.getFontType();
CharSequence text = myDocument.getCharsNoThreadCheck();
int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
while (!iterationState.atEnd() && !lIterator.atEnd()) {
int hEnd = iterationState.getEndOffset();
int lEnd = lIterator.getEnd();
if (hEnd >= lEnd) {
FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
if (collapsedFolderAt == null) {
position.x = drawBackground(g, backColor, text.subSequence(start, lEnd - lIterator.getSeparatorLength()),
position, fontType);
if (lIterator.getLineNumber() < lastLineIndex) {
if (backColor != null) {
Color saved = g.getColor();
g.setColor(backColor);
g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
g.setColor(saved);
}
}
else {
paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
break;
}
position.x = 0;
if (position.y > clip.y + clip.height) break;
position.y += lineHeight;
start = lEnd;
}
lIterator.advance();
}
else {
FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
if (collapsedFolderAt != null) {
position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText(),
position, fontType);
}
else {
if (hEnd > lEnd - lIterator.getSeparatorLength()) {
position.x = drawBackground(g, backColor, text.subSequence(start, lEnd - lIterator.getSeparatorLength()),
position, fontType);
}
else {
position.x = drawBackground(g, backColor, text.subSequence(start, hEnd), position, fontType);
}
}
iterationState.advance();
attributes = iterationState.getMergedAttributes();
backColor = attributes.getBackgroundColor();
fontType = attributes.getFontType();
start = iterationState.getStartOffset();
}
}
if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
}
}
private void paintRectangularSelection(Graphics g) {
final SelectionModel model = getSelectionModel();
if (!model.hasBlockSelection()) return;
final Point start = logicalPositionToXY(model.getBlockStart());
final Point end = logicalPositionToXY(model.getBlockEnd());
g.setColor(myScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR));
final int y;
final int height;
if (start.y <= end.y) {
y = start.y;
height = end.y - y + getLineHeight();
}
else {
y = end.y;
height = start.y - end.y + getLineHeight();
}
final int x = Math.min(start.x, end.x);
final int width = Math.max(2, Math.abs(end.x - start.x));
g.fillRect(x, y, width, height);
}
private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
Rectangle clip, int lineHeight) {
Color backColor = iterationState.getPastFileEndBackground();
if (backColor != null) {
Color saved = g.getColor();
g.setColor(backColor);
g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
g.setColor(saved);
}
}
private int drawBackground(Graphics g,
Color backColor,
CharSequence text,
Point position,
int fontType) {
int w = getTextSegmentWidth(text, position.x, fontType);
if (backColor != null) {
Color savedColor = g.getColor();
g.setColor(backColor);
g.fillRect(position.x, position.y, w, getLineHeight());
g.setColor(savedColor);
}
return position.x + w;
}
private LineIterator createLineIterator() {
return myDocument.createLineIterator();
}
private void paintText(Graphics g, Rectangle clip) {
int lineHeight = getLineHeight();
int visibleLineNumber = clip.y / lineHeight;
int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
if (startLineNumber >= myDocument.getLineCount() || startLineNumber < 0) {
return;
}
int start = myDocument.getLineStartOffset(startLineNumber);
IterationState iterationState = new IterationState(this, start, true);
LineIterator lIterator = createLineIterator();
lIterator.start(start);
if (lIterator.atEnd()) {
return;
}
TextAttributes attributes = iterationState.getMergedAttributes();
Color currentColor = attributes.getForegroundColor();
if (currentColor == null) {
currentColor = getForegroundColor();
}
Color effectColor = attributes.getEffectColor();
EffectType effectType = attributes.getEffectType();
int fontType = attributes.getFontType();
myCurrentFontType = null;
myFontCache = new ArrayList<CachedFontContent>();
g.setColor(currentColor);
Point position = new Point(0, visibleLineNumber * lineHeight);
while (!iterationState.atEnd() && !lIterator.atEnd()) {
int hEnd = iterationState.getEndOffset();
int lEnd = lIterator.getEnd();
if (hEnd >= lEnd) {
FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
if (collapsedFolderAt == null) {
drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
fontType, currentColor);
position.x = 0;
if (position.y > clip.y + clip.height) break;
position.y += lineHeight;
start = lEnd;
}
// myBorderEffect.eolReached(g, this);
lIterator.advance();
}
else {
FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
if (collapsedFolderAt != null) {
int foldingXStart = position.x;
position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
fontType, currentColor);
BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, position.x, getLineHeight(), effectColor,
effectType);
}
else {
if (hEnd > lEnd - lIterator.getSeparatorLength()) {
position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
effectType, fontType, currentColor);
}
else {
position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
}
}
iterationState.advance();
attributes = iterationState.getMergedAttributes();
Color color = attributes.getForegroundColor();
if (color == null) {
color = getForegroundColor();
}
if (color != currentColor) {
g.setColor(color);
currentColor = color;
}
effectColor = attributes.getEffectColor();
effectType = attributes.getEffectType();
fontType = attributes.getFontType();
start = iterationState.getStartOffset();
}
}
FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
if (collapsedFolderAt != null) {
int foldingXStart = position.x;
int foldingXEnd = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType,
currentColor);
BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, foldingXEnd, getLineHeight(), effectColor,
effectType);
// myBorderEffect.collapsedFolderReached(g, this);
}
flushCachedChars(g);
}
private class CachedFontContent {
final CharSequence[] data = new CharSequence[CACHED_CHARS_BUFFER_SIZE];
final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
int myCount = 0;
final FontInfo myFontType;
public CachedFontContent(FontInfo fontInfo) {
myFontType = fontInfo;
}
public void flushContent(Graphics g) {
if (myCount != 0) {
if (myCurrentFontType != myFontType) {
myCurrentFontType = myFontType;
g.setFont(myFontType.getFont());
}
Color currentColor = null;
for (int i = 0; i < myCount; i++) {
if (!color[i].equals(currentColor)) {
currentColor = color[i];
g.setColor(currentColor);
}
drawChars(g, data[i], x[i], y[i]);
color[i] = null;
data[i] = null;
}
myCount = 0;
}
}
public void addContent(Graphics g, CharSequence data, int x, int y, Color color) {
this.data[myCount] = data;
this.x[myCount] = x;
this.y[myCount] = y;
this.color[myCount] = color;
myCount++;
if (myCount >= CACHED_CHARS_BUFFER_SIZE || SystemInfo.isMac) {
flushContent(g);
}
}
}
private void flushCachedChars(Graphics g) {
for (int i = 0; i < myFontCache.size(); i++) {
myFontCache.get(i).flushContent(g);
}
myFontCache = null;
}
private void paintCaretCursor(Graphics g) {
myCaretCursor.paint(g);
}
private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
if (markupModel == null) return;
RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
for (int i = 0; i < lineMarkers.length; i++) {
RangeHighlighter lineMarker = lineMarkers[i];
paintLineMarkerSeparator(lineMarker, clip, g);
}
}
private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
if (!marker.isValid()) {
return;
}
Color separatorColor = marker.getLineSeparatorColor();
if (separatorColor != null) {
int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
.getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
return;
}
int endShift = clip.x + clip.width;
g.setColor(separatorColor);
if (mySettings.isRightMarginShown()
&& myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
endShift =
Math.min(endShift, mySettings.getRightMargin(myProject) * getSpaceWidth(Font.PLAIN));
}
int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
y += getLineHeight();
}
g.drawLine(0, y - 1, endShift, y - 1);
}
}
private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
EffectType effectType, int fontType, Color fontColor) {
if (start >= end) return position.x;
CharSequence text = myDocument.getCharsNoThreadCheck();
boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
if (!isInClip) return position.x;
int y = getLineHeight() - getDescent() + position.y;
int x = position.x;
return drawTabbedString(g, text.subSequence(start, end), x, y, effectColor, effectType, fontType, fontColor);
}
private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
EffectType effectType, int fontType, Color fontColor) {
boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
if (!isInClip) return position.x;
int y = getLineHeight() - getDescent() + position.y;
int x = position.x;
return drawTabbedString(g, text, x, y, effectColor, effectType, fontType,
fontColor);
}
private int drawTabbedString(Graphics g, CharSequence text, int x, int y, Color effectColor,
EffectType effectType, int fontType, Color fontColor) {
int xStart = x;
int start = 0;
final int textLength = text.length();
for (int i = 0; i < textLength; i++) {
if (text.charAt(i) != '\t') continue;
x = drawTablessString(text, start, i, g, x, y, fontType, fontColor);
int x1 = nextTabStop(x);
drawTabPlacer(g, y, x, x1);
x = x1;
start = i + 1;
}
x = drawTablessString(text, start, textLength, g, x, y, fontType, fontColor);
if (effectColor != null) {
Color savedColor = g.getColor();
int w = getTextSegmentWidth(text, xStart, fontType);
// myBorderEffect.flushIfCantProlong(g, this, effectType, effectColor);
if (effectType == EffectType.LINE_UNDERSCORE) {
g.setColor(effectColor);
g.drawLine(xStart, y + 1, xStart + w, y + 1);
g.setColor(savedColor);
}
else {
if (effectType == EffectType.STRIKEOUT) {
g.setColor(effectColor);
int y1 = y - getCharHeight() / 2;
g.drawLine(xStart, y1, xStart + w, y1);
g.setColor(savedColor);
}
else {
if (effectType == EffectType.WAVE_UNDERSCORE) {
g.setColor(effectColor);
drawWave(g, xStart, xStart + w, y + 1);
g.setColor(savedColor);
}
}
}
}
return x;
}
private int drawTablessString(final CharSequence text,
final int start,
final int end,
final Graphics g,
final int x,
final int y,
final int fontType,
final Color fontColor) {
int endX = x;
if (start < end) {
final FontInfo font = fontForChar(text.charAt(start), fontType);
for (int j = start; j < end; j++) {
final char c = text.charAt(j);
FontInfo newFont = fontForChar(c, fontType);
if (font != newFont) {
int x1 = drawTablessString(text, start, j, g, x, y, fontType, fontColor);
return drawTablessString(text, j, end, g, x1, y, fontType, fontColor);
}
endX += font.charWidth(c, myEditorComponent);
}
drawCharsCached(g, text.subSequence(start, end), x, y, fontType, fontColor);
}
return endX;
}
private FontInfo fontForChar(final char c, int style) {
return ComplementaryFontsRegistry.getFontAbleToDisplay(c, myScheme.getEditorFontSize(), style, myScheme.getEditorFontName());
}
public int charWidth(char c, int fontType) {
return fontForChar(c, fontType).charWidth(c, myEditorComponent);
}
private void drawTabPlacer(Graphics g, int y, int start, int stop) {
if (mySettings.isWhitespacesShown()) {
stop -= g.getFontMetrics().charWidth(' ') / 2;
Color oldColor = g.getColor();
g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR));
final int charHeight = getCharHeight();
final int halfCharHeight = charHeight / 2;
int mid = y - halfCharHeight;
int top = y - charHeight;
g.drawLine(start, mid, stop, mid);
g.drawLine(stop, y, stop, top);
g.fillPolygon(new int[]{stop - halfCharHeight, stop - halfCharHeight, stop},
new int[]{y, y - charHeight, y - halfCharHeight}, 3);
g.setColor(oldColor);
}
}
<caret>
private void drawCharsCached(Graphics g,
CharSequence data,
int x,
int y,
int fontType,
Color color) {
FontInfo fnt = fontForChar(data.charAt(0), fontType);
CachedFontContent cache = null;
for (int i = 0; i < myFontCache.size(); i++) {
CachedFontContent cache1 = myFontCache.get(i);
if (cache1.myFontType == fnt) {
cache = cache1;
break;
}
}
if (cache == null) {
cache = new CachedFontContent(fnt);
myFontCache.add(cache);
}
cache.addContent(g, data, x, y, color);
}
private void drawChars(Graphics g, CharSequence data, int x, int y) {
g.drawString(data.toString(), x, y);
if (mySettings.isWhitespacesShown()) {
Color oldColor = g.getColor();
g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR));
final FontMetrics metrics = g.getFontMetrics();
int halfSpaceWidth = metrics.charWidth(' ') / 2;
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) == ' ') {
g.fillRect(x + halfSpaceWidth, y, 1, 1);
}
x += metrics.charWidth(data.charAt(i));
}
g.setColor(oldColor);
}
}
private static final int WAVE_HEIGHT = 2;
private static final int WAVE_SEGMENT_LENGTH = 4;
private static void drawWave(Graphics g, int xStart, int xEnd, int y) {
int startSegment = xStart / WAVE_SEGMENT_LENGTH;
int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
for (int i = startSegment; i < endSegment; i++) {
drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
}
int x = WAVE_SEGMENT_LENGTH * endSegment;
g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
}
private static void drawWaveSegment(Graphics g, int x, int y) {
g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
}
private int getTextSegmentWidth(CharSequence text, int xStart, int fontType) {
int start = 0;
int x = xStart;
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) != '\t') continue;
if (i > start) {
for (int j = start; j < i; j++) x += charWidth(text.charAt(j), fontType);
}
x = nextTabStop(x);
start = i + 1;
}
if (start < text.length()) {
for (int j = start; j < text.length(); j++) x += charWidth(text.charAt(j), fontType);
}
return x - xStart;
}
public int getLineHeight() {
if (myLineHeight != -1) return myLineHeight;
ThreadingAssertions.assertEventDispatchThread();
FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
myLineHeight = (int)(fontMetrics.getHeight() * (isOneLineMode() ? 1 : myScheme.getLineSpacing()));
if (myLineHeight == 0) {
myLineHeight = fontMetrics.getHeight();
if (myLineHeight == 0) {
myLineHeight = 12;
}
}
return myLineHeight;
}
int getDescent() {
if (myDescent != -1) {
return myDescent;
}
FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
myDescent = fontMetrics.getDescent();
return myDescent;
}
public FontMetrics getFontMetrics(int fontType) {
if (myPlainFontMetrics == null) {
ThreadingAssertions.assertEventDispatchThread();
myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
}
if (fontType == Font.PLAIN) return myPlainFontMetrics;
if (fontType == Font.BOLD) return myBoldFontMetrics;
if (fontType == Font.ITALIC) return myItalicFontMetrics;
if (fontType == Font.BOLD + Font.ITALIC) return myBoldItalicFontMetrics;
LOG.assertTrue(false, "Unknown font type: " + fontType);
return myPlainFontMetrics;
}
private int getCharHeight() {
if (myCharHeight == -1) {
ThreadingAssertions.assertEventDispatchThread();
FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
myCharHeight = fontMetrics.charWidth('a');
}
return myCharHeight;
}
public Dimension getPreferredSize() {
Dimension draft = getSizeWithoutCaret();
int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
draft.width = Math.max(caretX, draft.width) + mySettings.getAdditionalColumnsCount() * getSpaceWidth(Font.PLAIN);
return draft;
}
private Dimension getSizeWithoutCaret() {
Dimension size = mySizeContainer.getContentSize();
if (isOneLineMode()) return new Dimension(size.width, getLineHeight());
if (mySettings.isAdditionalPageAtBottom()) {
int lineHeight = getLineHeight();
return new Dimension(size.width,
size.height +
Math.max(getScrollingModel().getVisibleArea().height - 2 * lineHeight, lineHeight));
}
return getContentSize();
}
public Dimension getContentSize() {
Dimension size = mySizeContainer.getContentSize();
return new Dimension(size.width, size.height + mySettings.getAdditionalLinesCount() * getLineHeight());
}
public JScrollPane getScrollPane() {
return myScrollPane;
}
public int logicalPositionToOffset(LogicalPosition pos) {
ThreadingAssertions.assertEventDispatchThread();
if (myDocument.getLineCount() == 0) return 0;
if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
if (pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
if (pos.line >= myDocument.getLineCount()) {
return myDocument.getTextLength();
}
int start = myDocument.getLineStartOffset(pos.line);
int end = myDocument.getLineEndOffset(pos.line);
CharSequence text = myDocument.getCharsNoThreadCheck();
if (pos.column == 0) return start;
return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
}
public void setLastColumnNumber(int val) {
ThreadingAssertions.assertEventDispatchThread();
myLastColumnNumber = val;
}
public int getLastColumnNumber() {
ThreadingAssertions.assertEventDispatchThread();
return myLastColumnNumber;
}
int getVisibleLineCount() {
int line = getDocument().getLineCount();
line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
return line;
}
public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
ThreadingAssertions.assertEventDispatchThread();
if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
int offset = logicalPositionToOffset(logicalPos);
FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
if (offset < getDocument().getTextLength() - 1) {
offset = outermostCollapsed.getStartOffset();
LogicalPosition foldStart = offsetToLogicalPosition(offset);
return logicalToVisualPosition(foldStart);
}
else {
offset = outermostCollapsed.getEndOffset() + 3;
}
}
int line = logicalPos.line;
int column = logicalPos.column;
line -= myFoldingModel.getFoldedLinesCountBefore(offset);
FoldRegion[] toplevel = myFoldingModel.fetchTopLevel();
for (int idx = myFoldingModel.getLastTopLevelIndexBefore(offset); idx >= 0; idx--) {
FoldRegion region = toplevel[idx];
if (region.isValid()) {
if (region.getDocument().getLineNumber(region.getEndOffset()) == logicalPos.line &&
region.getEndOffset() <= offset) {
LogicalPosition foldStart = offsetToLogicalPosition(region.getStartOffset());
LogicalPosition foldEnd = offsetToLogicalPosition(region.getEndOffset());
column += foldStart.column + region.getPlaceholderText().length() - foldEnd.column;
offset = region.getStartOffset();
logicalPos = foldStart;
}
else {
break;
}
}
}
LOG.assertTrue(line >= 0);
return new VisualPosition(line, Math.max(0, column));
}
private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
if (topLevelCollapsed == null) return null;
int start = 0;
int end = topLevelCollapsed.length - 1;
int i = 0;
while (start <= end) {
i = (start + end) / 2;
FoldRegion region = topLevelCollapsed[i];
LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
if (visFoldEnd.line < visual.line) {
start = i + 1;
}
else {
if (visFoldEnd.line > visual.line) {
end = i - 1;
}
else {
if (visFoldEnd.column < visual.column) {
start = i + 1;
}
else {
if (visFoldEnd.column > visual.column) {
end = i - 1;
}
else {
i--;
break;
}
}
}
}
}
while (i >= 0 && i < topLevelCollapsed.length) {
if (topLevelCollapsed[i].isValid()) break;
i--;
}
if (i >= 0 && i < topLevelCollapsed.length) {
FoldRegion region = topLevelCollapsed[i];
LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
i--;
if (i >= 0) {
return topLevelCollapsed[i];
}
else {
return null;
}
}
return region;
}
return null;
}
public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
ThreadingAssertions.assertEventDispatchThread();
if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
int line = visiblePos.line;
int column = visiblePos.column;
FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
if (lastCollapsedBefore != null) {
LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset());
VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
if (visFoldEnd.line == visiblePos.line) {
if (visiblePos.column >= visFoldEnd.column) {
column = logFoldEnd.column + (visiblePos.column - visFoldEnd.column);
}
else {
return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
}
}
}
if (column < 0) column = 0;
return new LogicalPosition(line, column);
}
private int calcLogicalLineNumber(int offset) {
int textLength = myDocument.getTextLength();
if (textLength == 0) return 0;
if (offset > textLength || offset < 0) {
throw new IndexOutOfBoundsException("Wrong offset: " + offset + " textLength: " + textLength);
}
int lineIndex = myDocument.getLineNumber(offset);
LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
return lineIndex;
}
private int calcColumnNumber(int offset, int lineIndex) {
if (myDocument.getTextLength() == 0) return 0;
CharSequence text = myDocument.getCharsSequence();
int start = myDocument.getLineStartOffset(lineIndex);
if (start == offset) return 0;
return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
}
private void moveCaretToScreenPos(int x, int y) {
if (x < 0) {
x = 0;
}
LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
int columnNumber = pos.column;
int lineNumber = pos.line;
if (lineNumber >= myDocument.getLineCount()) {
lineNumber = myDocument.getLineCount() - 1;
}
if (!mySettings.isVirtualSpace()) {
if (lineNumber >= 0) {
int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
if (columnNumber > lineEndColumnNumber) {
columnNumber = lineEndColumnNumber;
}
}
}
if (lineNumber < 0) {
lineNumber = 0;
columnNumber = 0;
}
if (!mySettings.isCaretInsideTabs()) {
int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
CharSequence text = myDocument.getCharsSequence();
if (offset >= 0 && offset < myDocument.getTextLength()) {
if (text.charAt(offset) == '\t') {
columnNumber = calcColumnNumber(offset, lineNumber);
}
}
}
LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
getCaretModel().moveToLogicalPosition(pos1);
}
private void runMousePressedCommand(final MouseEvent e) {
myMousePressedEvent = e;
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
EditorMouseListener[] mouseListeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
for (int i = 0; i < mouseListeners.length; i++) {
mouseListeners[i].mousePressed(event);
}
// On some systems (for example on Linux) popup trigger is MOUSE_PRESSED event.
// But this trigger is always consumed by popup handler. In that case we have to
// also move caret.
if (event.isConsumed() &&
!(event.getMouseEvent().isPopupTrigger() || event.getArea() == EditorMouseEventArea.EDITING_AREA)) {
return;
}
if (myCommandProcessor != null) {
Runnable runnable = new Runnable() {
public void run() {
processMousePressed(e);
}
};
myCommandProcessor.executeCommand(myProject, runnable, "", null);
}
else {
processMousePressed(e);
}
}
private void runMouseClickedCommand(final MouseEvent e) {
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
EditorMouseListener[] listeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].mouseClicked(event);
if (event.isConsumed()) {
e.consume();
return;
}
}
}
private void runMouseReleasedCommand(final MouseEvent e) {
myScrollingTimer.stop();
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
EditorMouseListener[] listeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].mouseReleased(event);
if (event.isConsumed()) {
e.consume();
return;
}
}
if (myCommandProcessor != null) {
Runnable runnable = new Runnable() {
public void run() {
processMouseReleased(e);
}
};
myCommandProcessor.executeCommand(myProject, runnable, "", null);
}
else {
processMouseReleased(e);
}
}
private void runMouseEnteredCommand(MouseEvent e) {
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
EditorMouseListener[] listeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].mouseEntered(event);
if (event.isConsumed()) {
e.consume();
return;
}
}
}
private void runMouseExitedCommand(MouseEvent e) {
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
EditorMouseListener[] listeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].mouseExited(event);
if (event.isConsumed()) {
e.consume();
return;
}
}
}
private void processMousePressed(MouseEvent e) {
if (myMouseSelectionState != MOUSE_SELECTION_STATE_NONE &&
System.currentTimeMillis() - myMouseSelectionChangeTimestamp > 1000) {
setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
}
int x = e.getX();
int y = e.getY();
if (x < 0) x = 0;
if (y < 0) y = 0;
final EditorMouseEventArea eventArea = getMouseEventArea(e);
if (eventArea == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
if (range != null) {
final boolean expansion = !range.isExpanded();
int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
Runnable processor = new Runnable() {
public void run() {
myFoldingModel.flushCaretShift();
range.setExpanded(expansion);
}
};
getFoldingModel().runBatchFoldingOperation(processor);
y = myGutterComponent.getHeadCenterY(range);
getScrollingModel().scrollVertically(y - scrollShift);
return;
}
}
if (e.getSource() == myGutterComponent) {
if (eventArea == EditorMouseEventArea.LINE_MARKERS_AREA ||
eventArea == EditorMouseEventArea.ANNOTATIONS_AREA) {
myGutterComponent.mousePressed(e);
if (e.isConsumed()) return;
}
x = 0;
}
int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
moveCaretToScreenPos(x, y);
if (e.isPopupTrigger()) return;
requestFocus();
int caretOffset = getCaretModel().getOffset();
myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
caretOffset >= mySelectionModel.getSelectionStart() &&
caretOffset <= mySelectionModel.getSelectionEnd();
if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
int[] starts = mySelectionModel.getBlockSelectionStarts();
int[] ends = mySelectionModel.getBlockSelectionEnds();
for (int i = 0; i < starts.length; i++) {
if (caretOffset >= starts[i] && caretOffset < ends[i]) {
myMousePressedInsideSelection = true;
break;
}
}
}
if (getMouseEventArea(e) == EditorMouseEventArea.LINE_NUMBERS_AREA && e.getClickCount() == 1) {
mySelectionModel.selectLineAtCaret();
setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
mySavedSelectionStart = mySelectionModel.getSelectionStart();
mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
return;
}
if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
if (caretOffset < mySavedSelectionStart) {
mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
}
else {
mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
}
}
else {
mySelectionModel.setSelection(oldSelectionStart, caretOffset);
}
}
else {
if (!myMousePressedInsideSelection && getSelectionModel().hasSelection()) {
setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
mySelectionModel.setSelection(caretOffset, caretOffset);
}
else {
if (!e.isPopupTrigger()) {
switch (e.getClickCount()) {
case 2:
mySelectionModel.selectWordAtCaret(mySettings.isMouseClickSelectionHonorsCamelWords());
setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
mySavedSelectionStart = mySelectionModel.getSelectionStart();
mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
getCaretModel().moveToOffset(mySavedSelectionEnd);
break;
case 3:
mySelectionModel.selectLineAtCaret();
setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
mySavedSelectionStart = mySelectionModel.getSelectionStart();
mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
break;
}
}
}
}
}
private static boolean isControlKeyDown(MouseEvent mouseEvent) {
return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
}
private void processMouseReleased(MouseEvent e) {
if (e.getSource() == myGutterComponent) {
myGutterComponent.mouseReleased(e);
}
if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
return;
}
// if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
final FoldRegion region = ((FoldingModelEx)getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
getFoldingModel().runBatchFoldingOperation(new Runnable() {
public void run() {
myFoldingModel.flushCaretShift();
region.setExpanded(true);
}
});
}
if (myMousePressedEvent != null && myMousePressedEvent.getClickCount() == 1 && myMousePressedInsideSelection) {
getSelectionModel().removeSelection();
}
}
public DataContext getDataContext() {
return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
}
public DataContext getProjectAwareDataContext(final DataContext original) {
if (original.getData(DataConstants.PROJECT) == myProject) return original;
return new DataContext() {
public Object getData(String dataId) {
if (DataConstants.PROJECT.equals(dataId)) {
return myProject;
}
return original.getData(dataId);
}
};
}
public EditorMouseEventArea getMouseEventArea(MouseEvent e) {
if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
int x = myGutterComponent.convertX(e.getX());
if (x >= myGutterComponent.getLineNumberAreaOffset() &&
x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) {
return EditorMouseEventArea.LINE_NUMBERS_AREA;
}
if (x >= myGutterComponent.getAnnotationsAreaOffset() &&
x <= myGutterComponent.getAnnotationsAreaOffset() + myGutterComponent.getAnnotationsAreaWidth()) {
return EditorMouseEventArea.ANNOTATIONS_AREA;
}
if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth()) {
return EditorMouseEventArea.LINE_MARKERS_AREA;
}
if (x >= myGutterComponent.getFoldingAreaOffset() &&
x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth()) {
return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
}
return null;
}
private void requestFocus() {
myEditorComponent.requestFocus();
}
private void validateMousePointer(MouseEvent e) {
if (e.getSource() == myGutterComponent) {
FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
myGutterComponent.setActiveFoldRegion(foldingAtCursor);
if (foldingAtCursor != null) {
myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
else {
myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
else {
myGutterComponent.setActiveFoldRegion(null);
if (getSelectionModel().hasSelection() &&
(e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
return;
}
}
myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
}
}
private void runMouseDraggedCommand(final MouseEvent e) {
if (myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
return;
}
myCommandProcessor.executeCommand(myProject, new Runnable() {
public void run() {
processMouseDragged(e);
}
}, "", MOUSE_DRAGGED_GROUP);
}
private void processMouseDragged(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
return;
}
Rectangle rect = getScrollingModel().getVisibleArea();
int dx = 0;
int x = e.getX();
if (e.getSource() == myGutterComponent) {
x = 0;
}
if (x < rect.x) {
dx = x - rect.x;
}
else {
if (x > rect.x + rect.width) {
dx = x - rect.x - rect.width;
}
}
int dy = 0;
int y = e.getY();
if (y < rect.y) {
dy = y - rect.y;
}
else {
if (y > rect.y + rect.height) {
dy = y - rect.y - rect.height;
}
}
if (dx == 0 && dy == 0) {
myScrollingTimer.stop();
SelectionModel selectionModel = getSelectionModel();
int oldSelectionStart = selectionModel.getLeadSelectionOffset();
int oldCaretOffset = getCaretModel().getOffset();
LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
moveCaretToScreenPos(x, y);
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
int newCaretOffset = getCaretModel().getOffset();
int caretShift = newCaretOffset - mySavedSelectionStart;
if (myMousePressedEvent != null &&
getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA &&
getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.LINE_NUMBERS_AREA) {
selectionModel.setSelection(oldSelectionStart, newCaretOffset);
}
else {
if (isColumnMode()) {
final LogicalPosition blockStart = selectionModel.hasBlockSelection()
? selectionModel.getBlockStart()
: oldLogicalCaret;
selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
}
else {
if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
if (caretShift < 0) {
int newSelection = newCaretOffset;
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
newSelection = mySelectionModel.getWordAtCaretStart();
}
else {
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
newSelection = logicalPositionToOffset(
visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0)));
}
}
if (newSelection < 0) newSelection = newCaretOffset;
selectionModel.setSelection(mySavedSelectionEnd, newSelection);
getCaretModel().moveToOffset(newSelection);
}
else {
int newSelection = newCaretOffset;
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
newSelection = mySelectionModel.getWordAtCaretEnd();
}
else {
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
newSelection = logicalPositionToOffset(
visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)));
}
}
if (newSelection < 0) newSelection = newCaretOffset;
selectionModel.setSelection(mySavedSelectionStart, newSelection);
getCaretModel().moveToOffset(newSelection);
}
return;
}
if (!myMousePressedInsideSelection) {
selectionModel.setSelection(oldSelectionStart, newCaretOffset);
}
else {
if (caretShift != 0) {
if (myMousePressedEvent != null) {
if (mySettings.isDndEnabled()) {
boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
isCopy
? TransferHandler.COPY
: TransferHandler.MOVE);
}
else {
selectionModel.removeSelection();
}
myMousePressedEvent = null;
}
}
}
}
}
}
else {
myScrollingTimer.start(dx, dy);
}
}
private static class RepaintCursorThread extends Thread {
private long mySleepTime = 500;
private boolean myIsBlinkCaret = true;
private EditorImpl myEditor = null;
private boolean isStopped = false;
private MyRepaintRunnable myRepaintRunnable;
public RepaintCursorThread() {
super("EditorCaretThread");
myRepaintRunnable = new MyRepaintRunnable();
}
private class MyRepaintRunnable implements Runnable {
public void run() {
if (myEditor != null) {
myEditor.myCaretCursor.repaint();
}
}
}
public void setBlinkPeriod(int blinkPeriod) {
mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
}
public void setBlinkCaret(boolean value) {
myIsBlinkCaret = value;
}
public synchronized void stopThread() {
isStopped = true;
}
public void run() {
while (true) {
try {
Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
}
catch (InterruptedException e) {
}
synchronized (this) {
if (isStopped) {
break;
}
}
if (myEditor == null) {
continue;
}
CaretCursor activeCursor = myEditor.myCaretCursor;
long time = System.currentTimeMillis();
time -= activeCursor.myStartTime;
if (time > mySleepTime) {
boolean toRepaint = true;
if (myIsBlinkCaret) {
activeCursor.isVisible = !activeCursor.isVisible;
}
else {
toRepaint = !activeCursor.isVisible;
activeCursor.isVisible = true;
}
if (toRepaint) {
SwingUtilities.invokeLater(myRepaintRunnable);
}
}
}
}
}
void updateCaretCursor() {
if (!IJSwingUtilities.hasFocus(getContentComponent())) {
stopOptimizedScrolling();
}
if (myCursorUpdater == null) {
myCursorUpdater = new Runnable() {
public void run() {
if (myCursorUpdater == null) return;
myCursorUpdater = null;
if (getDocument().getMarkupModel(myProject) == null) return;
VisualPosition caretPosition = getCaretModel().getVisualPosition();
Point pos1 = visualPositionToXY(caretPosition);
Point pos2 = visualPositionToXY(new VisualPosition(caretPosition.line, caretPosition.column + 1));
myCaretCursor.setPosition(pos1, pos2.x - pos1.x);
}
};
}
}
public void setCaretVisible(boolean b) {
if (b) {
myCaretCursor.activate();
}
else {
myCaretCursor.passivate();
}
}
public void addFocusListener(FocusChangeListener listener) {
myFocusListeners.add(listener);
}
public Project getProject() {
return myProject;
}
public boolean isOneLineMode() {
return myIsOneLineMode;
}
public boolean isEmbeddedIntoDialogWrapper() {
return myEmbeddedIntoDialogWrapper;
}
public void setEmbeddedIntoDialogWrapper(boolean b) {
ThreadingAssertions.assertEventDispatchThread();
myEmbeddedIntoDialogWrapper = b;
myScrollPane.setFocusable(!b);
myEditorComponent.setFocusCycleRoot(!b);
myEditorComponent.setFocusable(b);
}
public void setOneLineMode(boolean isOneLineMode) {
myIsOneLineMode = isOneLineMode;
getScrollPane().setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
reinitSettings();
}
public void stopOptimizedScrolling() {
if (SystemInfo.isMac) return;
myEditorComponent.setOpaque(false);
}
private void startOptimizedScrolling() {
if (SystemInfo.isMac) return;
myEditorComponent.setOpaque(true);
}
private class CaretCursor {
private Point myLocation;
private int myWidth;
private boolean isVisible = true;
private long myStartTime = 0;
public CaretCursor() {
myLocation = new Point(0, 0);
}
public void activate() {
synchronized (ourCaretThread) {
ourCaretThread.myEditor = EditorImpl.this;
ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
isVisible = true;
}
}
public void passivate() {
synchronized (ourCaretThread) {
isVisible = false;
}
}
public void setPosition(Point location, int width) {
myStartTime = System.currentTimeMillis();
myLocation = location;
isVisible = true;
myWidth = Math.max(width, 2);
repaint();
}
private void repaint() {
myEditorComponent.repaintEditorComponent(myLocation.x,
myLocation.y,
myWidth,
getLineHeight());
}
public void paint(Graphics g) {
if (!isVisible || !IJSwingUtilities.hasFocus(getContentComponent()) || isRendererMode()) return;
int x = myLocation.x;
int lineHeight = getLineHeight();
int y = myLocation.y;
Rectangle viewRect = getScrollingModel().getVisibleArea();
if (x - viewRect.x < 0) {
return;
}
g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
if (!SystemInfo.isMac) {
Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
if (background == null) background = getBackroundColor();
g.setXORMode(background);
}
if (EditorImpl.this.myIsInsertMode != mySettings.isBlockCursor()) {
for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
g.drawLine(x + i, y, x + i, y + lineHeight - 1);
}
}
else {
g.fillRect(x, y, myWidth, lineHeight - 1);
}
g.setPaintMode();
}
}
private class ScrollingTimer {
Timer myTimer;
private static final int TIMER_PERIOD = 100;
private static final int CYCLE_SIZE = 20;
private int myXCycles;
private int myYCycles;
private int myDx;
private int myDy;
private int xPassedCycles = 0;
private int yPassedCycles = 0;
public void start(int dx, int dy) {
myDx = 0;
myDy = 0;
if (dx > 0) {
myXCycles = CYCLE_SIZE / dx + 1;
myDx = 1 + dx / CYCLE_SIZE;
}
else {
if (dx < 0) {
myXCycles = -CYCLE_SIZE / dx + 1;
myDx = -1 + dx / CYCLE_SIZE;
}
}
if (dy > 0) {
myYCycles = CYCLE_SIZE / dy + 1;
myDy = 1 + dy / CYCLE_SIZE;
}
else {
if (dy < 0) {
myYCycles = -CYCLE_SIZE / dy + 1;
myDy = -1 + dy / CYCLE_SIZE;
}
}
if (myTimer != null) {
return;
}
myTimer = new Timer(TIMER_PERIOD,
new ActionListener() {
public void actionPerformed(ActionEvent e) {
myCommandProcessor.executeCommand(myProject, new Runnable() {
public void run() {
int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
int columnNumber = caretPosition.column;
xPassedCycles++;
if (xPassedCycles >= myXCycles) {
xPassedCycles = 0;
columnNumber += myDx;
}
int lineNumber = caretPosition.line;
yPassedCycles++;
if (yPassedCycles >= myYCycles) {
yPassedCycles = 0;
lineNumber += myDy;
}
LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
getCaretModel().moveToLogicalPosition(pos);
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
int newCaretOffset = getCaretModel().getOffset();
int caretShift = newCaretOffset - mySavedSelectionStart;
if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
if (caretShift < 0) {
int newSelection = newCaretOffset;
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
newSelection = mySelectionModel.getWordAtCaretStart();
}
else {
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
newSelection = logicalPositionToOffset(visualToLogicalPosition(
new VisualPosition(getCaretModel().getVisualPosition().line, 0)));
}
}
if (newSelection < 0) newSelection = newCaretOffset;
mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
getCaretModel().moveToOffset(newSelection);
}
else {
int newSelection = newCaretOffset;
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
newSelection = mySelectionModel.getWordAtCaretEnd();
}
else {
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
newSelection = logicalPositionToOffset(visualToLogicalPosition(
new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)));
}
}
if (newSelection < 0) newSelection = newCaretOffset;
mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
getCaretModel().moveToOffset(newSelection);
}
return;
}
if (mySelectionModel.hasBlockSelection()) {
mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(),
getCaretModel().getLogicalPosition());
}
else {
mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
}
}
},
"Move Cursor", null);
}
});
myTimer.start();
}
public void stop() {
if (myTimer != null) {
myTimer.stop();
myTimer = null;
}
}
}
class MyScrollBar extends JScrollBar {
public MyScrollBar(int orientation) {
super(orientation);
setFocusable(false);
}
/**
* This is helper method. It returns height of the top (descrease) scrollbar
* button. Please note, that it's possible to return real height only if scrollbar
* is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
* value.
*/
int getDecScrollButtonHeight() {
ScrollBarUI ui = getUI();
Insets insets = getInsets();
if (ui instanceof BasicScrollBarUI) {
try {
Field decrButtonField = BasicScrollBarUI.class.getDeclaredField("decrButton");
decrButtonField.setAccessible(true);
JButton decrButtonValue = (JButton)decrButtonField.get(ui);
LOG.assertTrue(decrButtonValue != null);
return insets.top + decrButtonValue.getHeight();
}
catch (Exception exc) {
exc.printStackTrace();
throw new IllegalStateException(exc.getMessage());
}
}
else {
return insets.top + 15;
}
}
/**
* This is helper method. It returns height of the bottom (increase) scrollbar
* button. Please note, that it's possible to return real height only if scrollbar
* is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
* value.
*/
int getIncScrollButtonHeight() {
ScrollBarUI ui = getUI();
Insets insets = getInsets();
if (ui instanceof BasicScrollBarUI) {
try {
Field incrButtonField = BasicScrollBarUI.class.getDeclaredField("incrButton");
incrButtonField.setAccessible(true);
JButton incrButtonValue = (JButton)incrButtonField.get(ui);
LOG.assertTrue(incrButtonValue != null);
return insets.bottom + incrButtonValue.getHeight();
}
catch (Exception exc) {
exc.printStackTrace();
throw new IllegalStateException(exc.getMessage());
}
}
else if ("apple.laf.AquaScrollBarUI".equals(ui.getClass().getName())) {
return insets.bottom + 30;
}
else {
return insets.bottom + 15;
}
}
public int getUnitIncrement(int direction) {
JViewport vp = myScrollPane.getViewport();
Rectangle vr = vp.getViewRect();
return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
}
public int getBlockIncrement(int direction) {
JViewport vp = myScrollPane.getViewport();
Rectangle vr = vp.getViewRect();
return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
}
}
private MyEditable getViewer() {
if (myEditable == null) {
myEditable = new MyEditable();
}
return myEditable;
}
public CopyProvider getCopyProvider() {
return getViewer();
}
public CutProvider getCutProvider() {
return getViewer();
}
public PasteProvider getPasteProvider() {
return getViewer();
}
public DeleteProvider getDeleteProvider() {
return getViewer();
}
private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
public void performCopy(DataContext dataContext) {
executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
}
public boolean isCopyEnabled(DataContext dataContext) {
return true;
}
public void performCut(DataContext dataContext) {
executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
}
public boolean isCutEnabled(DataContext dataContext) {
return !isViewer() && getDocument().isWritable();
}
public void performPaste(DataContext dataContext) {
executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
}
public boolean isPastePossible(DataContext dataContext) {
// Copy of isPasteEnabled. See interface method javadoc.
return !isViewer() && getDocument().isWritable();
}
public boolean isPasteEnabled(DataContext dataContext) {
return !isViewer() && getDocument().isWritable();
}
public void deleteElement(DataContext dataContext) {
executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
}
public boolean canDeleteElement(DataContext dataContext) {
return !isViewer() && getDocument().isWritable();
}
private void executeAction(String actionId, DataContext dataContext) {
EditorAction action = (EditorAction)ActionManager.getInstance().getAction(actionId);
if (action != null) {
action.actionPerformed(EditorImpl.this, dataContext);
}
}
}
public void setColorsScheme(EditorColorsScheme scheme) {
ThreadingAssertions.assertEventDispatchThread();
myScheme = scheme;
reinitSettings();
}
public EditorColorsScheme getColorsScheme() {
ThreadingAssertions.assertEventDispatchThread();
return myScheme;
}
public void setVerticalScrollbarOrientation(int type) {
ThreadingAssertions.assertEventDispatchThread();
int currentHorOffset = myScrollingModel.getHorizontalScrollOffset();
myScrollbarOrientation = type;
if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
myScrollPane.setLayout(new LeftHandScrollbarLayout());
}
else {
myScrollPane.setLayout(new ScrollPaneLayout());
}
myScrollingModel.scrollHorizontally(currentHorOffset);
}
public void setVerticalScrollbarVisible(boolean b) {
if (b) {
myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
}
else {
myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
}
}
public void setHorizontalScrollbarVisible(boolean b) {
if (b) {
myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}
else {
myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
}
public int getVerticalScrollbarOrientation() {
return myScrollbarOrientation;
}
public MyScrollBar getVerticalScrollBar() {
return myVerticalScrollBar;
}
public JPanel getPanel() {
return myPanel;
}
private int getMouseSelectionState() {
return myMouseSelectionState;
}
private void setMouseSelectionState(int mouseSelectionState) {
myMouseSelectionState = mouseSelectionState;
myMouseSelectionChangeTimestamp = System.currentTimeMillis();
}
public void replaceInputMethodText(InputMethodEvent e) {
getInputMethodRequests();
myInputMethodRequestsHandler.replaceInputMethodText(e);
}
public void inputMethodCaretPositionChanged(InputMethodEvent e) {
getInputMethodRequests();
myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
}
public InputMethodRequests getInputMethodRequests() {
if (myInputMethodRequestsHandler == null) {
myInputMethodRequestsHandler = new MyInputMethodHandler();
myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
}
return myInputMethodRequestsSwingWrapper;
}
public boolean processKeyTyped(KeyEvent e) {
if (e.getID() != KeyEvent.KEY_TYPED) return false;
char c = e.getKeyChar();
if (isReallyTypedEvent(e) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
processKeyTyped(c);
return true;
}
else {
return false;
}
}
private static boolean isReallyTypedEvent(KeyEvent e) {
int modifiers = e.getModifiers();
if (SystemInfo.isMac) {
return !e.isMetaDown() && !e.isControlDown();
}
return (modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK);
}
public void beforeModalityStateChanged() {
myScrollingModel.beforeModalityStateChanged();
}
private static class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
private InputMethodRequests myDelegate;
public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
myDelegate = delegate;
}
public Rectangle getTextLocation(final TextHitInfo offset) {
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getTextLocation(offset);
final Rectangle[] r = new Rectangle[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
r[0] = myDelegate.getTextLocation(offset);
}
});
}
catch (InterruptedException e) {
LOG.error(e);
}
catch (InvocationTargetException e) {
LOG.error(e);
}
return r[0];
}
public TextHitInfo getLocationOffset(final int x, final int y) {
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
final TextHitInfo[] r = new TextHitInfo[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
r[0] = myDelegate.getLocationOffset(x, y);
}
});
}
catch (InterruptedException e) {
LOG.error(e);
}
catch (InvocationTargetException e) {
LOG.error(e);
}
return r[0];
}
public int getInsertPositionOffset() {
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getInsertPositionOffset();
final int[] r = new int[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
r[0] = myDelegate.getInsertPositionOffset();
}
});
}
catch (InterruptedException e) {
LOG.error(e);
}
catch (InvocationTargetException e) {
LOG.error(e);
}
return r[0];
}
public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
final AttributedCharacterIterator.Attribute[] attributes) {
if (ApplicationManager.getApplication().isDispatchThread()) {
return myDelegate.getCommittedText(beginIndex,
endIndex,
attributes);
}
final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
}
});
}
catch (InterruptedException e) {
LOG.error(e);
}
catch (InvocationTargetException e) {
LOG.error(e);
}
return r[0];
}
public int getCommittedTextLength() {
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getCommittedTextLength();
final int[] r = new int[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
r[0] = myDelegate.getCommittedTextLength();
}
});
}
catch (InterruptedException e) {
LOG.error(e);
}
catch (InvocationTargetException e) {
LOG.error(e);
}
return r[0];
}
public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
return null;
}
public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getSelectedText(attributes);
final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
r[0] = myDelegate.getSelectedText(attributes);
}
});
}
catch (InterruptedException e) {
LOG.error(e);
}
catch (InvocationTargetException e) {
LOG.error(e);
}
return r[0];
}
}
private class MyInputMethodHandler implements InputMethodRequests {
private String composedText;
private int composedTextStart;
private int composedTextEnd;
public Rectangle getTextLocation(TextHitInfo offset) {
Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
Point p = getContentComponent().getLocationOnScreen();
r.translate(p.x, p.y);
return r;
}
public TextHitInfo getLocationOffset(int x, int y) {
if (composedText != null) {
Point p = getContentComponent().getLocationOnScreen();
p.x = x - p.x;
p.y = y - p.y;
int pos = logicalPositionToOffset(xyToLogicalPosition(p));
if (pos >= composedTextStart && pos <= composedTextEnd) {
return TextHitInfo.leading(pos - composedTextStart);
}
}
return null;
}
public int getInsertPositionOffset() {
int composedStartIndex = 0;
int composedEndIndex = 0;
if (composedText != null) {
composedStartIndex = composedTextStart;
composedEndIndex = composedTextEnd;
}
int caretIndex = getCaretModel().getOffset();
if (caretIndex < composedStartIndex) {
return caretIndex;
}
else {
if (caretIndex < composedEndIndex) {
return composedStartIndex;
}
else {
return caretIndex - (composedEndIndex - composedStartIndex);
}
}
}
private String getText(int startIdx, int endIdx) {
CharSequence chars = getDocument().getCharsSequence();
return chars.subSequence(startIdx, endIdx).toString();
}
public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
AttributedCharacterIterator.Attribute[] attributes) {
int composedStartIndex = 0;
int composedEndIndex = 0;
if (composedText != null) {
composedStartIndex = composedTextStart;
composedEndIndex = composedTextEnd;
}
String committed;
if (beginIndex < composedStartIndex) {
if (endIndex <= composedStartIndex) {
committed = getText(beginIndex, endIndex - beginIndex);
}
else {
int firstPartLength = composedStartIndex - beginIndex;
committed = getText(beginIndex, firstPartLength) +
getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
}
}
else {
committed = getText(beginIndex + (composedEndIndex - composedStartIndex),
endIndex - beginIndex);
}
return new AttributedString(committed).getIterator();
}
public int getCommittedTextLength() {
int length = getDocument().getTextLength();
if (composedText != null) {
length -= composedText.length();
}
return length;
}
public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
return null;
}
public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
String text = getSelectionModel().getSelectedText();
return text == null ? null : new AttributedString(text).getIterator();
}
private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
StringBuffer strBuf = new StringBuffer();
// create attributed string with no attributes
for (char c = text.setIndex(composedIndex);
c != CharacterIterator.DONE; c = text.next()) {
strBuf.append(c);
}
composedText = new String(strBuf);
}
private void setInputMethodCaretPosition(InputMethodEvent e) {
if (composedText != null) {
int dot;
dot = composedTextStart;
TextHitInfo caretPos = e.getCaret();
if (caretPos != null) {
int index = caretPos.getInsertionIndex();
dot += index;
}
getCaretModel().moveToOffset(dot);
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
}
private void runUndoTransparent(final Runnable runnable) {
CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
public void run() {
CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(runnable);
}
}, "", null);
}
});
}
private void replaceInputMethodText(InputMethodEvent e) {
int commitCount = e.getCommittedCharacterCount();
AttributedCharacterIterator text = e.getText();
int composedTextIndex;
// old composed text deletion
final Document doc = getDocument();
if (composedText != null) {
if (!isViewer() && doc.isWritable()) {
runUndoTransparent(new Runnable() {
public void run() {
doc.deleteString(Math.max(0, composedTextStart), Math.min(composedTextEnd, doc.getTextLength()));
}
});
}
composedText = null;
}
if (text != null) {
text.first();
// committed text insertion
if (commitCount > 0) {
for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
processKeyTyped(c);
}
}
}
// new composed text insertion
if (!isViewer() && doc.isWritable()) {
composedTextIndex = text.getIndex();
if (composedTextIndex < text.getEndIndex()) {
createComposedString(composedTextIndex, text);
runUndoTransparent(new Runnable() {
public void run() {
EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
}
});
composedTextStart = getCaretModel().getOffset();
composedTextEnd = getCaretModel().getOffset() + composedText.length();
}
}
}
}
}
private class MyMouseAdapter extends MouseAdapter {
public void mousePressed(MouseEvent e) {
runMousePressedCommand(e);
}
public void mouseReleased(MouseEvent e) {
runMouseReleasedCommand(e);
if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(Font.PLAIN) &&
Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
runMouseClickedCommand(e);
}
myMousePressedEvent = null;
}
public void mouseEntered(MouseEvent e) {
runMouseEnteredCommand(e);
}
public void mouseExited(MouseEvent e) {
runMouseExitedCommand(e);
EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
myGutterComponent.mouseExited(e);
}
HintManager.getInstance().getTooltipController().cancelTooltip(FOLDING_TOOLTIP_GROUP);
}
}
private static final TooltipGroup FOLDING_TOOLTIP_GROUP = new TooltipGroup("FOLDING_TOOLTIP_GROUP", 10);
private class MyMouseMotionListener implements MouseMotionListener {
public void mouseDragged(MouseEvent e) {
validateMousePointer(e);
runMouseDraggedCommand(e);
EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
myGutterComponent.mouseDragged(e);
}
EditorMouseMotionListener[] listeners = myMouseMotionListeners.toArray(
new EditorMouseMotionListener[myMouseMotionListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].mouseDragged(event);
}
}
public void mouseMoved(MouseEvent e) {
validateMousePointer(e);
EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
if (e.getSource() == myGutterComponent) {
myGutterComponent.mouseMoved(e);
}
if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
TooltipController controller = HintManager.getInstance().getTooltipController();
if (fold != null) {
DocumentFragment range = new DocumentFragment(myDocument, fold.getStartOffset(), fold.getEndOffset());
final Point p = SwingUtilities.convertPoint((Component)e.getSource(),
e.getPoint(),
getComponent().getRootPane().getLayeredPane());
controller.showTooltip(EditorImpl.this, p, new DocumentFragmentTooltipRenderer(range), false, FOLDING_TOOLTIP_GROUP);
}
else {
controller.cancelTooltip(FOLDING_TOOLTIP_GROUP);
}
}
EditorMouseMotionListener[] listeners = myMouseMotionListeners.toArray(
new EditorMouseMotionListener[myMouseMotionListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].mouseMoved(event);
}
}
}
private class MyColorSchemeDelegate implements EditorColorsScheme {
private HashMap<TextAttributesKey, TextAttributes> myOwnAttributes = new HashMap<TextAttributesKey, TextAttributes>();
private HashMap<ColorKey, Color> myOwnColors = new HashMap<ColorKey, Color>();
private HashMap<EditorFontType, Font> myFontsMap = null;
private Integer myFontSize = null;
private String myFaceName = null;
private EditorColorsScheme getGlobal() {
return EditorColorsManager.getInstance().getGlobalScheme();
}
public String getName() {
return getGlobal().getName();
}
protected void initFonts() {
String editorFontName = getEditorFontName();
int editorFontSize = getEditorFontSize();
myFontsMap = new HashMap<EditorFontType, Font>();
Font plainFont = new Font(editorFontName, Font.PLAIN, editorFontSize);
Font boldFont = new Font(editorFontName, Font.BOLD, editorFontSize);
Font italicFont = new Font(editorFontName, Font.ITALIC, editorFontSize);
Font boldItalicFont = new Font(editorFontName, Font.BOLD + Font.ITALIC, editorFontSize);
myFontsMap.put(EditorFontType.PLAIN, plainFont);
myFontsMap.put(EditorFontType.BOLD, boldFont);
myFontsMap.put(EditorFontType.ITALIC, italicFont);
myFontsMap.put(EditorFontType.BOLD_ITALIC, boldItalicFont);
reinitSettings();
}
public void setName(String name) {
getGlobal().setName(name);
}
public TextAttributes getAttributes(TextAttributesKey key) {
if (myOwnAttributes.containsKey(key)) return myOwnAttributes.get(key);
return getGlobal().getAttributes(key);
}
public void setAttributes(TextAttributesKey key, TextAttributes attributes) {
myOwnAttributes.put(key, attributes);
}
public Color getDefaultBackground() {
return getGlobal().getDefaultBackground();
}
public Color getDefaultForeground() {
return getGlobal().getDefaultForeground();
}
public Color getColor(ColorKey key) {
if (myOwnColors.containsKey(key)) return myOwnColors.get(key);
return getGlobal().getColor(key);
}
public void setColor(ColorKey key, Color color) {
myOwnColors.put(key, color);
// These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
// settings in this case.
myCaretModel.reinitSettings();
mySelectionModel.reinitSettings();
}
public int getEditorFontSize() {
if (myFontSize != null) return myFontSize.intValue();
return getGlobal().getEditorFontSize();
}
public void setEditorFontSize(int fontSize) {
if (fontSize < 8) fontSize = 8;
if (fontSize > 20) fontSize = 20;
myFontSize = new Integer(fontSize);
initFonts();
}
public String getEditorFontName() {
if (myFaceName != null) return myFaceName;
return getGlobal().getEditorFontName();
}
public void setEditorFontName(String fontName) {
myFaceName = fontName;
initFonts();
}
public Font getFont(EditorFontType key) {
if (myFontsMap != null) {
Font font = myFontsMap.get(key);
if (font != null) return font;
}
return getGlobal().getFont(key);
}
public void setFont(EditorFontType key, Font font) {
if (myFontsMap == null) {
initFonts();
}
myFontsMap.put(key, font);
reinitSettings();
}
public float getLineSpacing() {
return getGlobal().getLineSpacing();
}
public void setLineSpacing(float lineSpacing) {
getGlobal().setLineSpacing(lineSpacing);
}
public Object clone() {
return null;
}
public void readExternal(Element element) throws InvalidDataException {
}
public void writeExternal(Element element) throws WriteExternalException {
}
}
private static class MyTransferHandler extends TransferHandler {
private RangeMarker myDraggedRange = null;
private static Editor getEditor(JComponent comp) {
EditorComponentImpl editorComponent = (EditorComponentImpl)comp;
return editorComponent.getEditor();
}
public boolean importData(final JComponent comp, final Transferable t) {
final EditorImpl editor = (EditorImpl)getEditor(comp);
final int caretOffset = editor.getCaretModel().getOffset();
if (myDraggedRange != null &&
myDraggedRange.getStartOffset() <= caretOffset &&
caretOffset < myDraggedRange.getEndOffset()) {
return false;
}
if (myDraggedRange != null) {
editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
}
CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
try {
final int offset;
editor.getSelectionModel().removeSelection();
if (myDraggedRange != null) {
editor.getCaretModel().moveToOffset(caretOffset);
offset = caretOffset;
}
else {
offset = editor.getCaretModel().getOffset();
}
if (editor.getDocument().getRangeGuard(offset, offset) != null) return;
EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(
IdeActions.ACTION_EDITOR_PASTE);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable backup = clipboard.getContents(this);
ClipboardOwner clipboardOwner = new ClipboardOwner() {
public void lostOwnership(Clipboard clipboard, Transferable contents) {
}
};
clipboard.setContents(t, clipboardOwner);
editor.putUserData(LAST_PASTED_REGION, null);
pasteHandler.execute(editor, editor.getDataContext());
clipboard.setContents(backup, clipboardOwner);
TextRange range = editor.getUserData(LAST_PASTED_REGION);
if (range != null) {
editor.getCaretModel().moveToOffset(range.getStartOffset());
editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
}
}
catch (Exception exception) {
LOG.error(exception);
}
}
});
}
}, "Paste", DND_COMMAND_KEY);
return true;
}
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
Editor editor = getEditor(comp);
if (editor.isViewer()) return false;
if (!editor.getDocument().isWritable()) return false;
int offset = editor.getCaretModel().getOffset();
if (editor.getDocument().getRangeGuard(offset, offset) != null) return false;
for (int i = 0; i < transferFlavors.length; i++) {
DataFlavor transferFlavor = transferFlavors[i];
if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
}
return false;
}
protected Transferable createTransferable(JComponent c) {
Editor editor = getEditor(c);
String s = editor.getSelectionModel().getSelectedText();
if (s == null) return null;
int selectionStart = editor.getSelectionModel().getSelectionStart();
int selectionEnd = editor.getSelectionModel().getSelectionEnd();
myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
return new StringSelection(s);
}
public int getSourceActions(JComponent c) {
return COPY_OR_MOVE;
}
protected void exportDone(final JComponent source, Transferable data, int action) {
if (data == null) return;
if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
CommandProcessor.getInstance().executeCommand(((EditorImpl)getEditor(source)).myProject, new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
Document doc = getEditor(source).getDocument();
doc.startGuardedBlockChecking();
try {
doc.deleteString(myDraggedRange.getStartOffset(), myDraggedRange.getEndOffset());
}
catch (ReadOnlyFragmentModificationException e) {
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler().handle(e);
}
finally {
doc.stopGuardedBlockChecking();
}
}
});
}
}, "Move selection", DND_COMMAND_KEY);
}
myDraggedRange = null;
}
}
class EditorDocumentAdapter extends DocumentAdapter {
public void beforeDocumentChange(DocumentEvent e) {
beforeChangedUpdate(e);
}
public void documentChanged(DocumentEvent e) {
changedUpdate(e);
}
}
private class EditorSizeContainer {
private TIntArrayList myLineWidths;
private boolean myIsDirty;
private int myOldEndLine;
private Dimension mySize;
public void reset() {
int visLinesCount = getVisibleLineCount();
myLineWidths = new TIntArrayList(visLinesCount + 300);
int[] values = new int[visLinesCount];
Arrays.fill(values, -1);
myLineWidths.add(values);
myIsDirty = true;
}
public void beforeChange(DocumentEvent e) {
myOldEndLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
}
public void changedUpdate(DocumentEvent e) {
int startLine = offsetToVisualPosition(e.getOffset()).line;
int newEndLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
int oldEndLine = myOldEndLine;
if (myLineWidths.size() == 0) {
reset();
}
else {
int min = Math.min(oldEndLine, newEndLine);
for (int i = startLine; i <= min; i++) myLineWidths.set(i, -1);
if (newEndLine > oldEndLine) {
int[] delta = new int[newEndLine - oldEndLine];
Arrays.fill(delta, -1);
myLineWidths.insert(oldEndLine + 1, delta);
}
else if (oldEndLine > newEndLine) {
myLineWidths.remove(newEndLine + 1, oldEndLine - newEndLine);
}
myIsDirty = true;
}
}
private void validateSizes() {
if (!myIsDirty) return;
CharSequence text = myDocument.getCharsNoThreadCheck();
int lineCount = myLineWidths.size();
int end = myDocument.getTextLength();
int x = 0;
for (int line = 0; line < lineCount; line++) {
if (myLineWidths.getQuick(line) != -1) continue;
x = 0;
int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
if (offset >= myDocument.getTextLength()) {
myLineWidths.set(line, 0);
break;
}
IterationState state = new IterationState(EditorImpl.this, offset, false);
int fontType = state.getMergedAttributes().getFontType();
while (offset < end) {
char c = text.charAt(offset);
if (offset >= state.getEndOffset()) {
state.advance();
fontType = state.getMergedAttributes().getFontType();
}
FoldRegion collapsed = state.getCurrentFold();
if (collapsed != null) {
String placeholder = collapsed.getPlaceholderText();
for (int i = 0; i < placeholder.length(); i++) {
x += charWidth(placeholder.charAt(i), fontType);
}
offset = collapsed.getEndOffset();
}
else {
if (c == '\t') {
x = nextTabStop(x);
offset++;
}
else {
if (c == '\n') {
myLineWidths.set(line, x);
if (line + 1 >= lineCount || myLineWidths.getQuick(line + 1) != -1) break;
offset++;
x = 0;
line++;
}
else {
x += charWidth(c, fontType);
offset++;
}
}
}
}
}
if (lineCount > 0) {
myLineWidths.set(lineCount - 1, x); // Last line can be non-zero length and won't be caught by in-loop procedure since latter only react on \n's
}
int maxWidth = 0;
for (int i = 0; i < lineCount; i++) {
maxWidth = Math.max(maxWidth, myLineWidths.getQuick(i));
}
mySize = new Dimension(maxWidth, getLineHeight() * lineCount);
myIsDirty = false;
}
public Dimension getContentSize() {
validateSizes();
return mySize;
}
}
public EditorGutter getGutter() {
return getGutterComponentEx();
}
}