optimization: replace all occurrence is dramatically faster
[fedora-idea.git] / platform-impl / src / com / intellij / openapi / editor / impl / EditorImpl.java
blob991a822516df16e0e8b69822c87a7e47c2efd227
1 package com.intellij.openapi.editor.impl;
3 import com.intellij.Patches;
4 import com.intellij.codeInsight.hint.DocumentFragmentTooltipRenderer;
5 import com.intellij.codeInsight.hint.TooltipController;
6 import com.intellij.codeInsight.hint.TooltipGroup;
7 import com.intellij.concurrency.JobScheduler;
8 import com.intellij.ide.*;
9 import com.intellij.ide.dnd.DnDManager;
10 import com.intellij.openapi.actionSystem.*;
11 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
12 import com.intellij.openapi.application.ApplicationManager;
13 import com.intellij.openapi.application.ModalityState;
14 import com.intellij.openapi.application.ex.ApplicationManagerEx;
15 import com.intellij.openapi.command.CommandProcessor;
16 import com.intellij.openapi.command.UndoConfirmationPolicy;
17 import com.intellij.openapi.diagnostic.Logger;
18 import com.intellij.openapi.editor.*;
19 import com.intellij.openapi.editor.actionSystem.EditorAction;
20 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
21 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
22 import com.intellij.openapi.editor.colors.*;
23 import com.intellij.openapi.editor.event.*;
24 import com.intellij.openapi.editor.ex.*;
25 import com.intellij.openapi.editor.ex.util.EditorUtil;
26 import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter;
27 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
28 import com.intellij.openapi.editor.highlighter.HighlighterClient;
29 import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
30 import com.intellij.openapi.editor.impl.event.MarkupModelListener;
31 import com.intellij.openapi.editor.markup.*;
32 import com.intellij.openapi.fileEditor.FileDocumentManager;
33 import com.intellij.openapi.progress.ProgressManager;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.util.*;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.ui.GuiUtils;
39 import com.intellij.ui.JScrollPane2;
40 import com.intellij.util.Alarm;
41 import com.intellij.util.IJSwingUtilities;
42 import com.intellij.util.containers.HashMap;
43 import com.intellij.util.ui.EmptyClipboardOwner;
44 import com.intellij.util.ui.UIUtil;
45 import com.intellij.util.ui.update.UiNotifyConnector;
46 import gnu.trove.TIntArrayList;
47 import org.jdom.Element;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
52 import javax.swing.*;
53 import javax.swing.Timer;
54 import javax.swing.plaf.ScrollBarUI;
55 import javax.swing.plaf.basic.BasicScrollBarUI;
56 import java.awt.*;
57 import java.awt.datatransfer.Clipboard;
58 import java.awt.datatransfer.DataFlavor;
59 import java.awt.datatransfer.StringSelection;
60 import java.awt.datatransfer.Transferable;
61 import java.awt.dnd.DropTarget;
62 import java.awt.dnd.DropTargetAdapter;
63 import java.awt.dnd.DropTargetDragEvent;
64 import java.awt.dnd.DropTargetDropEvent;
65 import java.awt.event.*;
66 import java.awt.font.TextHitInfo;
67 import java.awt.im.InputMethodRequests;
68 import java.beans.PropertyChangeListener;
69 import java.beans.PropertyChangeSupport;
70 import java.lang.reflect.Field;
71 import java.lang.reflect.InvocationTargetException;
72 import java.text.AttributedCharacterIterator;
73 import java.text.AttributedString;
74 import java.text.CharacterIterator;
75 import java.util.*;
76 import java.util.concurrent.CopyOnWriteArrayList;
77 import java.util.concurrent.ScheduledFuture;
78 import java.util.concurrent.TimeUnit;
80 public final class EditorImpl extends UserDataHolderBase implements EditorEx, HighlighterClient {
81 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
82 private static final Key DND_COMMAND_KEY = Key.create("DndCommand");
83 public static final Key<Boolean> DO_DOCUMENT_UPDATE_TEST = Key.create("DoDocumentUpdateTest");
84 private final DocumentImpl myDocument;
86 private JPanel myPanel;
87 private JScrollPane myScrollPane;
88 private EditorComponentImpl myEditorComponent;
89 private EditorGutterComponentImpl myGutterComponent;
91 static {
92 @SuppressWarnings({"UnusedDeclaration"})
93 ComplementaryFontsRegistry registry; // load costly font info
96 private final CommandProcessor myCommandProcessor;
97 private MyScrollBar myVerticalScrollBar;
99 private final CopyOnWriteArrayList<EditorMouseListener> myMouseListeners = new CopyOnWriteArrayList<EditorMouseListener>();
100 private final CopyOnWriteArrayList<EditorMouseMotionListener> myMouseMotionListeners;
102 private int myCharHeight = -1;
103 private int myLineHeight = -1;
104 private int myDescent = -1;
106 private boolean myIsInsertMode = true;
108 private final CaretCursor myCaretCursor;
109 private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
111 private final Key<Object> MOUSE_DRAGGED_GROUP = Key.create("MouseDraggedGroup");
113 private final DocumentListener myEditorDocumentAdapter;
115 private final SettingsImpl mySettings;
117 private boolean isReleased = false;
119 private MouseEvent myMousePressedEvent = null;
121 private int mySavedSelectionStart = -1;
122 private int mySavedSelectionEnd = -1;
123 private int myLastColumnNumber = 0;
125 private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
126 private MyEditable myEditable;
128 private EditorColorsScheme myScheme;
129 private final boolean myIsViewer;
130 private final SelectionModelImpl mySelectionModel;
131 private final EditorMarkupModelImpl myMarkupModel;
132 private final FoldingModelImpl myFoldingModel;
133 private final ScrollingModelImpl myScrollingModel;
134 private final CaretModelImpl myCaretModel;
136 private static final RepaintCursorCommand ourCaretBlinkingCommand;
138 // private final BorderEffect myBorderEffect = new BorderEffect();
140 private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
141 private FoldRegion myMouseSelectedRegion = null;
143 private static final int MOUSE_SELECTION_STATE_NONE = 0;
144 private static final int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
145 private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
147 private final MarkupModelListener myMarkupModelListener;
149 private EditorHighlighter myHighlighter;
151 private int myScrollbarOrientation;
152 private boolean myMousePressedInsideSelection;
153 private FontMetrics myPlainFontMetrics;
154 private FontMetrics myBoldFontMetrics;
155 private FontMetrics myItalicFontMetrics;
156 private FontMetrics myBoldItalicFontMetrics;
158 private static final int CACHED_CHARS_BUFFER_SIZE = 300;
160 private final ArrayList<CachedFontContent> myFontCache = new ArrayList<CachedFontContent>();
161 private FontInfo myCurrentFontType = null;
163 private final EditorSizeContainer mySizeContainer = new EditorSizeContainer();
165 private Runnable myCursorUpdater;
166 private int myCaretUpdateVShift;
167 private final Project myProject;
168 private long myMouseSelectionChangeTimestamp;
169 private int mySavedCaretOffsetForDNDUndoHack;
170 private final ArrayList<FocusChangeListener> myFocusListeners = new ArrayList<FocusChangeListener>();
172 private MyInputMethodHandler myInputMethodRequestsHandler;
173 private InputMethodRequests myInputMethodRequestsSwingWrapper;
174 private boolean myIsOneLineMode;
175 private boolean myIsRendererMode;
176 private VirtualFile myVirtualFile;
177 private boolean myIsColumnMode = false;
178 private Color myForcedBackground = null;
179 private Dimension myPreferredSize;
180 private Runnable myGutterSizeUpdater = null;
181 private Alarm myAppleRepaintAlarm;
182 private boolean myEmbeddedIntoDialogWrapper;
183 private CachedFontContent myLastCache;
184 private boolean mySpacesHaveSameWidth;
186 private Point myLastBackgroundPosition = null;
187 private Color myLastBackgroundColor = null;
188 private int myLastBackgroundWidth;
189 private static final boolean ourIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
190 private JPanel myHeaderPanel;
191 private JLayeredPane myLayeredPane;
193 private MouseEvent myInitialMouseEvent;
194 private boolean myIgnoreMouseEventsConsequitiveToInitial;
196 private String myReleasedAt = null;
198 static {
199 ourCaretBlinkingCommand = new RepaintCursorCommand();
200 ourCaretBlinkingCommand.start();
204 public EditorImpl(Document document, boolean viewer, Project project) {
205 myProject = project;
206 myDocument = (DocumentImpl)document;
207 myScheme = new MyColorSchemeDelegate();
208 myIsViewer = viewer;
209 mySettings = new SettingsImpl(this);
211 mySelectionModel = new SelectionModelImpl(this);
212 myMarkupModel = new EditorMarkupModelImpl(this);
213 myFoldingModel = new FoldingModelImpl(this);
214 myCaretModel = new CaretModelImpl(this);
215 mySizeContainer.reset();
217 myCommandProcessor = CommandProcessor.getInstance();
219 myEditorDocumentAdapter = new EditorDocumentAdapter();
220 myMouseMotionListeners = new CopyOnWriteArrayList<EditorMouseMotionListener>();
222 myMarkupModelListener = new MarkupModelListener() {
223 public void rangeHighlighterChanged(MarkupModelEvent event) {
224 assertIsDispatchThread();
226 RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl)event.getHighlighter();
227 if (rangeHighlighter.isValid()) {
228 int start = rangeHighlighter.getAffectedAreaStartOffset();
229 int end = rangeHighlighter.getAffectedAreaEndOffset();
230 int startLine = myDocument.getLineNumber(start);
231 int endLine = myDocument.getLineNumber(end);
232 repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount()));
234 else {
235 repaint(0, getDocument().getTextLength());
237 ((EditorMarkupModelImpl)getMarkupModel()).repaint();
238 ((EditorMarkupModelImpl)getMarkupModel()).markDirtied();
239 GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
240 if (renderer != null) {
241 updateGutterSize();
243 updateCaretCursor();
247 ((MarkupModelEx)myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
248 ((MarkupModelEx)getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
250 myDocument.addDocumentListener(myFoldingModel);
251 myDocument.addDocumentListener(myCaretModel);
252 myDocument.addDocumentListener(mySelectionModel);
253 myDocument.addDocumentListener(myEditorDocumentAdapter);
255 myCaretCursor = new CaretCursor();
257 myFoldingModel.flushCaretShift();
258 myScrollbarOrientation = VERTICAL_SCROLLBAR_RIGHT;
260 EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
261 setHighlighter(highlighter);
263 initComponent();
265 myScrollingModel = new ScrollingModelImpl(this);
267 myGutterComponent.updateSize();
268 Dimension preferredSize = getPreferredSize();
269 myEditorComponent.setSize(preferredSize);
271 if (Patches.APPLE_BUG_ID_3716835) {
272 myScrollingModel.addVisibleAreaListener(new VisibleAreaListener() {
273 public void visibleAreaChanged(VisibleAreaEvent e) {
274 if (myAppleRepaintAlarm == null) {
275 myAppleRepaintAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
277 myAppleRepaintAlarm.cancelAllRequests();
278 myAppleRepaintAlarm.addRequest(new Runnable() {
279 public void run() {
280 repaint(0, myDocument.getTextLength());
282 }, 50, ModalityState.stateForComponent(myEditorComponent));
287 updateCaretCursor();
289 // This hacks context layout problem where editor appears scrolled to the right just after it is created.
290 if (!ourIsUnitTestMode) {
291 UiNotifyConnector.doWhenFirstShown(myEditorComponent, new Runnable() {
292 public void run() {
293 if (!isDisposed()) {
294 myScrollingModel.disableAnimation();
295 myScrollingModel.scrollHorizontally(0);
296 myScrollingModel.enableAnimation();
303 public boolean isViewer() {
304 return myIsViewer || myIsRendererMode;
307 public boolean isRendererMode() {
308 return myIsRendererMode;
311 public void setRendererMode(boolean isRendererMode) {
312 myIsRendererMode = isRendererMode;
315 public void setFile(VirtualFile vFile) {
316 myVirtualFile = vFile;
317 reinitSettings();
320 public VirtualFile getVirtualFile() {
321 return myVirtualFile;
324 @NotNull
325 public SelectionModel getSelectionModel() {
326 return mySelectionModel;
329 @NotNull
330 public MarkupModel getMarkupModel() {
331 return myMarkupModel;
334 @NotNull
335 public FoldingModel getFoldingModel() {
336 return myFoldingModel;
339 @NotNull
340 public CaretModel getCaretModel() {
341 return myCaretModel;
344 @NotNull
345 public ScrollingModel getScrollingModel() {
346 return myScrollingModel;
349 @NotNull
350 public EditorSettings getSettings() {
351 assertIsDispatchThread();
352 return mySettings;
355 public void reinitSettings() {
356 assertIsDispatchThread();
357 myCharHeight = -1;
358 myLineHeight = -1;
359 myDescent = -1;
360 myPlainFontMetrics = null;
362 myCaretModel.reinitSettings();
363 mySelectionModel.reinitSettings();
364 mySettings.reinitSettings();
365 ourCaretBlinkingCommand.setBlinkCaret(mySettings.isBlinkCaret());
366 ourCaretBlinkingCommand.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
367 mySizeContainer.reset();
368 myFoldingModel.refreshSettings();
369 myFoldingModel.rebuild();
371 if (myScheme instanceof MyColorSchemeDelegate) {
372 ((MyColorSchemeDelegate)myScheme).updateGlobalScheme();
374 myHighlighter.setColorScheme(myScheme);
376 myGutterComponent.reinitSettings();
377 myGutterComponent.revalidate();
379 myEditorComponent.repaint();
381 updateCaretCursor();
383 if (myInitialMouseEvent != null) {
384 myIgnoreMouseEventsConsequitiveToInitial = true;
388 public void release() {
389 if (isReleased) {
390 LOG.error("Double release. First released at: =====\n" + myReleasedAt+"\n======");
393 myReleasedAt = StringUtil.getThrowableText(new Throwable());
395 isReleased = true;
396 myDocument.removeDocumentListener(myHighlighter);
397 myDocument.removeDocumentListener(myEditorDocumentAdapter);
398 myDocument.removeDocumentListener(myFoldingModel);
399 myDocument.removeDocumentListener(myCaretModel);
400 myDocument.removeDocumentListener(mySelectionModel);
402 MarkupModelEx markupModel = (MarkupModelEx)myDocument.getMarkupModel(myProject, false);
403 if (markupModel instanceof MarkupModelImpl) {
404 markupModel.removeMarkupModelListener(myMarkupModelListener);
407 myMarkupModel.dispose();
409 myLineHeight = -1;
410 myCharHeight = -1;
411 myDescent = -1;
412 myPlainFontMetrics = null;
413 myScrollingModel.dispose();
414 myGutterComponent.dispose();
415 clearCaretThread();
418 private void clearCaretThread() {
419 synchronized (ourCaretBlinkingCommand) {
420 if (ourCaretBlinkingCommand.myEditor == this) {
421 ourCaretBlinkingCommand.myEditor = null;
426 private void initComponent() {
427 myEditorComponent = new EditorComponentImpl(this);
428 // myStatusBar = new EditorStatusBarImpl();
430 myLayeredPane = new JLayeredPane() {
431 @Override
432 public void doLayout() {
433 final Component[] components = getComponents();
434 final Rectangle r = getBounds();
435 for (Component c : components) {
436 if (c instanceof JScrollPane) {
437 c.setBounds(0, 0, r.width, r.height);
438 } else {
439 final Dimension d = c.getPreferredSize();
440 final MyScrollBar scrollBar = getVerticalScrollBar();
441 c.setBounds(r.width - d.width - scrollBar.getWidth() - 30, 20, d.width, d.height);
447 myScrollPane = new JScrollPane2() {
448 protected void processMouseWheelEvent(MouseWheelEvent e) {
449 if (mySettings.isWheelFontChangeEnabled()) {
450 boolean changeFontSize = SystemInfo.isMac
451 ? !e.isControlDown() && e.isMetaDown() && !e.isAltDown() && !e.isShiftDown()
452 : e.isControlDown() && !e.isMetaDown() && !e.isAltDown() && !e.isShiftDown();
453 if (changeFontSize) {
454 setFontSize(myScheme.getEditorFontSize() + e.getWheelRotation());
455 return;
459 super.processMouseWheelEvent(e);
462 myPanel = new JPanel() {
463 public void addNotify() {
464 super.addNotify();
465 if (((JComponent)getParent()).getBorder() != null) myScrollPane.setBorder(null);
469 //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
470 myPanel.setLayout(new BorderLayout());
471 myHeaderPanel = new JPanel(new BorderLayout()) {
472 private int myOldHeight = 0;
474 public void revalidate() {
475 myOldHeight = getHeight();
476 super.revalidate();
479 protected void validateTree() {
480 int height = myOldHeight;
481 super.validateTree();
482 height -= getHeight();
484 if (height != 0) {
485 myVerticalScrollBar.setValue(myVerticalScrollBar.getValue() - height);
487 myOldHeight = getHeight();
491 myPanel.add(myHeaderPanel, BorderLayout.NORTH);
493 myVerticalScrollBar = new MyScrollBar(Adjustable.VERTICAL);
495 myGutterComponent = new EditorGutterComponentImpl(this);
496 myGutterComponent.setOpaque(true);
498 myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
499 final MyScrollBar horizontalScrollBar = new MyScrollBar(Adjustable.HORIZONTAL);
500 myScrollPane.setHorizontalScrollBar(horizontalScrollBar);
501 myScrollPane.setViewportView(myEditorComponent);
502 //myScrollPane.setBorder(null);
503 myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
504 myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
507 myScrollPane.setRowHeaderView(myGutterComponent);
508 stopOptimizedScrolling();
510 myEditorComponent.setTransferHandler(new MyTransferHandler());
511 myEditorComponent.setAutoscrolls(true);
513 /* Default mode till 1.4.0
514 * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
516 myLayeredPane.add(myScrollPane, JLayeredPane.DEFAULT_LAYER);
517 myPanel.add(myLayeredPane);
519 if (ContextMenuImpl.mayShowToolbar(myDocument)) {
520 final JComponent contextMenu = new ContextMenuImpl(myScrollPane, this);
521 myLayeredPane.add(contextMenu, JLayeredPane.POPUP_LAYER);
524 //myPanel.add(myScrollPane);
526 myEditorComponent.addKeyListener(new KeyAdapter() {
527 public void keyTyped(KeyEvent event) {
528 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
529 if (event.isConsumed()) {
530 return;
532 if (processKeyTyped(event)) {
533 event.consume();
538 MyMouseAdapter mouseAdapter = new MyMouseAdapter();
539 myEditorComponent.addMouseListener(mouseAdapter);
540 myGutterComponent.addMouseListener(mouseAdapter);
542 MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
543 myEditorComponent.addMouseMotionListener(mouseMotionListener);
544 myGutterComponent.addMouseMotionListener(mouseMotionListener);
546 myEditorComponent.addFocusListener(new FocusAdapter() {
547 public void focusGained(FocusEvent e) {
548 myCaretCursor.activate();
549 int caretLine = getCaretModel().getLogicalPosition().line;
550 repaintLines(caretLine, caretLine);
551 fireFocusGained();
554 public void focusLost(FocusEvent e) {
555 clearCaretThread();
556 int caretLine = getCaretModel().getLogicalPosition().line;
557 repaintLines(caretLine, caretLine);
558 fireFocusLost();
562 // myBorderEffect.reset();
563 try {
564 final DropTarget dropTarget = myEditorComponent.getDropTarget();
565 if (dropTarget != null) { // might be null in headless environment
566 dropTarget.addDropTargetListener(new DropTargetAdapter() {
567 public void drop(DropTargetDropEvent dtde) {
570 public void dragOver(DropTargetDragEvent dtde) {
571 Point location = dtde.getLocation();
573 moveCaretToScreenPos(location.x, location.y);
574 getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
579 catch (TooManyListenersException e) {
580 LOG.error(e);
583 myPanel.addComponentListener(new ComponentAdapter() {
584 public void componentResized(ComponentEvent e) {
585 myMarkupModel.repaint();
590 public void setFontSize(final int fontSize) {
591 int oldFontSize = myScheme.getEditorFontSize();
592 myScheme.setEditorFontSize(fontSize);
593 myPropertyChangeSupport.firePropertyChange(PROP_FONT_SIZE, oldFontSize, fontSize);
596 private void processKeyTyped(char c) {
597 // [vova] This is patch for Mac OS X. Under Mac "input methods"
598 // is handled before our EventQueue consume upcoming KeyEvents.
599 IdeEventQueue queue = IdeEventQueue.getInstance();
600 if (queue.shouldNotTypeInEditor() || ProgressManager.getInstance().hasModalProgressIndicator()) {
601 return;
603 ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
604 DataContext dataContext = getDataContext();
605 actionManager.fireBeforeEditorTyping(c, dataContext);
606 EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
609 private void fireFocusLost() {
610 FocusChangeListener[] listeners = getFocusListeners();
611 for (FocusChangeListener listener : listeners) {
612 listener.focusLost(this);
616 private FocusChangeListener[] getFocusListeners() {
617 return myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
620 private void fireFocusGained() {
621 FocusChangeListener[] listeners = getFocusListeners();
622 for (FocusChangeListener listener : listeners) {
623 listener.focusGained(this);
627 public void setHighlighter(EditorHighlighter highlighter) {
628 assertIsDispatchThread();
629 final Document document = getDocument();
630 if (myHighlighter != null) {
631 document.removeDocumentListener(myHighlighter);
634 document.addDocumentListener(highlighter);
635 highlighter.setEditor(this);
636 highlighter.setText(document.getCharsSequence());
637 myHighlighter = highlighter;
638 if (document instanceof DocumentImpl) {
639 ((DocumentImpl)document).rememberEditorHighlighterForCachesOptimization(highlighter);
642 if (myPanel != null) {
643 reinitSettings();
647 public EditorHighlighter getHighlighter() {
648 assertIsDispatchThread();
649 return myHighlighter;
652 @NotNull
653 public JComponent getContentComponent() {
654 return myEditorComponent;
657 public EditorGutterComponentEx getGutterComponentEx() {
658 return myGutterComponent;
661 public void addPropertyChangeListener(PropertyChangeListener listener) {
662 myPropertyChangeSupport.addPropertyChangeListener(listener);
665 public void removePropertyChangeListener(PropertyChangeListener listener) {
666 myPropertyChangeSupport.removePropertyChangeListener(listener);
669 public void setInsertMode(boolean mode) {
670 assertIsDispatchThread();
671 boolean oldValue = myIsInsertMode;
672 myIsInsertMode = mode;
673 myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, mode);
674 //Repaint the caret line by moving caret to the same place
675 LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
676 getCaretModel().moveToLogicalPosition(caretPosition);
679 public boolean isInsertMode() {
680 return myIsInsertMode;
683 public void setColumnMode(boolean mode) {
684 assertIsDispatchThread();
685 boolean oldValue = myIsColumnMode;
686 myIsColumnMode = mode;
687 myPropertyChangeSupport.firePropertyChange(PROP_COLUMN_MODE, oldValue, mode);
690 public boolean isColumnMode() {
691 return myIsColumnMode;
694 private int yPositionToVisibleLineNumber(int y) {
695 return y / getLineHeight();
698 public int getSpaceWidth(int fontType) {
699 int width = charWidth(' ', fontType);
700 return width > 0 ? width : 1;
703 @NotNull
704 public VisualPosition xyToVisualPosition(@NotNull Point p) {
705 int line = yPositionToVisibleLineNumber(p.y);
707 int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
708 int textLength = myDocument.getTextLength();
710 if (offset >= textLength) return new VisualPosition(line, 0);
712 int column = 0;
713 int prevX = 0;
714 CharSequence text = myDocument.getCharsNoThreadCheck();
715 char c = ' ';
716 IterationState state = new IterationState(this, offset, false);
718 int fontType = state.getMergedAttributes().getFontType();
719 int spaceSize = getSpaceWidth(fontType);
721 int x = 0;
722 outer:
723 while (true) {
724 if (offset >= textLength) break;
726 if (offset >= state.getEndOffset()) {
727 state.advance();
728 fontType = state.getMergedAttributes().getFontType();
731 FoldRegion region = state.getCurrentFold();
732 if (region != null) {
733 char[] placeholder = region.getPlaceholderText().toCharArray();
734 for (char aPlaceholder : placeholder) {
735 c = aPlaceholder;
736 x += charWidth(c, fontType);
737 if (x >= p.x) break outer;
738 column++;
740 offset = region.getEndOffset();
742 else {
743 prevX = x;
744 c = text.charAt(offset);
745 if (c == '\n') {
746 break;
748 if (c == '\t') {
749 x = nextTabStop(x);
751 else {
752 x += charWidth(c, fontType);
755 if (x >= p.x) break;
757 if (c == '\t') {
758 column += (x - prevX) / spaceSize;
760 else {
761 column++;
764 offset++;
768 int charWidth = charWidth(c, fontType);
770 if (x >= p.x && c == '\t') {
771 if (mySettings.isCaretInsideTabs()) {
772 column += (p.x - prevX) / spaceSize;
773 if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
775 else {
776 if ((x - p.x) * 2 < x - prevX) {
777 column += (x - prevX) / spaceSize;
781 else {
782 if (x >= p.x) {
783 if ((x - p.x) * 2 < charWidth) column++;
785 else {
786 column += (p.x - x) / getSpaceWidth(fontType);
790 return new VisualPosition(line, column);
793 @NotNull
794 public VisualPosition offsetToVisualPosition(int offset) {
795 return logicalToVisualPosition(offsetToLogicalPosition(offset));
798 @NotNull
799 public LogicalPosition offsetToLogicalPosition(int offset) {
800 int line = calcLogicalLineNumber(offset);
801 int column = calcColumnNumber(offset, line);
802 return new LogicalPosition(line, column);
805 @NotNull
806 public LogicalPosition xyToLogicalPosition(@NotNull Point p) {
807 final Point pp;
808 if (p.x >= 0 && p.y >= 0) {
809 pp = p;
811 else {
812 pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
815 return visualToLogicalPosition(xyToVisualPosition(pp));
818 private int logicalLineToY(int line) {
819 VisualPosition visible = logicalToVisualPosition(new LogicalPosition(line, 0));
820 return visibleLineNumberToYPosition(visible.line);
823 @NotNull
824 public Point logicalPositionToXY(@NotNull LogicalPosition pos) {
825 VisualPosition visible = logicalToVisualPosition(pos);
826 int y = visibleLineNumberToYPosition(visible.line);
828 int lineStartOffset;
830 if (pos.line == 0) {
831 lineStartOffset = 0;
833 else {
834 if (pos.line >= myDocument.getLineCount()) {
835 lineStartOffset = myDocument.getTextLength();
837 else {
838 lineStartOffset = myDocument.getLineStartOffset(pos.line);
842 int x = getTabbedTextWidth(lineStartOffset, visible);
843 return new Point(x, y);
846 @NotNull
847 public Point visualPositionToXY(@NotNull VisualPosition visible) {
848 int y = visibleLineNumberToYPosition(visible.line);
849 int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
851 int lineStartOffset;
853 if (logLine == 0) {
854 lineStartOffset = 0;
856 else {
857 if (logLine >= myDocument.getLineCount()) {
858 lineStartOffset = myDocument.getTextLength();
860 else {
861 lineStartOffset = myDocument.getLineStartOffset(logLine);
865 int x = getTabbedTextWidth(lineStartOffset, visible);
866 return new Point(x, y);
869 private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
870 if (pos.column == 0) return 0;
872 int x = 0;
873 int offset = lineStartOffset;
874 CharSequence text = myDocument.getCharsNoThreadCheck();
875 int textLength = myDocument.getTextLength();
876 IterationState state = new IterationState(this, offset, false);
877 int fontType = state.getMergedAttributes().getFontType();
878 int spaceSize = getSpaceWidth(fontType);
880 int column = 0;
881 outer:
882 while (column < pos.column) {
883 if (offset >= textLength) break;
885 if (offset >= state.getEndOffset()) {
886 state.advance();
887 fontType = state.getMergedAttributes().getFontType();
890 FoldRegion region = state.getCurrentFold();
892 if (region != null) {
893 char[] placeholder = region.getPlaceholderText().toCharArray();
894 for (char aPlaceholder : placeholder) {
895 x += charWidth(aPlaceholder, fontType);
896 column++;
897 if (column >= pos.column) break outer;
899 offset = region.getEndOffset();
901 else {
902 char c = text.charAt(offset);
903 if (c == '\n') {
904 break;
906 if (c == '\t') {
907 int prevX = x;
908 x = nextTabStop(x);
909 column += (x - prevX) / spaceSize;
911 else {
912 x += charWidth(c, fontType);
913 column++;
915 offset++;
919 if (column != pos.column) {
920 x += getSpaceWidth(fontType) * (pos.column - column);
923 return x;
926 public int nextTabStop(int x) {
927 int tabSize = getTabSize();
928 if (tabSize <= 0) {
929 tabSize = 1;
932 tabSize *= getSpaceWidth(Font.PLAIN);
934 int nTabs = x / tabSize;
935 return (nTabs + 1) * tabSize;
938 public int visibleLineNumberToYPosition(int lineNum) {
939 if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
940 return lineNum * getLineHeight();
943 public void repaint(int startOffset, int endOffset) {
944 if (!isShowing() || myScrollPane == null || myDocument.isInBulkUpdate()) {
945 return;
948 assertIsDispatchThread();
950 if (endOffset > myDocument.getTextLength()) {
951 endOffset = myDocument.getTextLength();
953 if (startOffset < endOffset) {
954 int startLine = myDocument.getLineNumber(startOffset);
955 int endLine = myDocument.getLineNumber(endOffset);
956 repaintLines(startLine, endLine);
960 private boolean isShowing() {
961 return myGutterComponent != null && myGutterComponent.isShowing();
964 private void repaintToScreenBotton(int startLine) {
965 Rectangle visibleRect = getScrollingModel().getVisibleArea();
966 int yStartLine = logicalLineToY(startLine);
967 int yEndLine = visibleRect.y + visibleRect.height;
969 myEditorComponent.repaintEditorComponent(visibleRect.x, yStartLine, visibleRect.x + visibleRect.width, yEndLine - yStartLine);
970 myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
973 public void repaintLines(int startLine, int endLine) {
974 if (!isShowing()) return;
976 Rectangle visibleRect = getScrollingModel().getVisibleArea();
977 int yStartLine = logicalLineToY(startLine);
978 int yEndLine = logicalLineToY(endLine) + getLineHeight() + WAVE_HEIGHT;
980 myEditorComponent.repaintEditorComponent(visibleRect.x, yStartLine, visibleRect.x + visibleRect.width, yEndLine - yStartLine);
981 myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
984 private void beforeChangedUpdate(DocumentEvent e) {
985 if (!myDocument.isInBulkUpdate()) {
986 Rectangle viewRect = getScrollingModel().getVisibleArea();
987 Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
988 myCaretUpdateVShift = pos.y - viewRect.y;
990 mySizeContainer.beforeChange(e);
993 private void changedUpdate(DocumentEvent e) {
994 if (myScrollPane == null) return;
996 stopOptimizedScrolling();
997 mySelectionModel.removeBlockSelection();
999 mySizeContainer.changedUpdate(e);
1000 validateSize();
1002 int startLine = calcLogicalLineNumber(e.getOffset());
1003 int endLine = calcLogicalLineNumber(e.getOffset() + e.getNewLength());
1005 boolean painted = false;
1006 if (myDocument.getTextLength() > 0) {
1007 int startDocLine = myDocument.getLineNumber(e.getOffset());
1008 int endDocLine = myDocument.getLineNumber(e.getOffset() + e.getNewLength());
1009 if (e.getOldLength() > e.getNewLength() || startDocLine != endDocLine) {
1010 updateGutterSize();
1013 if (countLineFeeds(e.getOldFragment()) != countLineFeeds(e.getNewFragment())) {
1014 // Lines removed. Need to repaint till the end of the screen
1015 repaintToScreenBotton(startLine);
1016 painted = true;
1020 updateCaretCursor();
1021 if (!painted) {
1022 repaintLines(startLine, endLine);
1025 if (!myDocument.isInBulkUpdate()) {
1026 Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
1027 int scrollOffset = caretLocation.y - myCaretUpdateVShift;
1028 getScrollingModel().scrollVertically(scrollOffset);
1032 private static int countLineFeeds(CharSequence c) {
1033 return StringUtil.countNewLines(c);
1036 private void updateGutterSize() {
1037 if (myGutterSizeUpdater != null) return;
1038 myGutterSizeUpdater = new Runnable() {
1039 public void run() {
1040 if (!isDisposed() && isShowing()) {
1041 myGutterComponent.updateSize();
1043 myGutterSizeUpdater = null;
1047 SwingUtilities.invokeLater(myGutterSizeUpdater);
1050 void validateSize() {
1051 Dimension dim = getPreferredSize();
1053 if (!dim.equals(myPreferredSize) && !myDocument.isInBulkUpdate()) {
1054 myPreferredSize = dim;
1056 stopOptimizedScrolling();
1057 int lineNum = Math.max(1, getDocument().getLineCount());
1058 myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth(Integer.toString(lineNum + 2)) + 6);
1060 myEditorComponent.setSize(dim);
1061 myEditorComponent.fireResized();
1063 myMarkupModel.repaint();
1067 void recalcSizeAndRepaint() {
1068 mySizeContainer.reset();
1069 validateSize();
1070 myEditorComponent.repaintEditorComponent();
1073 @NotNull
1074 public Document getDocument() {
1075 return myDocument;
1078 @NotNull
1079 public JComponent getComponent() {
1080 return myPanel;
1083 public void addEditorMouseListener(@NotNull EditorMouseListener listener) {
1084 myMouseListeners.add(listener);
1087 public void removeEditorMouseListener(@NotNull EditorMouseListener listener) {
1088 boolean success = myMouseListeners.remove(listener);
1089 LOG.assertTrue(success);
1092 public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
1093 myMouseMotionListeners.add(listener);
1096 public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
1097 boolean success = myMouseMotionListeners.remove(listener);
1098 LOG.assertTrue(success);
1101 public boolean isDisposed() {
1102 return isReleased;
1105 void paint(Graphics g) {
1106 startOptimizedScrolling();
1108 if (myCursorUpdater != null) {
1109 myCursorUpdater.run();
1110 myCursorUpdater = null;
1113 Rectangle clip = getClipBounds(g);
1115 if (clip == null) {
1116 return;
1119 Rectangle viewRect = getScrollingModel().getVisibleArea();
1120 if (viewRect == null) {
1121 return;
1124 if (isReleased) {
1125 g.setColor(new Color(128, 255, 128));
1126 g.fillRect(clip.x, clip.y, clip.width, clip.height);
1128 return;
1131 paintBackgrounds(g, clip);
1132 paintRectangularSelection(g);
1133 paintRightMargin(g, clip);
1134 final MarkupModel docMarkup = myDocument.getMarkupModel(myProject);
1135 paintLineMarkersSeparators(g, clip, docMarkup);
1136 paintLineMarkersSeparators(g, clip, myMarkupModel);
1137 paintText(g, clip);
1138 paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
1139 BorderEffect borderEffect = new BorderEffect(this, g);
1140 borderEffect.paintHighlighters(getHighlighter());
1141 borderEffect.paintHighlighters(docMarkup.getAllHighlighters());
1142 borderEffect.paintHighlighters(getMarkupModel().getAllHighlighters());
1143 paintCaretCursor(g);
1145 paintComposedTextDecoration((Graphics2D)g);
1148 public void setHeaderComponent(JComponent header) {
1149 myHeaderPanel.removeAll();
1150 if (header != null) {
1151 myHeaderPanel.add(header);
1154 myHeaderPanel.revalidate();
1157 public boolean hasHeaderComponent() {
1158 return myHeaderPanel.getComponentCount() > 0;
1161 @Nullable
1162 public JComponent getHeaderComponent() {
1163 if (hasHeaderComponent()) {
1164 return (JComponent)myHeaderPanel.getComponent(0);
1166 return null;
1169 public void setBackgroundColor(Color color) {
1170 myForcedBackground = color;
1173 public void resetBackgourndColor() {
1174 myForcedBackground = null;
1177 public Color getForegroundColor() {
1178 return myScheme.getDefaultForeground();
1181 public Color getBackroundColor() {
1182 if (myForcedBackground != null) return myForcedBackground;
1184 return getBackgroundIgnoreForced();
1187 private Color getBackgroundColor(final TextAttributes attributes) {
1188 final Color attrColor = attributes.getBackgroundColor();
1189 return attrColor == myScheme.getDefaultBackground() ? getBackroundColor() : attrColor;
1192 private Color getBackgroundIgnoreForced() {
1193 Color color = myScheme.getDefaultBackground();
1194 if (myDocument.isWritable()) {
1195 return color;
1197 Color readOnlyColor = myScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR);
1198 return readOnlyColor != null ? readOnlyColor : color;
1201 private void paintComposedTextDecoration(Graphics2D g) {
1202 if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
1203 VisualPosition visStart =
1204 offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
1205 int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
1206 Point p1 = visualPositionToXY(visStart);
1207 Point p2 =
1208 logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
1210 Stroke saved = g.getStroke();
1211 BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2, 0, 2}, 0);
1212 g.setStroke(dotted);
1213 UIUtil.drawLine(g, p1.x, y, p2.x, y);
1214 g.setStroke(saved);
1218 private static Rectangle getClipBounds(Graphics g) {
1219 return g.getClipBounds();
1222 private void paintRightMargin(Graphics g, Rectangle clip) {
1223 Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
1224 if (!mySettings.isRightMarginShown() || rightMargin == null) {
1225 return;
1227 int x = mySettings.getRightMargin(myProject) * getSpaceWidth(Font.PLAIN);
1228 if (x >= clip.x && x < clip.x + clip.width) {
1229 g.setColor(rightMargin);
1230 UIUtil.drawLine(g, x, clip.y, x, clip.y + clip.height);
1234 private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
1235 if (myDocument.getLineCount() == 0) return;
1236 int startLineNumber = yPositionToVisibleLineNumber(clip.y);
1237 int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
1239 final MarkupModel docMarkup = myDocument.getMarkupModel(myProject);
1240 RangeHighlighter[] segmentHighlighters = docMarkup.getAllHighlighters();
1241 for (RangeHighlighter segmentHighlighter : segmentHighlighters) {
1242 paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighter, startLineNumber, endLineNumber);
1245 segmentHighlighters = getMarkupModel().getAllHighlighters();
1246 for (RangeHighlighter segmentHighlighter : segmentHighlighters) {
1247 paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighter, startLineNumber, endLineNumber);
1251 private void paintSegmentHighlighterAfterEndOfLine(Graphics g,
1252 RangeHighlighterEx segmentHighlighter,
1253 int startLineNumber,
1254 int endLineNumber) {
1255 if (!segmentHighlighter.isValid()) {
1256 return;
1258 if (segmentHighlighter.isAfterEndOfLine()) {
1259 int startOffset = segmentHighlighter.getStartOffset();
1260 int visibleStartLine = offsetToVisualPosition(startOffset).line;
1262 if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
1263 if (visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
1264 int logStartLine = offsetToLogicalPosition(startOffset).line;
1265 LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
1266 Point end = logicalPositionToXY(logPosition);
1267 int charWidth = getSpaceWidth(Font.PLAIN);
1268 int lineHeight = getLineHeight();
1269 TextAttributes attributes = segmentHighlighter.getTextAttributes();
1270 if (attributes != null && getBackgroundColor(attributes) != null) {
1271 g.setColor(getBackgroundColor(attributes));
1272 g.fillRect(end.x, end.y, charWidth, lineHeight);
1274 if (attributes != null && attributes.getEffectColor() != null) {
1275 int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
1276 g.setColor(attributes.getEffectColor());
1277 if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
1278 drawWave(g, end.x, end.x + charWidth - 1, y);
1280 else if (attributes.getEffectType() != EffectType.BOXED) {
1281 UIUtil.drawLine(g, end.x, y, end.x + charWidth - 1, y);
1289 public int getMaxWidthInRange(int startOffset, int endOffset) {
1290 int width = 0;
1291 VisualPosition start = offsetToVisualPosition(startOffset);
1292 VisualPosition end = offsetToVisualPosition(endOffset);
1294 for (int i = start.line; i <= end.line; i++) {
1295 int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
1296 int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
1298 if (lineWidth > width) {
1299 width = lineWidth;
1303 return width;
1306 private void paintBackgrounds(Graphics g, Rectangle clip) {
1307 Color defaultBackground = getBackroundColor();
1308 g.setColor(defaultBackground);
1309 g.fillRect(clip.x, clip.y, clip.width, clip.height);
1311 int lineHeight = getLineHeight();
1313 int visibleLineNumber = clip.y / lineHeight;
1315 int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
1317 if (startLineNumber >= myDocument.getLineCount() || startLineNumber < 0) {
1318 return;
1321 int start = myDocument.getLineStartOffset(startLineNumber);
1323 IterationState iterationState = new IterationState(this, start, true);
1325 LineIterator lIterator = createLineIterator();
1326 lIterator.start(start);
1327 if (lIterator.atEnd()) {
1328 return;
1331 myLastBackgroundPosition = null;
1332 myLastBackgroundColor = null;
1334 TextAttributes attributes = iterationState.getMergedAttributes();
1335 Color backColor = getBackgroundColor(attributes);
1336 Point position = new Point(0, visibleLineNumber * lineHeight);
1337 int fontType = attributes.getFontType();
1338 CharSequence text = myDocument.getCharsNoThreadCheck();
1339 int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
1340 while (!iterationState.atEnd() && !lIterator.atEnd()) {
1341 int hEnd = iterationState.getEndOffset();
1342 int lEnd = lIterator.getEnd();
1344 if (hEnd >= lEnd) {
1345 FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
1346 if (collapsedFolderAt == null) {
1347 position.x = drawBackground(g, backColor, text.subSequence(start, lEnd - lIterator.getSeparatorLength()), position, fontType,
1348 defaultBackground, clip);
1350 if (lIterator.getLineNumber() < lastLineIndex) {
1351 if (backColor != null && !backColor.equals(defaultBackground)) {
1352 g.setColor(backColor);
1353 g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
1356 else {
1357 paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground);
1358 break;
1361 position.x = 0;
1362 if (position.y > clip.y + clip.height) break;
1363 position.y += lineHeight;
1364 start = lEnd;
1367 lIterator.advance();
1369 else {
1370 FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
1371 if (collapsedFolderAt != null) {
1372 position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText(), position, fontType, defaultBackground, clip);
1374 else {
1375 if (hEnd > lEnd - lIterator.getSeparatorLength()) {
1376 position.x = drawBackground(g, backColor, text.subSequence(start, lEnd - lIterator.getSeparatorLength()), position, fontType,
1377 defaultBackground, clip);
1379 else {
1380 position.x = drawBackground(g, backColor, text.subSequence(start, hEnd), position, fontType, defaultBackground, clip);
1384 iterationState.advance();
1385 attributes = iterationState.getMergedAttributes();
1386 backColor = getBackgroundColor(attributes);
1387 fontType = attributes.getFontType();
1388 start = iterationState.getStartOffset();
1392 flushBackground(g, clip);
1394 if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
1395 paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground);
1399 private void paintRectangularSelection(Graphics g) {
1400 final SelectionModel model = getSelectionModel();
1401 if (!model.hasBlockSelection()) return;
1402 final LogicalPosition blockStart = model.getBlockStart();
1403 final LogicalPosition blockEnd = model.getBlockEnd();
1404 assert blockStart != null;
1405 assert blockEnd != null;
1407 final Point start = logicalPositionToXY(blockStart);
1408 final Point end = logicalPositionToXY(blockEnd);
1409 g.setColor(myScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR));
1410 final int y;
1411 final int height;
1412 if (start.y <= end.y) {
1413 y = start.y;
1414 height = end.y - y + getLineHeight();
1416 else {
1417 y = end.y;
1418 height = start.y - end.y + getLineHeight();
1420 final int x = Math.min(start.x, end.x);
1421 final int width = Math.max(2, Math.abs(end.x - start.x));
1422 g.fillRect(x, y, width, height);
1425 private static void paintAfterFileEndBackground(IterationState iterationState,
1426 Graphics g,
1427 Point position,
1428 Rectangle clip,
1429 int lineHeight,
1430 final Color defaultBackground) {
1431 Color backColor = iterationState.getPastFileEndBackground();
1432 if (backColor != null && !backColor.equals(defaultBackground)) {
1433 g.setColor(backColor);
1434 g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
1438 private int drawBackground(Graphics g,
1439 Color backColor,
1440 CharSequence text,
1441 Point position,
1442 int fontType,
1443 Color defaultBackground,
1444 Rectangle clip) {
1445 int w = getTextSegmentWidth(text, position.x, fontType, clip);
1447 if (backColor != null && !backColor.equals(defaultBackground) && clip.intersects(position.x, position.y, w, getLineHeight())) {
1448 if (backColor.equals(myLastBackgroundColor) && myLastBackgroundPosition.y == position.y &&
1449 myLastBackgroundPosition.x + myLastBackgroundWidth == position.x) {
1450 myLastBackgroundWidth += w;
1452 else {
1453 flushBackground(g, clip);
1454 myLastBackgroundColor = backColor;
1455 myLastBackgroundPosition = new Point(position);
1456 myLastBackgroundWidth = w;
1460 return position.x + w;
1463 private void flushBackground(Graphics g, final Rectangle clip) {
1464 if (myLastBackgroundColor != null) {
1465 final Point position = myLastBackgroundPosition;
1466 final int w = myLastBackgroundWidth;
1467 final int height = getLineHeight();
1468 if (clip.intersects(position.x, position.y, w, height)) {
1469 g.setColor(myLastBackgroundColor);
1470 g.fillRect(position.x, position.y, w, height);
1472 myLastBackgroundColor = null;
1476 private LineIterator createLineIterator() {
1477 return myDocument.createLineIterator();
1480 private void paintText(Graphics g, Rectangle clip) {
1481 myLastCache = null;
1482 final int plainSpaceWidth = getSpaceWidth(Font.PLAIN);
1483 final int boldSpaceWidth = getSpaceWidth(Font.BOLD);
1484 final int italicSpaceWidth = getSpaceWidth(Font.ITALIC);
1485 final int boldItalicSpaceWidth = getSpaceWidth(Font.BOLD | Font.ITALIC);
1486 mySpacesHaveSameWidth =
1487 plainSpaceWidth == boldSpaceWidth && plainSpaceWidth == italicSpaceWidth && plainSpaceWidth == boldItalicSpaceWidth;
1489 int lineHeight = getLineHeight();
1491 int visibleLineNumber = clip.y / lineHeight;
1493 int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
1495 if (startLineNumber >= myDocument.getLineCount() || startLineNumber < 0) {
1496 return;
1499 int start = myDocument.getLineStartOffset(startLineNumber);
1501 IterationState iterationState = new IterationState(this, start, true);
1503 LineIterator lIterator = createLineIterator();
1504 lIterator.start(start);
1505 if (lIterator.atEnd()) {
1506 return;
1509 TextAttributes attributes = iterationState.getMergedAttributes();
1510 Color currentColor = attributes.getForegroundColor();
1511 if (currentColor == null) {
1512 currentColor = getForegroundColor();
1514 Color effectColor = attributes.getEffectColor();
1515 EffectType effectType = attributes.getEffectType();
1516 int fontType = attributes.getFontType();
1517 myCurrentFontType = null;
1518 g.setColor(currentColor);
1519 Point position = new Point(0, visibleLineNumber * lineHeight);
1520 final char[] chars = myDocument.getRawChars();
1521 while (!iterationState.atEnd() && !lIterator.atEnd()) {
1522 int hEnd = iterationState.getEndOffset();
1523 int lEnd = lIterator.getEnd();
1524 if (hEnd >= lEnd) {
1525 FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
1526 if (collapsedFolderAt == null) {
1527 drawString(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType, fontType,
1528 currentColor);
1529 position.x = 0;
1530 if (position.y > clip.y + clip.height) break;
1531 position.y += lineHeight;
1532 start = lEnd;
1535 // myBorderEffect.eolReached(g, this);
1536 lIterator.advance();
1538 else {
1539 FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
1540 if (collapsedFolderAt != null) {
1541 int foldingXStart = position.x;
1542 position.x =
1543 drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
1544 BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, position.x, getLineHeight(), effectColor, effectType);
1547 else {
1548 if (hEnd > lEnd - lIterator.getSeparatorLength()) {
1549 position.x = drawString(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
1550 fontType, currentColor);
1552 else {
1553 position.x = drawString(g, chars, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
1557 iterationState.advance();
1558 attributes = iterationState.getMergedAttributes();
1560 currentColor = attributes.getForegroundColor();
1561 if (currentColor == null) {
1562 currentColor = getForegroundColor();
1565 effectColor = attributes.getEffectColor();
1566 effectType = attributes.getEffectType();
1567 fontType = attributes.getFontType();
1569 start = iterationState.getStartOffset();
1573 FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
1574 if (collapsedFolderAt != null) {
1575 int foldingXStart = position.x;
1576 int foldingXEnd =
1577 drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
1578 BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, foldingXEnd, getLineHeight(), effectColor, effectType);
1579 // myBorderEffect.collapsedFolderReached(g, this);
1582 flushCachedChars(g);
1585 private class CachedFontContent {
1586 final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
1587 final int[] starts = new int[CACHED_CHARS_BUFFER_SIZE];
1588 final int[] ends = new int[CACHED_CHARS_BUFFER_SIZE];
1589 final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
1590 final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
1591 final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
1593 int myCount = 0;
1594 final FontInfo myFontType;
1596 private char[] myLastData;
1598 private CachedFontContent(FontInfo fontInfo) {
1599 myFontType = fontInfo;
1602 private void flushContent(Graphics g) {
1603 if (myCount != 0) {
1604 if (myCurrentFontType != myFontType) {
1605 myCurrentFontType = myFontType;
1606 g.setFont(myFontType.getFont());
1608 Color currentColor = null;
1609 for (int i = 0; i < myCount; i++) {
1610 if (!Comparing.equal(color[i], currentColor)) {
1611 currentColor = color[i];
1612 g.setColor(currentColor != null ? currentColor : Color.black);
1615 drawChars(g, data[i], starts[i], ends[i], x[i], y[i]);
1616 color[i] = null;
1617 data[i] = null;
1620 myCount = 0;
1621 myLastData = null;
1625 private void addContent(Graphics g, char[] _data, int _start, int _end, int _x, int _y, Color _color) {
1626 final int count = myCount;
1627 if (count > 0) {
1628 final int lastCount = count - 1;
1629 final Color lastColor = color[lastCount];
1630 if (_data == myLastData && _start == ends[lastCount] && (_color == null || lastColor == null || _color == lastColor)) {
1631 ends[lastCount] = _end;
1632 if (lastColor == null) color[lastCount] = _color;
1633 return;
1637 myLastData = _data;
1638 data[count] = _data;
1639 x[count] = _x;
1640 y[count] = _y;
1641 starts[count] = _start;
1642 ends[count] = _end;
1643 color[count] = _color;
1645 myCount++;
1646 if (count >= CACHED_CHARS_BUFFER_SIZE - 1) {
1647 flushContent(g);
1652 private void flushCachedChars(Graphics g) {
1653 for (CachedFontContent cache : myFontCache) {
1654 cache.flushContent(g);
1656 myLastCache = null;
1659 private void paintCaretCursor(Graphics g) {
1660 myCaretCursor.paint(g);
1663 private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
1664 if (markupModel == null) return;
1665 RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
1666 for (RangeHighlighter lineMarker : lineMarkers) {
1667 paintLineMarkerSeparator(lineMarker, clip, g);
1671 private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
1672 if (!marker.isValid()) {
1673 return;
1675 Color separatorColor = marker.getLineSeparatorColor();
1676 if (separatorColor != null) {
1677 int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
1678 .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
1679 if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
1680 return;
1683 int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
1684 if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
1685 y += getLineHeight();
1688 if (y < clip.y || y > clip.y + clip.height) return;
1690 int endShift = clip.x + clip.width;
1691 g.setColor(separatorColor);
1693 if (mySettings.isRightMarginShown() && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
1694 endShift = Math.min(endShift, mySettings.getRightMargin(myProject) * getSpaceWidth(Font.PLAIN));
1697 UIUtil.drawLine(g, 0, y - 1, endShift, y - 1);
1701 private int drawString(Graphics g,
1702 final char[] text,
1703 int start,
1704 int end,
1705 Point position,
1706 Rectangle clip,
1707 Color effectColor,
1708 EffectType effectType,
1709 int fontType,
1710 Color fontColor) {
1711 if (start >= end) return position.x;
1713 boolean isInClip = getLineHeight() + position.y >= clip.y && position.y <= clip.y + clip.height;
1715 if (!isInClip) return position.x;
1717 int y = getLineHeight() - getDescent() + position.y;
1718 int x = position.x;
1719 return drawTabbedString(g, text, start, end, x, y, effectColor, effectType, fontType, fontColor, clip);
1722 private int drawString(Graphics g,
1723 String text,
1724 Point position,
1725 Rectangle clip,
1726 Color effectColor,
1727 EffectType effectType,
1728 int fontType,
1729 Color fontColor) {
1730 boolean isInClip = getLineHeight() + position.y >= clip.y && position.y <= clip.y + clip.height;
1732 if (!isInClip) return position.x;
1734 int y = getLineHeight() - getDescent() + position.y;
1735 int x = position.x;
1737 return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType, fontColor, clip);
1740 private int drawTabbedString(Graphics g,
1741 char[] text,
1742 int start,
1743 int end,
1744 int x,
1745 int y,
1746 Color effectColor,
1747 EffectType effectType,
1748 int fontType,
1749 Color fontColor,
1750 final Rectangle clip) {
1751 int xStart = x;
1753 for (int i = start; i < end; i++) {
1754 if (text[i] != '\t') continue;
1756 x = drawTablessString(text, start, i, g, x, y, fontType, fontColor, clip);
1758 int x1 = nextTabStop(x);
1759 drawTabPlacer(g, y, x, x1);
1760 x = x1;
1761 start = i + 1;
1764 x = drawTablessString(text, start, end, g, x, y, fontType, fontColor, clip);
1766 if (effectColor != null) {
1767 final Color savedColor = g.getColor();
1769 // myBorderEffect.flushIfCantProlong(g, this, effectType, effectColor);
1770 int xEnd = x;
1771 if (xStart < clip.x && xEnd < clip.x || xStart > clip.x + clip.width && xEnd > clip.x + clip.width) {
1772 return x;
1775 if (xEnd > clip.x + clip.width) {
1776 xEnd = clip.x + clip.width;
1778 if (xStart < clip.x) {
1779 xStart = clip.x;
1782 if (effectType == EffectType.LINE_UNDERSCORE) {
1783 g.setColor(effectColor);
1784 UIUtil.drawLine(g, xStart, y + 1, xEnd, y + 1);
1785 g.setColor(savedColor);
1787 else if (effectType == EffectType.BOLD_LINE_UNDERSCORE) {
1788 g.setColor(effectColor);
1789 UIUtil.drawLine(g, xStart, y, xEnd, y);
1790 UIUtil.drawLine(g, xStart, y + 1, xEnd, y + 1);
1791 g.setColor(savedColor);
1793 else if (effectType == EffectType.STRIKEOUT) {
1794 g.setColor(effectColor);
1795 int y1 = y - getCharHeight() / 2;
1796 UIUtil.drawLine(g, xStart, y1, xEnd, y1);
1797 g.setColor(savedColor);
1799 else if (effectType == EffectType.WAVE_UNDERSCORE) {
1800 g.setColor(effectColor);
1801 drawWave(g, xStart, xEnd, y + 1);
1802 g.setColor(savedColor);
1806 return x;
1809 private int drawTablessString(final char[] text,
1810 int start,
1811 final int end,
1812 final Graphics g,
1813 int x,
1814 final int y,
1815 final int fontType,
1816 final Color fontColor,
1817 final Rectangle clip) {
1818 int endX = x;
1819 if (start < end) {
1820 FontInfo font = fontForChar(text[start], fontType);
1821 for (int j = start; j < end; j++) {
1822 final char c = text[j];
1823 FontInfo newFont = fontForChar(c, fontType);
1824 if (font != newFont || endX > clip.x + clip.width) {
1825 if (!(x < clip.x && endX < clip.x || x > clip.x + clip.width && endX > clip.x + clip.width)) {
1826 drawCharsCached(g, text, start, j, x, y, fontType, fontColor);
1828 start = j;
1829 x = endX;
1830 font = newFont;
1832 if (x < clip.x && endX < clip.x) {
1833 start = j;
1834 x = endX;
1835 font = newFont;
1837 else if (x > clip.x + clip.width) {
1838 return endX;
1840 endX += font.charWidth(c, myEditorComponent);
1843 if (!(x < clip.x && endX < clip.x || x > clip.x + clip.width && endX > clip.x + clip.width)) {
1844 drawCharsCached(g, text, start, end, x, y, fontType, fontColor);
1848 return endX;
1851 private FontInfo fontForChar(final char c, int style) {
1852 return ComplementaryFontsRegistry.getFontAbleToDisplay(c, myScheme.getEditorFontSize(), style, myScheme.getEditorFontName());
1855 public final int charWidth(char c, int fontType) {
1856 return fontForChar(c, fontType).charWidth(c, myEditorComponent);
1859 private void drawTabPlacer(Graphics g, int y, int start, int stop) {
1860 if (mySettings.isWhitespacesShown()) {
1861 stop -= g.getFontMetrics().charWidth(' ') / 2;
1862 Color oldColor = g.getColor();
1863 g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR));
1864 final int charHeight = getCharHeight();
1865 final int halfCharHeight = charHeight / 2;
1866 int mid = y - halfCharHeight;
1867 int top = y - charHeight;
1868 UIUtil.drawLine(g, start, mid, stop, mid);
1869 UIUtil.drawLine(g, stop, y, stop, top);
1870 g.fillPolygon(new int[]{stop - halfCharHeight, stop - halfCharHeight, stop}, new int[]{y, y - charHeight, y - halfCharHeight}, 3);
1871 g.setColor(oldColor);
1875 private void drawCharsCached(Graphics g, char[] data, int start, int end, int x, int y, int fontType, Color color) {
1876 if (mySpacesHaveSameWidth && myLastCache != null && spacesOnly(data, start, end)) {
1877 myLastCache.addContent(g, data, start, end, x, y, null);
1879 else {
1880 FontInfo fnt = fontForChar(data[start], fontType);
1881 CachedFontContent cache = null;
1882 for (CachedFontContent fontCache : myFontCache) {
1883 if (fontCache.myFontType == fnt) {
1884 cache = fontCache;
1885 break;
1888 if (cache == null) {
1889 cache = new CachedFontContent(fnt);
1890 myFontCache.add(cache);
1893 myLastCache = cache;
1894 cache.addContent(g, data, start, end, x, y, color);
1898 private static boolean spacesOnly(char[] chars, int start, int end) {
1899 for (int i = start; i < end; i++) {
1900 if (chars[i] != ' ') return false;
1902 return true;
1905 private void drawChars(Graphics g, char[] data, int start, int end, int x, int y) {
1906 g.drawChars(data, start, end - start, x, y);
1908 if (mySettings.isWhitespacesShown()) {
1909 Color oldColor = g.getColor();
1910 g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR));
1911 final FontMetrics metrics = g.getFontMetrics();
1912 int halfSpaceWidth = metrics.charWidth(' ') / 2;
1913 for (int i = start; i < end; i++) {
1914 if (data[i] == ' ') {
1915 g.fillRect(x + halfSpaceWidth, y, 1, 1);
1917 x += metrics.charWidth(data[i]);
1919 g.setColor(oldColor);
1923 private static final int WAVE_HEIGHT = 2;
1924 private static final int WAVE_SEGMENT_LENGTH = 4;
1926 private static void drawWave(Graphics g, int xStart, int xEnd, int y) {
1927 int startSegment = xStart / WAVE_SEGMENT_LENGTH;
1928 int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
1929 for (int i = startSegment; i < endSegment; i++) {
1930 drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
1933 int x = WAVE_SEGMENT_LENGTH * endSegment;
1934 UIUtil.drawLine(g, x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
1937 private static void drawWaveSegment(Graphics g, int x, int y) {
1938 UIUtil.drawLine(g, x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
1939 UIUtil.drawLine(g, x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
1942 private int getTextSegmentWidth(CharSequence text, int xStart, int fontType, Rectangle clip) {
1943 int x = xStart;
1945 final int textLength = text.length();
1946 for (int i = 0; i < textLength && xStart < clip.x + clip.width; i++) {
1947 if (text.charAt(i) == '\t') {
1948 x = nextTabStop(x);
1950 else {
1951 x += charWidth(text.charAt(i), fontType);
1953 if (x > clip.x + clip.width) {
1954 break;
1957 return x - xStart;
1960 public int getLineHeight() {
1961 if (myLineHeight != -1) return myLineHeight;
1963 assertIsDispatchThread();
1965 FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
1966 myLineHeight = (int)(fontMetrics.getHeight() * (isOneLineMode() ? 1 : myScheme.getLineSpacing()));
1967 if (myLineHeight == 0) {
1968 myLineHeight = fontMetrics.getHeight();
1969 if (myLineHeight == 0) {
1970 myLineHeight = 12;
1974 return myLineHeight;
1977 int getDescent() {
1978 if (myDescent != -1) {
1979 return myDescent;
1981 FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
1982 myDescent = fontMetrics.getDescent();
1983 return myDescent;
1986 FontMetrics getFontMetrics(int fontType) {
1987 if (myPlainFontMetrics == null) {
1988 assertIsDispatchThread();
1989 myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
1990 myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
1991 myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
1992 myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
1995 if (fontType == Font.PLAIN) return myPlainFontMetrics;
1996 if (fontType == Font.BOLD) return myBoldFontMetrics;
1997 if (fontType == Font.ITALIC) return myItalicFontMetrics;
1998 if (fontType == Font.BOLD + Font.ITALIC) return myBoldItalicFontMetrics;
2000 LOG.assertTrue(false, "Unknown font type: " + fontType);
2002 return myPlainFontMetrics;
2005 private int getCharHeight() {
2006 if (myCharHeight == -1) {
2007 assertIsDispatchThread();
2008 FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
2009 myCharHeight = fontMetrics.charWidth('a');
2011 return myCharHeight;
2014 public Dimension getPreferredSize() {
2015 if (ourIsUnitTestMode && getUserData(DO_DOCUMENT_UPDATE_TEST) == null) {
2016 return new Dimension(1, 1);
2019 final Dimension draft = getSizeWithoutCaret();
2020 final int additionalSpace = mySettings.getAdditionalColumnsCount() * getSpaceWidth(Font.PLAIN);
2022 if (!myDocument.isInBulkUpdate()) {
2023 int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
2024 draft.width = Math.max(caretX, draft.width) + additionalSpace;
2026 else {
2027 draft.width += additionalSpace;
2029 return draft;
2032 private Dimension getSizeWithoutCaret() {
2033 Dimension size = mySizeContainer.getContentSize();
2034 if (isOneLineMode()) return new Dimension(size.width, getLineHeight());
2035 if (mySettings.isAdditionalPageAtBottom()) {
2036 int lineHeight = getLineHeight();
2037 return new Dimension(size.width, size.height + Math.max(getScrollingModel().getVisibleArea().height - 2 * lineHeight, lineHeight));
2040 return getContentSize();
2043 public Dimension getContentSize() {
2044 Dimension size = mySizeContainer.getContentSize();
2045 return new Dimension(size.width, size.height + mySettings.getAdditionalLinesCount() * getLineHeight());
2048 public JScrollPane getScrollPane() {
2049 return myScrollPane;
2052 public int logicalPositionToOffset(@NotNull LogicalPosition pos) {
2053 assertIsDispatchThread();
2054 if (myDocument.getLineCount() == 0) return 0;
2056 if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
2057 if (pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
2059 if (pos.line >= myDocument.getLineCount()) {
2060 return myDocument.getTextLength();
2063 int start = myDocument.getLineStartOffset(pos.line);
2064 int end = myDocument.getLineEndOffset(pos.line);
2066 CharSequence text = myDocument.getCharsNoThreadCheck();
2068 if (pos.column == 0) return start;
2069 return EditorUtil.calcOffset(this, text, start, end, pos.column, getTabSize());
2072 public void setLastColumnNumber(int val) {
2073 assertIsDispatchThread();
2074 myLastColumnNumber = val;
2077 public int getLastColumnNumber() {
2078 assertIsDispatchThread();
2079 return myLastColumnNumber;
2082 int getVisibleLineCount() {
2083 int line = getDocument().getLineCount();
2084 line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
2085 return line;
2088 @NotNull
2089 public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos) {
2090 assertIsDispatchThread();
2091 if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
2093 int offset = logicalPositionToOffset(logicalPos);
2095 FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
2096 if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
2097 if (offset < getDocument().getTextLength() - 1) {
2098 offset = outermostCollapsed.getStartOffset();
2099 LogicalPosition foldStart = offsetToLogicalPosition(offset);
2100 return logicalToVisualPosition(foldStart);
2102 else {
2103 offset = outermostCollapsed.getEndOffset() + 3;
2107 int line = logicalPos.line;
2108 int column = logicalPos.column;
2110 line -= myFoldingModel.getFoldedLinesCountBefore(offset);
2112 FoldRegion[] toplevel = myFoldingModel.fetchTopLevel();
2113 for (int idx = myFoldingModel.getLastTopLevelIndexBefore(offset); idx >= 0; idx--) {
2114 FoldRegion region = toplevel[idx];
2115 if (region.isValid()) {
2116 if (region.getDocument().getLineNumber(region.getEndOffset()) == logicalPos.line && region.getEndOffset() <= offset) {
2117 LogicalPosition foldStart = offsetToLogicalPosition(region.getStartOffset());
2118 LogicalPosition foldEnd = offsetToLogicalPosition(region.getEndOffset());
2119 column += foldStart.column + region.getPlaceholderText().length() - foldEnd.column;
2120 offset = region.getStartOffset();
2121 logicalPos = foldStart;
2123 else {
2124 break;
2129 LOG.assertTrue(line >= 0);
2131 return new VisualPosition(line, Math.max(0, column));
2134 @Nullable
2135 private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
2136 FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
2138 if (topLevelCollapsed == null) return null;
2140 int start = 0;
2141 int end = topLevelCollapsed.length - 1;
2142 int i = 0;
2144 while (start <= end) {
2145 i = (start + end) / 2;
2146 FoldRegion region = topLevelCollapsed[i];
2147 LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
2148 VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
2149 if (visFoldEnd.line < visual.line) {
2150 start = i + 1;
2152 else {
2153 if (visFoldEnd.line > visual.line) {
2154 end = i - 1;
2156 else {
2157 if (visFoldEnd.column < visual.column) {
2158 start = i + 1;
2160 else {
2161 if (visFoldEnd.column > visual.column) {
2162 end = i - 1;
2164 else {
2165 i--;
2166 break;
2173 while (i >= 0 && i < topLevelCollapsed.length) {
2174 if (topLevelCollapsed[i].isValid()) break;
2175 i--;
2178 if (i >= 0 && i < topLevelCollapsed.length) {
2179 FoldRegion region = topLevelCollapsed[i];
2180 LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
2181 VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
2182 if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
2183 i--;
2184 if (i >= 0) {
2185 return topLevelCollapsed[i];
2187 else {
2188 return null;
2191 return region;
2194 return null;
2197 @NotNull
2198 public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos) {
2199 assertIsDispatchThread();
2200 if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
2202 int line = visiblePos.line;
2203 int column = visiblePos.column;
2205 FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
2207 if (lastCollapsedBefore != null) {
2208 LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset());
2209 VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
2211 line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
2212 if (visFoldEnd.line == visiblePos.line) {
2213 if (visiblePos.column >= visFoldEnd.column) {
2214 column = logFoldEnd.column + (visiblePos.column - visFoldEnd.column);
2216 else {
2217 return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
2222 if (column < 0) column = 0;
2224 return new LogicalPosition(line, column);
2227 private int calcLogicalLineNumber(int offset) {
2228 int textLength = myDocument.getTextLength();
2229 if (textLength == 0) return 0;
2231 if (offset > textLength || offset < 0) {
2232 throw new IndexOutOfBoundsException("Wrong offset: " + offset + " textLength: " + textLength);
2235 int lineIndex = myDocument.getLineNumber(offset);
2237 LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
2239 return lineIndex;
2242 private int calcColumnNumber(int offset, int lineIndex) {
2243 if (myDocument.getTextLength() == 0) return 0;
2245 CharSequence text = myDocument.getCharsSequence();
2246 int start = myDocument.getLineStartOffset(lineIndex);
2247 if (start == offset) return 0;
2248 return EditorUtil.calcColumnNumber(this, text, start, offset, getTabSize());
2251 public int getTabSize() {
2252 Project project = myProject;
2253 return project != null && project.isDisposed() ? 0 : mySettings.getTabSize(project);
2256 private void moveCaretToScreenPos(int x, int y) {
2257 if (x < 0) {
2258 x = 0;
2261 LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
2263 int columnNumber = pos.column;
2264 int lineNumber = pos.line;
2266 if (lineNumber >= myDocument.getLineCount()) {
2267 lineNumber = myDocument.getLineCount() - 1;
2269 if (!mySettings.isVirtualSpace()) {
2270 if (lineNumber >= 0) {
2271 int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
2272 int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
2273 if (columnNumber > lineEndColumnNumber) {
2274 columnNumber = lineEndColumnNumber;
2278 if (lineNumber < 0) {
2279 lineNumber = 0;
2280 columnNumber = 0;
2282 if (!mySettings.isCaretInsideTabs()) {
2283 int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
2284 CharSequence text = myDocument.getCharsSequence();
2285 if (offset >= 0 && offset < myDocument.getTextLength()) {
2286 if (text.charAt(offset) == '\t') {
2287 columnNumber = calcColumnNumber(offset, lineNumber);
2291 LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
2292 getCaretModel().moveToLogicalPosition(pos1);
2295 private void runMousePressedCommand(final MouseEvent e) {
2296 myMousePressedEvent = e;
2297 EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
2299 for (EditorMouseListener mouseListener : myMouseListeners) {
2300 mouseListener.mousePressed(event);
2303 // On some systems (for example on Linux) popup trigger is MOUSE_PRESSED event.
2304 // But this trigger is always consumed by popup handler. In that case we have to
2305 // also move caret.
2306 if (event.isConsumed() && !(event.getMouseEvent().isPopupTrigger() || event.getArea() == EditorMouseEventArea.EDITING_AREA)) {
2307 return;
2310 if (myCommandProcessor != null) {
2311 Runnable runnable = new Runnable() {
2312 public void run() {
2313 processMousePressed(e);
2316 myCommandProcessor.executeCommand(myProject, runnable, "", getDocument(), UndoConfirmationPolicy.DEFAULT, getDocument());
2318 else {
2319 processMousePressed(e);
2323 private void runMouseClickedCommand(final MouseEvent e) {
2324 EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
2325 for (EditorMouseListener listener : myMouseListeners) {
2326 listener.mouseClicked(event);
2327 if (event.isConsumed()) {
2328 e.consume();
2329 return;
2334 private void runMouseReleasedCommand(final MouseEvent e) {
2335 myScrollingTimer.stop();
2336 EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
2337 for (EditorMouseListener listener : myMouseListeners) {
2338 listener.mouseReleased(event);
2339 if (event.isConsumed()) {
2340 e.consume();
2341 return;
2345 if (myCommandProcessor != null) {
2346 Runnable runnable = new Runnable() {
2347 public void run() {
2348 processMouseReleased(e);
2351 myCommandProcessor.executeCommand(myProject, runnable, "", getDocument(), UndoConfirmationPolicy.DEFAULT, getDocument());
2353 else {
2354 processMouseReleased(e);
2358 private void runMouseEnteredCommand(MouseEvent e) {
2359 EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
2360 for (EditorMouseListener listener : myMouseListeners) {
2361 listener.mouseEntered(event);
2362 if (event.isConsumed()) {
2363 e.consume();
2364 return;
2369 private void runMouseExitedCommand(MouseEvent e) {
2370 EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
2371 for (EditorMouseListener listener : myMouseListeners) {
2372 listener.mouseExited(event);
2373 if (event.isConsumed()) {
2374 e.consume();
2375 return;
2380 private void processMousePressed(MouseEvent e) {
2381 myInitialMouseEvent = e;
2383 if (myMouseSelectionState != MOUSE_SELECTION_STATE_NONE && System.currentTimeMillis() - myMouseSelectionChangeTimestamp > 1000) {
2384 setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
2387 int x = e.getX();
2388 int y = e.getY();
2390 if (x < 0) x = 0;
2391 if (y < 0) y = 0;
2393 final EditorMouseEventArea eventArea = getMouseEventArea(e);
2394 if (eventArea == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
2395 final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
2396 if (range != null) {
2397 final boolean expansion = !range.isExpanded();
2399 int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
2400 Runnable processor = new Runnable() {
2401 public void run() {
2402 myFoldingModel.flushCaretShift();
2403 range.setExpanded(expansion);
2406 getFoldingModel().runBatchFoldingOperation(processor);
2407 y = myGutterComponent.getHeadCenterY(range);
2408 getScrollingModel().scrollVertically(y - scrollShift);
2409 return;
2413 if (e.getSource() == myGutterComponent) {
2414 if (eventArea == EditorMouseEventArea.LINE_MARKERS_AREA || eventArea == EditorMouseEventArea.ANNOTATIONS_AREA || eventArea == EditorMouseEventArea.LINE_NUMBERS_AREA) {
2415 myGutterComponent.mousePressed(e);
2416 if (e.isConsumed()) return;
2418 x = 0;
2421 int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
2422 moveCaretToScreenPos(x, y);
2424 if (e.isPopupTrigger()) return;
2426 requestFocus();
2428 int caretOffset = getCaretModel().getOffset();
2430 myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
2431 myMousePressedInsideSelection = mySelectionModel.hasSelection() && caretOffset >= mySelectionModel.getSelectionStart() &&
2432 caretOffset <= mySelectionModel.getSelectionEnd();
2434 if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
2435 int[] starts = mySelectionModel.getBlockSelectionStarts();
2436 int[] ends = mySelectionModel.getBlockSelectionEnds();
2437 for (int i = 0; i < starts.length; i++) {
2438 if (caretOffset >= starts[i] && caretOffset < ends[i]) {
2439 myMousePressedInsideSelection = true;
2440 break;
2445 if (getMouseEventArea(e) == EditorMouseEventArea.LINE_NUMBERS_AREA && e.getClickCount() == 1) {
2446 mySelectionModel.selectLineAtCaret();
2447 setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
2448 mySavedSelectionStart = mySelectionModel.getSelectionStart();
2449 mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
2450 return;
2453 if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
2454 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
2455 if (caretOffset < mySavedSelectionStart) {
2456 mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
2458 else {
2459 mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
2462 else {
2463 mySelectionModel.setSelection(oldSelectionStart, caretOffset);
2466 else {
2467 if (!myMousePressedInsideSelection && getSelectionModel().hasSelection()) {
2468 setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
2469 mySelectionModel.setSelection(caretOffset, caretOffset);
2471 else {
2472 if (!e.isPopupTrigger()) {
2473 switch (e.getClickCount()) {
2474 case 2:
2475 mySelectionModel.selectWordAtCaret(mySettings.isMouseClickSelectionHonorsCamelWords());
2476 setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
2477 mySavedSelectionStart = mySelectionModel.getSelectionStart();
2478 mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
2479 getCaretModel().moveToOffset(mySavedSelectionEnd);
2480 break;
2482 case 3:
2483 mySelectionModel.selectLineAtCaret();
2484 setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
2485 mySavedSelectionStart = mySelectionModel.getSelectionStart();
2486 mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
2487 break;
2494 private boolean checkIgnore(MouseEvent e, boolean isFinalCheck) {
2495 if (!myIgnoreMouseEventsConsequitiveToInitial) {
2496 myInitialMouseEvent = null;
2497 return false;
2500 if (e.getComponent() != myInitialMouseEvent.getComponent() || !e.getPoint().equals(myInitialMouseEvent.getPoint())) {
2501 myIgnoreMouseEventsConsequitiveToInitial = false;
2502 myInitialMouseEvent = null;
2503 return false;
2506 if (isFinalCheck) {
2507 myIgnoreMouseEventsConsequitiveToInitial = false;
2508 myInitialMouseEvent = null;
2511 e.consume();
2513 return true;
2516 private void processMouseReleased(MouseEvent e) {
2517 if (checkIgnore(e, true)) return;
2519 if (e.getSource() == myGutterComponent) {
2520 myGutterComponent.mouseReleased(e);
2523 if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
2524 return;
2527 // if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
2528 final FoldRegion region = ((FoldingModelEx)getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
2529 if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
2530 getFoldingModel().runBatchFoldingOperation(new Runnable() {
2531 public void run() {
2532 myFoldingModel.flushCaretShift();
2533 region.setExpanded(true);
2538 if (myMousePressedEvent != null && myMousePressedEvent.getClickCount() == 1 && myMousePressedInsideSelection) {
2539 getSelectionModel().removeSelection();
2543 public DataContext getDataContext() {
2544 return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
2547 private DataContext getProjectAwareDataContext(final DataContext original) {
2548 if (PlatformDataKeys.PROJECT.getData(original) == myProject) return original;
2550 return new DataContext() {
2551 public Object getData(String dataId) {
2552 if (DataConstants.PROJECT.equals(dataId)) {
2553 return myProject;
2555 return original.getData(dataId);
2561 public EditorMouseEventArea getMouseEventArea(@NotNull MouseEvent e) {
2562 if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
2564 int x = myGutterComponent.convertX(e.getX());
2566 if (x >= myGutterComponent.getLineNumberAreaOffset() &&
2567 x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) {
2568 return EditorMouseEventArea.LINE_NUMBERS_AREA;
2571 if (x >= myGutterComponent.getAnnotationsAreaOffset() &&
2572 x <= myGutterComponent.getAnnotationsAreaOffset() + myGutterComponent.getAnnotationsAreaWidth()) {
2573 return EditorMouseEventArea.ANNOTATIONS_AREA;
2576 if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
2577 x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth()) {
2578 return EditorMouseEventArea.LINE_MARKERS_AREA;
2581 if (x >= myGutterComponent.getFoldingAreaOffset() &&
2582 x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth()) {
2583 return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
2586 return null;
2589 private void requestFocus() {
2590 myEditorComponent.requestFocus();
2593 private void validateMousePointer(MouseEvent e) {
2594 if (e.getSource() == myGutterComponent) {
2595 FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
2596 myGutterComponent.setActiveFoldRegion(foldingAtCursor);
2597 if (foldingAtCursor != null) {
2598 myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
2600 else {
2601 myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
2604 else {
2605 myGutterComponent.setActiveFoldRegion(null);
2606 if (getSelectionModel().hasSelection() && (e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK)) == 0) {
2607 int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
2608 if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
2609 myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
2610 return;
2613 myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
2617 private void runMouseDraggedCommand(final MouseEvent e) {
2618 if (myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
2619 return;
2621 myCommandProcessor.executeCommand(myProject, new Runnable() {
2622 public void run() {
2623 processMouseDragged(e);
2625 }, "", MOUSE_DRAGGED_GROUP, UndoConfirmationPolicy.DEFAULT, getDocument());
2628 private void processMouseDragged(MouseEvent e) {
2629 if (SwingUtilities.isRightMouseButton(e)) {
2630 return;
2632 Rectangle rect = getScrollingModel().getVisibleArea();
2634 int x = e.getX();
2636 if (e.getSource() == myGutterComponent) {
2637 x = 0;
2640 int dx = 0;
2641 if (x < rect.x) {
2642 dx = x - rect.x;
2644 else {
2645 if (x > rect.x + rect.width) {
2646 dx = x - rect.x - rect.width;
2650 int dy = 0;
2651 int y = e.getY();
2652 if (y < rect.y) {
2653 dy = y - rect.y;
2655 else {
2656 if (y > rect.y + rect.height) {
2657 dy = y - rect.y - rect.height;
2660 if (dx == 0 && dy == 0) {
2661 myScrollingTimer.stop();
2663 SelectionModel selectionModel = getSelectionModel();
2664 int oldSelectionStart = selectionModel.getLeadSelectionOffset();
2665 int oldCaretOffset = getCaretModel().getOffset();
2666 LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
2667 moveCaretToScreenPos(x, y);
2668 getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
2670 int newCaretOffset = getCaretModel().getOffset();
2671 int caretShift = newCaretOffset - mySavedSelectionStart;
2673 if (myMousePressedEvent != null && getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA &&
2674 getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.LINE_NUMBERS_AREA) {
2675 selectionModel.setSelection(oldSelectionStart, newCaretOffset);
2677 else {
2678 if (isColumnMode()) {
2679 final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
2680 selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
2682 else {
2683 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
2684 if (caretShift < 0) {
2685 int newSelection = newCaretOffset;
2686 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
2687 newSelection = mySelectionModel.getWordAtCaretStart();
2689 else {
2690 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
2691 newSelection =
2692 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0)));
2695 if (newSelection < 0) newSelection = newCaretOffset;
2696 selectionModel.setSelection(mySavedSelectionEnd, newSelection);
2697 getCaretModel().moveToOffset(newSelection);
2699 else {
2700 int newSelection = newCaretOffset;
2701 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
2702 newSelection = mySelectionModel.getWordAtCaretEnd();
2704 else {
2705 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
2706 newSelection =
2707 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)));
2710 if (newSelection < 0) newSelection = newCaretOffset;
2711 selectionModel.setSelection(mySavedSelectionStart, newSelection);
2712 getCaretModel().moveToOffset(newSelection);
2714 return;
2717 if (!myMousePressedInsideSelection) {
2718 selectionModel.setSelection(oldSelectionStart, newCaretOffset);
2720 else {
2721 if (caretShift != 0) {
2722 if (myMousePressedEvent != null) {
2723 if (mySettings.isDndEnabled()) {
2724 boolean isCopy = UIUtil.isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
2725 mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
2726 getContentComponent().getTransferHandler()
2727 .exportAsDrag(getContentComponent(), e, isCopy ? TransferHandler.COPY : TransferHandler.MOVE);
2729 else {
2730 selectionModel.removeSelection();
2732 myMousePressedEvent = null;
2739 else {
2740 myScrollingTimer.start(dx, dy);
2744 private static class RepaintCursorCommand implements Runnable {
2745 private long mySleepTime = 500;
2746 private boolean myIsBlinkCaret = true;
2747 private EditorImpl myEditor = null;
2748 private final MyRepaintRunnable myRepaintRunnable;
2749 private ScheduledFuture<?> mySchedulerHandle;
2751 private RepaintCursorCommand() {
2752 myRepaintRunnable = new MyRepaintRunnable();
2755 private class MyRepaintRunnable implements Runnable {
2756 public void run() {
2757 if (myEditor != null) {
2758 myEditor.myCaretCursor.repaint();
2763 public void start() {
2764 if (mySchedulerHandle != null) {
2765 mySchedulerHandle.cancel(false);
2767 mySchedulerHandle = JobScheduler.getScheduler().scheduleAtFixedRate(this, mySleepTime, mySleepTime, TimeUnit.MILLISECONDS);
2770 private void setBlinkPeriod(int blinkPeriod) {
2771 mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
2772 start();
2775 private void setBlinkCaret(boolean value) {
2776 myIsBlinkCaret = value;
2779 public void run() {
2780 if (myEditor != null) {
2781 CaretCursor activeCursor = myEditor.myCaretCursor;
2783 long time = System.currentTimeMillis();
2784 time -= activeCursor.myStartTime;
2786 if (time > mySleepTime) {
2787 boolean toRepaint = true;
2788 if (myIsBlinkCaret) {
2789 activeCursor.isVisible = !activeCursor.isVisible;
2791 else {
2792 toRepaint = !activeCursor.isVisible;
2793 activeCursor.isVisible = true;
2796 if (toRepaint) {
2797 SwingUtilities.invokeLater(myRepaintRunnable);
2804 void updateCaretCursor() {
2805 if (!ourIsUnitTestMode && !IJSwingUtilities.hasFocus(getContentComponent())) {
2806 stopOptimizedScrolling();
2809 if (myCursorUpdater == null) {
2810 myCursorUpdater = new Runnable() {
2811 public void run() {
2812 if (myCursorUpdater == null) return;
2813 myCursorUpdater = null;
2814 VisualPosition caretPosition = getCaretModel().getVisualPosition();
2815 Point pos1 = visualPositionToXY(caretPosition);
2816 Point pos2 = visualPositionToXY(new VisualPosition(caretPosition.line, caretPosition.column + 1));
2817 myCaretCursor.setPosition(pos1, pos2.x - pos1.x);
2823 public boolean setCaretVisible(boolean b) {
2824 boolean old = myCaretCursor.isActive();
2825 if (b) {
2826 myCaretCursor.activate();
2828 else {
2829 myCaretCursor.passivate();
2831 return old;
2834 public void addFocusListener(FocusChangeListener listener) {
2835 myFocusListeners.add(listener);
2838 public Project getProject() {
2839 return myProject;
2842 public boolean isOneLineMode() {
2843 return myIsOneLineMode;
2846 public boolean isEmbeddedIntoDialogWrapper() {
2847 return myEmbeddedIntoDialogWrapper;
2850 public void setEmbeddedIntoDialogWrapper(boolean b) {
2851 assertIsDispatchThread();
2853 myEmbeddedIntoDialogWrapper = b;
2854 myScrollPane.setFocusable(!b);
2855 myEditorComponent.setFocusCycleRoot(!b);
2856 myEditorComponent.setFocusable(b);
2859 public void setOneLineMode(boolean isOneLineMode) {
2860 myIsOneLineMode = isOneLineMode;
2861 getScrollPane().setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
2862 reinitSettings();
2865 public void stopOptimizedScrolling() {
2866 myEditorComponent.setOpaque(false);
2869 private void startOptimizedScrolling() {
2870 myEditorComponent.setOpaque(true);
2873 private class CaretCursor {
2874 private Point myLocation;
2875 private int myWidth;
2877 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
2878 private boolean isVisible = false;
2879 private long myStartTime = 0;
2881 private CaretCursor() {
2882 myLocation = new Point(0, 0);
2885 private void activate() {
2886 final boolean blink = mySettings.isBlinkCaret();
2887 final int blinkPeriod = mySettings.getCaretBlinkPeriod();
2888 synchronized (ourCaretBlinkingCommand) {
2889 ourCaretBlinkingCommand.myEditor = EditorImpl.this;
2890 ourCaretBlinkingCommand.setBlinkCaret(blink);
2891 ourCaretBlinkingCommand.setBlinkPeriod(blinkPeriod);
2892 isVisible = true;
2896 public boolean isActive() {
2897 synchronized (ourCaretBlinkingCommand) {
2898 return isVisible;
2902 private void passivate() {
2903 synchronized (ourCaretBlinkingCommand) {
2904 isVisible = false;
2908 private void setPosition(Point location, int width) {
2909 myStartTime = System.currentTimeMillis();
2910 myLocation = location;
2911 isVisible = true;
2912 myWidth = Math.max(width, 2);
2913 repaint();
2916 private void repaint() {
2917 myEditorComponent.repaintEditorComponent(myLocation.x, myLocation.y, myWidth, getLineHeight());
2920 private void paint(Graphics g) {
2921 if (!isVisible || !IJSwingUtilities.hasFocus(getContentComponent()) || isRendererMode()) return;
2923 int x = myLocation.x;
2924 int lineHeight = getLineHeight();
2925 int y = myLocation.y;
2927 Rectangle viewRect = getScrollingModel().getVisibleArea();
2928 if (x - viewRect.x < 0) {
2929 return;
2933 g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
2935 if (myIsInsertMode != mySettings.isBlockCursor()) {
2936 for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
2937 UIUtil.drawLine(g, x + i, y, x + i, y + lineHeight - 1);
2940 else {
2941 Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
2942 if (background == null) background = getBackroundColor();
2943 g.setXORMode(background);
2945 g.fillRect(x, y, myWidth, lineHeight - 1);
2947 g.setPaintMode();
2952 private class ScrollingTimer {
2953 Timer myTimer;
2954 private static final int TIMER_PERIOD = 100;
2955 private static final int CYCLE_SIZE = 20;
2956 private int myXCycles;
2957 private int myYCycles;
2958 private int myDx;
2959 private int myDy;
2960 private int xPassedCycles = 0;
2961 private int yPassedCycles = 0;
2963 private void start(int dx, int dy) {
2964 myDx = 0;
2965 myDy = 0;
2966 if (dx > 0) {
2967 myXCycles = CYCLE_SIZE / dx + 1;
2968 myDx = 1 + dx / CYCLE_SIZE;
2970 else {
2971 if (dx < 0) {
2972 myXCycles = -CYCLE_SIZE / dx + 1;
2973 myDx = -1 + dx / CYCLE_SIZE;
2977 if (dy > 0) {
2978 myYCycles = CYCLE_SIZE / dy + 1;
2979 myDy = 1 + dy / CYCLE_SIZE;
2981 else {
2982 if (dy < 0) {
2983 myYCycles = -CYCLE_SIZE / dy + 1;
2984 myDy = -1 + dy / CYCLE_SIZE;
2988 if (myTimer != null) {
2989 return;
2991 myTimer = new Timer(TIMER_PERIOD, new ActionListener() {
2992 public void actionPerformed(ActionEvent e) {
2993 myCommandProcessor.executeCommand(myProject, new Runnable() {
2994 public void run() {
2995 int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
2996 LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
2997 int columnNumber = caretPosition.column;
2998 xPassedCycles++;
2999 if (xPassedCycles >= myXCycles) {
3000 xPassedCycles = 0;
3001 columnNumber += myDx;
3004 int lineNumber = caretPosition.line;
3005 yPassedCycles++;
3006 if (yPassedCycles >= myYCycles) {
3007 yPassedCycles = 0;
3008 lineNumber += myDy;
3011 LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
3012 getCaretModel().moveToLogicalPosition(pos);
3013 getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
3015 int newCaretOffset = getCaretModel().getOffset();
3016 int caretShift = newCaretOffset - mySavedSelectionStart;
3018 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
3019 if (caretShift < 0) {
3020 int newSelection = newCaretOffset;
3021 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
3022 newSelection = mySelectionModel.getWordAtCaretStart();
3024 else {
3025 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
3026 newSelection =
3027 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0)));
3030 if (newSelection < 0) newSelection = newCaretOffset;
3031 mySelectionModel.setSelection(validateOffset(mySavedSelectionEnd), newSelection);
3032 getCaretModel().moveToOffset(newSelection);
3034 else {
3035 int newSelection = newCaretOffset;
3036 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
3037 newSelection = mySelectionModel.getWordAtCaretEnd();
3039 else {
3040 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
3041 newSelection = logicalPositionToOffset(
3042 visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)));
3045 if (newSelection < 0) newSelection = newCaretOffset;
3046 mySelectionModel.setSelection(validateOffset(mySavedSelectionStart), newSelection);
3047 getCaretModel().moveToOffset(newSelection);
3049 return;
3052 if (mySelectionModel.hasBlockSelection()) {
3053 mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
3055 else {
3056 mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
3059 }, EditorBundle.message("move.cursor.command.name"), getDocument(), UndoConfirmationPolicy.DEFAULT, getDocument());
3062 myTimer.start();
3065 private void stop() {
3066 if (myTimer != null) {
3067 myTimer.stop();
3068 myTimer = null;
3072 private int validateOffset(int offset) {
3073 if (offset < 0) return 0;
3074 if (offset > myDocument.getTextLength()) return myDocument.getTextLength();
3075 return offset;
3079 class MyScrollBar extends JScrollBar {
3080 @NonNls private static final String DECR_BUTTON_FIELD = "decrButton";
3081 @NonNls private static final String INCR_BUTTON_FIELD = "incrButton";
3082 @NonNls private static final String APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS = "apple.laf.AquaScrollBarUI";
3084 private MyScrollBar(int orientation) {
3085 super(orientation);
3086 setFocusable(false);
3087 putClientProperty("JScrollBar.fastWheelScrolling", Boolean.TRUE); // fast scrolling for JDK 6
3091 * This is helper method. It returns height of the top (descrease) scrollbar
3092 * button. Please note, that it's possible to return real height only if scrollbar
3093 * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
3094 * value.
3096 int getDecScrollButtonHeight() {
3097 ScrollBarUI barUI = getUI();
3098 Insets insets = getInsets();
3099 if (barUI instanceof BasicScrollBarUI) {
3100 try {
3101 Field decrButtonField = BasicScrollBarUI.class.getDeclaredField(DECR_BUTTON_FIELD);
3102 decrButtonField.setAccessible(true);
3103 JButton decrButtonValue = (JButton)decrButtonField.get(barUI);
3104 LOG.assertTrue(decrButtonValue != null);
3105 return insets.top + decrButtonValue.getHeight();
3107 catch (Exception exc) {
3108 throw new IllegalStateException(exc.getMessage());
3111 else {
3112 return insets.top + 15;
3117 * This is helper method. It returns height of the bottom (increase) scrollbar
3118 * button. Please note, that it's possible to return real height only if scrollbar
3119 * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
3120 * value.
3122 int getIncScrollButtonHeight() {
3123 ScrollBarUI barUI = getUI();
3124 Insets insets = getInsets();
3125 if (barUI instanceof BasicScrollBarUI) {
3126 try {
3127 Field incrButtonField = BasicScrollBarUI.class.getDeclaredField(INCR_BUTTON_FIELD);
3128 incrButtonField.setAccessible(true);
3129 JButton incrButtonValue = (JButton)incrButtonField.get(barUI);
3130 LOG.assertTrue(incrButtonValue != null);
3131 return insets.bottom + incrButtonValue.getHeight();
3133 catch (Exception exc) {
3134 throw new IllegalStateException(exc.getMessage());
3137 else if (APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS.equals(barUI.getClass().getName())) {
3138 return insets.bottom + 30;
3140 else {
3141 return insets.bottom + 15;
3145 public int getUnitIncrement(int direction) {
3146 JViewport vp = myScrollPane.getViewport();
3147 Rectangle vr = vp.getViewRect();
3148 return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
3151 public int getBlockIncrement(int direction) {
3152 JViewport vp = myScrollPane.getViewport();
3153 Rectangle vr = vp.getViewRect();
3154 return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
3158 private MyEditable getViewer() {
3159 if (myEditable == null) {
3160 myEditable = new MyEditable();
3162 return myEditable;
3165 public CopyProvider getCopyProvider() {
3166 return getViewer();
3169 public CutProvider getCutProvider() {
3170 return getViewer();
3173 public PasteProvider getPasteProvider() {
3175 return getViewer();
3178 public DeleteProvider getDeleteProvider() {
3179 return getViewer();
3182 private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
3183 public void performCopy(DataContext dataContext) {
3184 executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
3187 public boolean isCopyEnabled(DataContext dataContext) {
3188 return true;
3191 public void performCut(DataContext dataContext) {
3192 executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
3195 public boolean isCutEnabled(DataContext dataContext) {
3196 return !isViewer() && getDocument().isWritable();
3199 public void performPaste(DataContext dataContext) {
3200 executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
3203 public boolean isPastePossible(DataContext dataContext) {
3204 // Copy of isPasteEnabled. See interface method javadoc.
3205 return !isViewer() && getDocument().isWritable();
3208 public boolean isPasteEnabled(DataContext dataContext) {
3209 return !isViewer() && getDocument().isWritable();
3212 public void deleteElement(DataContext dataContext) {
3213 executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
3216 public boolean canDeleteElement(DataContext dataContext) {
3217 return !isViewer() && getDocument().isWritable();
3220 private void executeAction(String actionId, DataContext dataContext) {
3221 EditorAction action = (EditorAction)ActionManager.getInstance().getAction(actionId);
3222 if (action != null) {
3223 action.actionPerformed(EditorImpl.this, dataContext);
3228 public void setColorsScheme(@NotNull EditorColorsScheme scheme) {
3229 assertIsDispatchThread();
3230 myScheme = scheme;
3231 reinitSettings();
3234 @NotNull
3235 public EditorColorsScheme getColorsScheme() {
3236 assertIsDispatchThread();
3237 return myScheme;
3240 void assertIsDispatchThread() {
3241 ApplicationManagerEx.getApplicationEx().assertIsDispatchThread(myEditorComponent);
3244 public void setVerticalScrollbarOrientation(int type) {
3245 assertIsDispatchThread();
3246 int currentHorOffset = myScrollingModel.getHorizontalScrollOffset();
3247 myScrollbarOrientation = type;
3248 if (type == VERTICAL_SCROLLBAR_LEFT) {
3249 myScrollPane.setLayout(new LeftHandScrollbarLayout());
3251 else {
3252 myScrollPane.setLayout(new ScrollPaneLayout());
3254 myScrollingModel.scrollHorizontally(currentHorOffset);
3257 public void setVerticalScrollbarVisible(boolean b) {
3258 if (b) {
3259 myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
3261 else {
3262 myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
3266 public void setHorizontalScrollbarVisible(boolean b) {
3267 if (b) {
3268 myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
3270 else {
3271 myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
3275 int getVerticalScrollbarOrientation() {
3276 return myScrollbarOrientation;
3279 MyScrollBar getVerticalScrollBar() {
3280 return myVerticalScrollBar;
3283 JPanel getPanel() {
3284 return myPanel;
3287 private int getMouseSelectionState() {
3288 return myMouseSelectionState;
3291 private void setMouseSelectionState(int mouseSelectionState) {
3292 myMouseSelectionState = mouseSelectionState;
3293 myMouseSelectionChangeTimestamp = System.currentTimeMillis();
3297 void replaceInputMethodText(InputMethodEvent e) {
3298 getInputMethodRequests();
3299 myInputMethodRequestsHandler.replaceInputMethodText(e);
3302 void inputMethodCaretPositionChanged(InputMethodEvent e) {
3303 getInputMethodRequests();
3304 myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
3307 InputMethodRequests getInputMethodRequests() {
3308 if (myInputMethodRequestsHandler == null) {
3309 myInputMethodRequestsHandler = new MyInputMethodHandler();
3310 myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
3312 return myInputMethodRequestsSwingWrapper;
3315 public boolean processKeyTyped(KeyEvent e) {
3316 if (e.getID() != KeyEvent.KEY_TYPED) return false;
3317 char c = e.getKeyChar();
3318 if (UIUtil.isReallyTypedEvent(e)) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
3319 processKeyTyped(c);
3320 return true;
3322 else {
3323 return false;
3327 void beforeModalityStateChanged() {
3328 myScrollingModel.beforeModalityStateChanged();
3331 private static class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
3332 private final InputMethodRequests myDelegate;
3334 private MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
3335 myDelegate = delegate;
3338 public Rectangle getTextLocation(final TextHitInfo offset) {
3339 if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getTextLocation(offset);
3341 final Rectangle[] r = new Rectangle[1];
3342 try {
3343 GuiUtils.invokeAndWait(new Runnable() {
3344 public void run() {
3345 r[0] = myDelegate.getTextLocation(offset);
3349 catch (InterruptedException e) {
3350 LOG.error(e);
3352 catch (InvocationTargetException e) {
3353 LOG.error(e);
3355 return r[0];
3358 public TextHitInfo getLocationOffset(final int x, final int y) {
3359 if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
3361 final TextHitInfo[] r = new TextHitInfo[1];
3362 try {
3363 GuiUtils.invokeAndWait(new Runnable() {
3364 public void run() {
3365 r[0] = myDelegate.getLocationOffset(x, y);
3369 catch (InterruptedException e) {
3370 LOG.error(e);
3372 catch (InvocationTargetException e) {
3373 LOG.error(e);
3375 return r[0];
3378 public int getInsertPositionOffset() {
3379 if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getInsertPositionOffset();
3381 final int[] r = new int[1];
3382 try {
3383 GuiUtils.invokeAndWait(new Runnable() {
3384 public void run() {
3385 r[0] = myDelegate.getInsertPositionOffset();
3389 catch (InterruptedException e) {
3390 LOG.error(e);
3392 catch (InvocationTargetException e) {
3393 LOG.error(e);
3395 return r[0];
3398 public AttributedCharacterIterator getCommittedText(final int beginIndex,
3399 final int endIndex,
3400 final AttributedCharacterIterator.Attribute[] attributes) {
3401 if (ApplicationManager.getApplication().isDispatchThread()) {
3402 return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
3404 final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
3405 try {
3406 GuiUtils.invokeAndWait(new Runnable() {
3407 public void run() {
3408 r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
3412 catch (InterruptedException e) {
3413 LOG.error(e);
3415 catch (InvocationTargetException e) {
3416 LOG.error(e);
3418 return r[0];
3421 public int getCommittedTextLength() {
3422 if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getCommittedTextLength();
3423 final int[] r = new int[1];
3424 try {
3425 GuiUtils.invokeAndWait(new Runnable() {
3426 public void run() {
3427 r[0] = myDelegate.getCommittedTextLength();
3431 catch (InterruptedException e) {
3432 LOG.error(e);
3434 catch (InvocationTargetException e) {
3435 LOG.error(e);
3437 return r[0];
3440 public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
3441 return null;
3444 public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
3445 if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getSelectedText(attributes);
3447 final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
3448 try {
3449 GuiUtils.invokeAndWait(new Runnable() {
3450 public void run() {
3451 r[0] = myDelegate.getSelectedText(attributes);
3455 catch (InterruptedException e) {
3456 LOG.error(e);
3458 catch (InvocationTargetException e) {
3459 LOG.error(e);
3461 return r[0];
3465 private class MyInputMethodHandler implements InputMethodRequests {
3466 private String composedText;
3467 private int composedTextStart;
3468 private int composedTextEnd;
3470 public Rectangle getTextLocation(TextHitInfo offset) {
3471 Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
3472 Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
3473 Point p = getContentComponent().getLocationOnScreen();
3474 r.translate(p.x, p.y);
3476 return r;
3479 public TextHitInfo getLocationOffset(int x, int y) {
3480 if (composedText != null) {
3481 Point p = getContentComponent().getLocationOnScreen();
3482 p.x = x - p.x;
3483 p.y = y - p.y;
3484 int pos = logicalPositionToOffset(xyToLogicalPosition(p));
3485 if (pos >= composedTextStart && pos <= composedTextEnd) {
3486 return TextHitInfo.leading(pos - composedTextStart);
3489 return null;
3492 public int getInsertPositionOffset() {
3493 int composedStartIndex = 0;
3494 int composedEndIndex = 0;
3495 if (composedText != null) {
3496 composedStartIndex = composedTextStart;
3497 composedEndIndex = composedTextEnd;
3500 int caretIndex = getCaretModel().getOffset();
3502 if (caretIndex < composedStartIndex) {
3503 return caretIndex;
3505 else {
3506 if (caretIndex < composedEndIndex) {
3507 return composedStartIndex;
3509 else {
3510 return caretIndex - (composedEndIndex - composedStartIndex);
3515 private String getText(int startIdx, int endIdx) {
3516 CharSequence chars = getDocument().getCharsSequence();
3517 return chars.subSequence(startIdx, endIdx).toString();
3520 public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex, AttributedCharacterIterator.Attribute[] attributes) {
3521 int composedStartIndex = 0;
3522 int composedEndIndex = 0;
3523 if (composedText != null) {
3524 composedStartIndex = composedTextStart;
3525 composedEndIndex = composedTextEnd;
3528 String committed;
3529 if (beginIndex < composedStartIndex) {
3530 if (endIndex <= composedStartIndex) {
3531 committed = getText(beginIndex, endIndex - beginIndex);
3533 else {
3534 int firstPartLength = composedStartIndex - beginIndex;
3535 committed = getText(beginIndex, firstPartLength) + getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
3538 else {
3539 committed = getText(beginIndex + (composedEndIndex - composedStartIndex), endIndex - beginIndex);
3542 return new AttributedString(committed).getIterator();
3545 public int getCommittedTextLength() {
3546 int length = getDocument().getTextLength();
3547 if (composedText != null) {
3548 length -= composedText.length();
3550 return length;
3553 public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
3554 return null;
3557 public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
3558 String text = getSelectionModel().getSelectedText();
3559 return text == null ? null : new AttributedString(text).getIterator();
3562 private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
3563 StringBuffer strBuf = new StringBuffer();
3565 // create attributed string with no attributes
3566 for (char c = text.setIndex(composedIndex); c != CharacterIterator.DONE; c = text.next()) {
3567 strBuf.append(c);
3570 composedText = new String(strBuf);
3573 private void setInputMethodCaretPosition(InputMethodEvent e) {
3574 if (composedText != null) {
3575 int dot = composedTextStart;
3577 TextHitInfo caretPos = e.getCaret();
3578 if (caretPos != null) {
3579 dot += caretPos.getInsertionIndex();
3582 getCaretModel().moveToOffset(dot);
3583 getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
3587 private void runUndoTransparent(final Runnable runnable) {
3588 CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
3589 public void run() {
3590 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
3591 public void run() {
3592 ApplicationManager.getApplication().runWriteAction(runnable);
3594 }, "", getDocument(), UndoConfirmationPolicy.DEFAULT, getDocument());
3599 private void replaceInputMethodText(InputMethodEvent e) {
3600 int commitCount = e.getCommittedCharacterCount();
3601 AttributedCharacterIterator text = e.getText();
3603 // old composed text deletion
3604 final Document doc = getDocument();
3606 if (composedText != null) {
3607 if (!isViewer() && doc.isWritable()) {
3608 runUndoTransparent(new Runnable() {
3609 public void run() {
3610 doc.deleteString(Math.max(0, composedTextStart), Math.min(composedTextEnd, doc.getTextLength()));
3614 composedText = null;
3617 if (text != null) {
3618 text.first();
3620 // committed text insertion
3621 if (commitCount > 0) {
3622 //noinspection ForLoopThatDoesntUseLoopVariable
3623 for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
3624 if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
3625 processKeyTyped(c);
3630 // new composed text insertion
3631 if (!isViewer() && doc.isWritable()) {
3632 int composedTextIndex = text.getIndex();
3633 if (composedTextIndex < text.getEndIndex()) {
3634 createComposedString(composedTextIndex, text);
3636 runUndoTransparent(new Runnable() {
3637 public void run() {
3638 EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
3642 composedTextStart = getCaretModel().getOffset();
3643 composedTextEnd = getCaretModel().getOffset() + composedText.length();
3650 private class MyMouseAdapter extends MouseAdapter {
3651 public void mousePressed(MouseEvent e) {
3652 requestFocus();
3653 runMousePressedCommand(e);
3656 public void mouseReleased(MouseEvent e) {
3657 runMouseReleasedCommand(e);
3658 if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
3659 Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(Font.PLAIN) &&
3660 Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
3661 runMouseClickedCommand(e);
3663 myMousePressedEvent = null;
3666 public void mouseEntered(MouseEvent e) {
3667 runMouseEnteredCommand(e);
3670 public void mouseExited(MouseEvent e) {
3671 runMouseExitedCommand(e);
3672 EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
3673 if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
3674 myGutterComponent.mouseExited(e);
3677 TooltipController.getInstance().cancelTooltip(FOLDING_TOOLTIP_GROUP);
3681 private static final TooltipGroup FOLDING_TOOLTIP_GROUP = new TooltipGroup("FOLDING_TOOLTIP_GROUP", 10);
3683 private class MyMouseMotionListener implements MouseMotionListener {
3684 public void mouseDragged(MouseEvent e) {
3685 validateMousePointer(e);
3686 runMouseDraggedCommand(e);
3687 EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
3688 if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
3689 myGutterComponent.mouseDragged(e);
3692 for (EditorMouseMotionListener listener : myMouseMotionListeners) {
3693 listener.mouseDragged(event);
3697 public void mouseMoved(MouseEvent e) {
3698 validateMousePointer(e);
3699 EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
3700 if (e.getSource() == myGutterComponent) {
3701 myGutterComponent.mouseMoved(e);
3704 if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
3705 FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
3706 TooltipController controller = TooltipController.getInstance();
3707 if (fold != null) {
3708 DocumentFragment range = createDocumentFragment(fold);
3709 final Point p =
3710 SwingUtilities.convertPoint((Component)e.getSource(), e.getPoint(), getComponent().getRootPane().getLayeredPane());
3711 controller.showTooltip(EditorImpl.this, p, new DocumentFragmentTooltipRenderer(range), false, FOLDING_TOOLTIP_GROUP);
3713 else {
3714 controller.cancelTooltip(FOLDING_TOOLTIP_GROUP);
3718 for (EditorMouseMotionListener listener : myMouseMotionListeners) {
3719 listener.mouseMoved(event);
3723 private DocumentFragment createDocumentFragment(FoldRegion fold) {
3724 final FoldingGroup group = fold.getGroup();
3725 final int foldStart = fold.getStartOffset();
3726 if (group != null) {
3727 final int endOffset = myFoldingModel.getEndOffset(group);
3728 if (offsetToVisualPosition(endOffset).line == offsetToVisualPosition(foldStart).line) {
3729 return new DocumentFragment(myDocument, foldStart, endOffset);
3733 final int oldEnd = fold.getEndOffset();
3734 return new DocumentFragment(myDocument, foldStart, oldEnd);
3738 private class MyColorSchemeDelegate implements EditorColorsScheme {
3739 private final HashMap<TextAttributesKey, TextAttributes> myOwnAttributes = new HashMap<TextAttributesKey, TextAttributes>();
3740 private final HashMap<ColorKey, Color> myOwnColors = new HashMap<ColorKey, Color>();
3741 private Map<EditorFontType, Font> myFontsMap = null;
3742 private int myFontSize = -1;
3743 private String myFaceName = null;
3744 private EditorColorsScheme myGlobalScheme;
3746 private MyColorSchemeDelegate() {
3747 updateGlobalScheme();
3750 private EditorColorsScheme getGlobal() {
3751 return myGlobalScheme;
3754 public String getName() {
3755 return getGlobal().getName();
3759 protected void initFonts() {
3760 String editorFontName = getEditorFontName();
3761 int editorFontSize = getEditorFontSize();
3763 myFontsMap = new EnumMap<EditorFontType, Font>(EditorFontType.class);
3765 Font plainFont = new Font(editorFontName, Font.PLAIN, editorFontSize);
3766 Font boldFont = new Font(editorFontName, Font.BOLD, editorFontSize);
3767 Font italicFont = new Font(editorFontName, Font.ITALIC, editorFontSize);
3768 Font boldItalicFont = new Font(editorFontName, Font.BOLD + Font.ITALIC, editorFontSize);
3770 myFontsMap.put(EditorFontType.PLAIN, plainFont);
3771 myFontsMap.put(EditorFontType.BOLD, boldFont);
3772 myFontsMap.put(EditorFontType.ITALIC, italicFont);
3773 myFontsMap.put(EditorFontType.BOLD_ITALIC, boldItalicFont);
3775 reinitSettings();
3778 public void setName(String name) {
3779 getGlobal().setName(name);
3782 public TextAttributes getAttributes(TextAttributesKey key) {
3783 if (myOwnAttributes.containsKey(key)) return myOwnAttributes.get(key);
3784 return getGlobal().getAttributes(key);
3787 public void setAttributes(TextAttributesKey key, TextAttributes attributes) {
3788 myOwnAttributes.put(key, attributes);
3791 public Color getDefaultBackground() {
3792 return getGlobal().getDefaultBackground();
3795 public Color getDefaultForeground() {
3796 return getGlobal().getDefaultForeground();
3799 public Color getColor(ColorKey key) {
3800 if (myOwnColors.containsKey(key)) return myOwnColors.get(key);
3801 return getGlobal().getColor(key);
3804 public void setColor(ColorKey key, Color color) {
3805 myOwnColors.put(key, color);
3807 // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
3808 // settings in this case.
3809 myCaretModel.reinitSettings();
3810 mySelectionModel.reinitSettings();
3813 public int getEditorFontSize() {
3814 if (myFontSize == -1) {
3815 return getGlobal().getEditorFontSize();
3817 return myFontSize;
3820 public void setEditorFontSize(int fontSize) {
3821 if (fontSize < 8) fontSize = 8;
3822 if (fontSize > 20) fontSize = 20;
3823 myFontSize = fontSize;
3824 initFonts();
3827 public String getEditorFontName() {
3828 if (myFaceName == null) {
3829 return getGlobal().getEditorFontName();
3831 return myFaceName;
3834 public void setEditorFontName(String fontName) {
3835 myFaceName = fontName;
3836 initFonts();
3839 public Font getFont(EditorFontType key) {
3840 if (myFontsMap != null) {
3841 Font font = myFontsMap.get(key);
3842 if (font != null) return font;
3844 return getGlobal().getFont(key);
3847 public void setFont(EditorFontType key, Font font) {
3848 if (myFontsMap == null) {
3849 initFonts();
3851 myFontsMap.put(key, font);
3852 reinitSettings();
3855 public float getLineSpacing() {
3856 return getGlobal().getLineSpacing();
3859 public void setLineSpacing(float lineSpacing) {
3860 getGlobal().setLineSpacing(lineSpacing);
3863 public Object clone() {
3864 return null;
3867 public void readExternal(Element element) throws InvalidDataException {
3870 public void writeExternal(Element element) throws WriteExternalException {
3873 public void updateGlobalScheme() {
3874 myGlobalScheme = EditorColorsManager.getInstance().getGlobalScheme();
3878 private static class MyTransferHandler extends TransferHandler {
3879 private RangeMarker myDraggedRange = null;
3881 private static Editor getEditor(JComponent comp) {
3882 EditorComponentImpl editorComponent = (EditorComponentImpl)comp;
3883 return editorComponent.getEditor();
3886 public boolean importData(final JComponent comp, final Transferable t) {
3887 final EditorImpl editor = (EditorImpl)getEditor(comp);
3889 final int caretOffset = editor.getCaretModel().getOffset();
3890 if (myDraggedRange != null && myDraggedRange.getStartOffset() <= caretOffset && caretOffset < myDraggedRange.getEndOffset()) {
3891 return false;
3894 if (myDraggedRange != null) {
3895 editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
3898 CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
3899 public void run() {
3900 ApplicationManager.getApplication().runWriteAction(new Runnable() {
3901 public void run() {
3902 try {
3903 editor.getSelectionModel().removeSelection();
3904 final int offset;
3905 if (myDraggedRange != null) {
3906 editor.getCaretModel().moveToOffset(caretOffset);
3907 offset = caretOffset;
3909 else {
3910 offset = editor.getCaretModel().getOffset();
3912 if (editor.getDocument().getRangeGuard(offset, offset) != null) return;
3914 EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
3915 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
3917 Transferable backup = null;
3918 try {
3919 backup = clipboard.getContents(this);
3920 clipboard.setContents(t, EmptyClipboardOwner.INSTANCE);
3922 catch (Exception e) {
3923 LOG.info("Error communicating with system clipboard", e);
3926 editor.putUserData(LAST_PASTED_REGION, null);
3927 pasteHandler.execute(editor, editor.getDataContext());
3928 try {
3929 if (backup != null) {
3930 clipboard.setContents(backup, EmptyClipboardOwner.INSTANCE);
3933 catch (IllegalStateException e) {
3934 LOG.info(e);
3937 TextRange range = editor.getUserData(LAST_PASTED_REGION);
3938 if (range != null) {
3939 editor.getCaretModel().moveToOffset(range.getStartOffset());
3940 editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
3943 catch (Exception exception) {
3944 LOG.error(exception);
3949 }, EditorBundle.message("paste.command.name"), DND_COMMAND_KEY, UndoConfirmationPolicy.DEFAULT, editor.getDocument());
3951 return true;
3954 public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
3955 Editor editor = getEditor(comp);
3956 if (editor.isViewer()) return false;
3958 int offset = editor.getCaretModel().getOffset();
3959 if (editor.getDocument().getRangeGuard(offset, offset) != null) return false;
3961 for (DataFlavor transferFlavor : transferFlavors) {
3962 if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
3965 return false;
3968 protected Transferable createTransferable(JComponent c) {
3969 Editor editor = getEditor(c);
3970 String s = editor.getSelectionModel().getSelectedText();
3971 if (s == null) return null;
3972 int selectionStart = editor.getSelectionModel().getSelectionStart();
3973 int selectionEnd = editor.getSelectionModel().getSelectionEnd();
3974 myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
3976 return new StringSelection(s);
3979 public int getSourceActions(JComponent c) {
3980 return COPY_OR_MOVE;
3983 protected void exportDone(final JComponent source, Transferable data, int action) {
3984 if (data == null) return;
3986 final Component last = DnDManager.getInstance().getLastDropHandler();
3988 if (last != null && !(last instanceof EditorComponentImpl)) return;
3990 final Editor editor = getEditor(source);
3991 if (action == MOVE && !editor.isViewer()) {
3992 if (!editor.getDocument().isWritable()) {
3993 if (!FileDocumentManager.fileForDocumentCheckedOutSuccessfully(editor.getDocument(), editor.getProject())){
3994 return;
3997 CommandProcessor.getInstance().executeCommand(((EditorImpl)editor).myProject, new Runnable() {
3998 public void run() {
3999 ApplicationManager.getApplication().runWriteAction(new Runnable() {
4000 public void run() {
4001 Document doc = editor.getDocument();
4002 doc.startGuardedBlockChecking();
4003 try {
4004 doc.deleteString(myDraggedRange.getStartOffset(), myDraggedRange.getEndOffset());
4006 catch (ReadOnlyFragmentModificationException e) {
4007 EditorActionManager.getInstance().getReadonlyFragmentModificationHandler().handle(e);
4009 finally {
4010 doc.stopGuardedBlockChecking();
4015 }, EditorBundle.message("move.selection.command.name"), DND_COMMAND_KEY, UndoConfirmationPolicy.DEFAULT, editor.getDocument());
4018 myDraggedRange = null;
4022 class EditorDocumentAdapter implements PrioritizedDocumentListener {
4023 public void beforeDocumentChange(DocumentEvent e) {
4024 beforeChangedUpdate(e);
4027 public void documentChanged(DocumentEvent e) {
4028 changedUpdate(e);
4031 public int getPriority() {
4032 return 5;
4036 private class EditorSizeContainer {
4037 private TIntArrayList myLineWidths;
4038 private boolean myIsDirty;
4039 private int myOldEndLine;
4040 private Dimension mySize;
4041 private int myMaxWidth = -1;
4043 public void reset() {
4044 int visLinesCount = getVisibleLineCount();
4045 myLineWidths = new TIntArrayList(visLinesCount + 300);
4046 int[] values = new int[visLinesCount];
4047 Arrays.fill(values, -1);
4048 myLineWidths.add(values);
4049 myIsDirty = true;
4052 public void beforeChange(DocumentEvent e) {
4053 if (myDocument.isInBulkUpdate()) {
4054 myMaxWidth = mySize != null ? mySize.width : -1;
4058 myOldEndLine = getVisualPositionLine(e.getOffset() + e.getOldLength());
4061 private int getVisualPositionLine(int offset) {
4062 // Do round up of offset to the nearest line start (valid since we need only line)
4063 // This is needed for preventing access to lexer editor highlighter regions [that are reset] during bulk mode operation
4064 final int startLineOffset = myDocument.getLineStartOffset(calcLogicalLineNumber(offset));
4065 return offsetToVisualPosition(startLineOffset).line;
4068 public void changedUpdate(DocumentEvent e) {
4069 int startLine = e.getOldLength() == 0 ? myOldEndLine : getVisualPositionLine(e.getOffset());
4070 int newEndLine = e.getNewLength() == 0 ? startLine : getVisualPositionLine(e.getOffset() + e.getNewLength());
4071 int oldEndLine = myOldEndLine;
4073 final int lineWidthSize = myLineWidths.size();
4074 if (lineWidthSize == 0) {
4075 reset();
4077 else {
4078 final int min = Math.min(oldEndLine, newEndLine);
4079 final boolean toAddNewLines = min >= lineWidthSize;
4081 if (toAddNewLines) {
4082 final int[] delta = new int[min - lineWidthSize + 1];
4083 myLineWidths.insert(lineWidthSize, delta);
4086 for (int i = startLine; i <= min; i++) myLineWidths.set(i, -1);
4087 if (newEndLine > oldEndLine) {
4088 int[] delta = new int[newEndLine - oldEndLine];
4089 Arrays.fill(delta, -1);
4090 myLineWidths.insert(oldEndLine + 1, delta);
4092 else if (oldEndLine > newEndLine && !toAddNewLines && newEndLine + 1 < lineWidthSize) {
4093 myLineWidths.remove(newEndLine + 1, Math.min(oldEndLine, lineWidthSize) - newEndLine);
4095 myIsDirty = true;
4099 private void validateSizes() {
4100 if (!myIsDirty) return;
4102 int lineCount = myLineWidths.size();
4104 if (myMaxWidth != -1 && myDocument.isInBulkUpdate()) {
4105 mySize = new Dimension(myMaxWidth, getLineHeight() * lineCount);
4106 myIsDirty = false;
4107 return;
4110 final CharSequence text = myDocument.getCharsNoThreadCheck();
4111 int end = myDocument.getTextLength();
4112 int x = 0;
4113 final int fontSize = myScheme.getEditorFontSize();
4114 final String fontName = myScheme.getEditorFontName();
4116 for (int line = 0; line < lineCount; line++) {
4117 if (myLineWidths.getQuick(line) != -1) continue;
4118 x = 0;
4119 int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
4121 if (offset >= myDocument.getTextLength()) {
4122 myLineWidths.set(line, 0);
4123 break;
4126 IterationState state = new IterationState(EditorImpl.this, offset, false);
4127 int fontType = state.getMergedAttributes().getFontType();
4129 while (offset < end) {
4130 char c = text.charAt(offset);
4131 if (offset >= state.getEndOffset()) {
4132 state.advance();
4133 fontType = state.getMergedAttributes().getFontType();
4136 FoldRegion collapsed = state.getCurrentFold();
4137 if (collapsed != null) {
4138 String placeholder = collapsed.getPlaceholderText();
4139 for (int i = 0; i < placeholder.length(); i++) {
4140 x += charWidth(placeholder.charAt(i), fontType);
4142 offset = collapsed.getEndOffset();
4144 else {
4145 if (c == '\t') {
4146 x = nextTabStop(x);
4147 offset++;
4149 else {
4150 if (c == '\n') {
4151 myLineWidths.set(line, x);
4152 if (line + 1 >= lineCount || myLineWidths.getQuick(line + 1) != -1) break;
4153 offset++;
4154 x = 0;
4155 //noinspection AssignmentToForLoopParameter
4156 line++;
4158 else {
4159 x += ComplementaryFontsRegistry.getFontAbleToDisplay(c, fontSize, fontType, fontName).charWidth(c, myEditorComponent);
4160 offset++;
4167 if (lineCount > 0) {
4168 myLineWidths.set(lineCount - 1,
4169 x); // Last line can be non-zero length and won't be caught by in-loop procedure since latter only react on \n's
4172 int maxWidth = 0;
4173 for (int i = 0; i < lineCount; i++) {
4174 maxWidth = Math.max(maxWidth, myLineWidths.getQuick(i));
4177 mySize = new Dimension(maxWidth, getLineHeight() * lineCount);
4179 myIsDirty = false;
4182 public Dimension getContentSize() {
4183 validateSizes();
4184 return mySize;
4188 @NotNull
4189 public EditorGutter getGutter() {
4190 return getGutterComponentEx();
4193 public int calcColumnNumber(CharSequence text, int start, int offset, int tabSize) {
4194 IterationState state = new IterationState(this, start, false);
4195 int fontType = state.getMergedAttributes().getFontType();
4196 int column = 0;
4197 int x = 0;
4198 int spaceSize = getSpaceWidth(fontType);
4199 for (int i = start; i < offset; i++) {
4200 if (i >= state.getEndOffset()) {
4201 state.advance();
4202 fontType = state.getMergedAttributes().getFontType();
4205 char c = text.charAt(i);
4206 if (c == '\t') {
4207 int prevX = x;
4208 x = nextTabStop(x);
4209 column += (x - prevX) / spaceSize;
4210 //column += Math.max(1, (x - prevX) / spaceSize);
4212 else {
4213 x += charWidth(c, fontType);
4214 column++;
4218 return column;