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
;
53 import javax
.swing
.Timer
;
54 import javax
.swing
.plaf
.ScrollBarUI
;
55 import javax
.swing
.plaf
.basic
.BasicScrollBarUI
;
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
;
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
;
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;
199 ourCaretBlinkingCommand
= new RepaintCursorCommand();
200 ourCaretBlinkingCommand
.start();
204 public EditorImpl(Document document
, boolean viewer
, Project project
) {
206 myDocument
= (DocumentImpl
)document
;
207 myScheme
= new MyColorSchemeDelegate();
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()));
235 repaint(0, getDocument().getTextLength());
237 ((EditorMarkupModelImpl
)getMarkupModel()).repaint();
238 ((EditorMarkupModelImpl
)getMarkupModel()).markDirtied();
239 GutterIconRenderer renderer
= rangeHighlighter
.getGutterIconRenderer();
240 if (renderer
!= null) {
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
);
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() {
280 repaint(0, myDocument
.getTextLength());
282 }, 50, ModalityState
.stateForComponent(myEditorComponent
));
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() {
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
;
320 public VirtualFile
getVirtualFile() {
321 return myVirtualFile
;
325 public SelectionModel
getSelectionModel() {
326 return mySelectionModel
;
330 public MarkupModel
getMarkupModel() {
331 return myMarkupModel
;
335 public FoldingModel
getFoldingModel() {
336 return myFoldingModel
;
340 public CaretModel
getCaretModel() {
345 public ScrollingModel
getScrollingModel() {
346 return myScrollingModel
;
350 public EditorSettings
getSettings() {
351 assertIsDispatchThread();
355 public void reinitSettings() {
356 assertIsDispatchThread();
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();
383 if (myInitialMouseEvent
!= null) {
384 myIgnoreMouseEventsConsequitiveToInitial
= true;
388 public void release() {
390 LOG
.error("Double release. First released at: =====\n" + myReleasedAt
+"\n======");
393 myReleasedAt
= StringUtil
.getThrowableText(new Throwable());
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();
412 myPlainFontMetrics
= null;
413 myScrollingModel
.dispose();
414 myGutterComponent
.dispose();
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() {
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
);
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());
459 super.processMouseWheelEvent(e
);
462 myPanel
= new JPanel() {
463 public void 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();
479 protected void validateTree() {
480 int height
= myOldHeight
;
481 super.validateTree();
482 height
-= getHeight();
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()) {
532 if (processKeyTyped(event
)) {
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
);
554 public void focusLost(FocusEvent e
) {
556 int caretLine
= getCaretModel().getLogicalPosition().line
;
557 repaintLines(caretLine
, caretLine
);
562 // myBorderEffect.reset();
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
) {
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()) {
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) {
647 public EditorHighlighter
getHighlighter() {
648 assertIsDispatchThread();
649 return myHighlighter
;
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;
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);
714 CharSequence text
= myDocument
.getCharsNoThreadCheck();
716 IterationState state
= new IterationState(this, offset
, false);
718 int fontType
= state
.getMergedAttributes().getFontType();
719 int spaceSize
= getSpaceWidth(fontType
);
724 if (offset
>= textLength
) break;
726 if (offset
>= state
.getEndOffset()) {
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
) {
736 x
+= charWidth(c
, fontType
);
737 if (x
>= p
.x
) break outer
;
740 offset
= region
.getEndOffset();
744 c
= text
.charAt(offset
);
752 x
+= charWidth(c
, fontType
);
758 column
+= (x
- prevX
) / spaceSize
;
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
++;
776 if ((x
- p
.x
) * 2 < x
- prevX
) {
777 column
+= (x
- prevX
) / spaceSize
;
783 if ((x
- p
.x
) * 2 < charWidth
) column
++;
786 column
+= (p
.x
- x
) / getSpaceWidth(fontType
);
790 return new VisualPosition(line
, column
);
794 public VisualPosition
offsetToVisualPosition(int offset
) {
795 return logicalToVisualPosition(offsetToLogicalPosition(offset
));
799 public LogicalPosition
offsetToLogicalPosition(int offset
) {
800 int line
= calcLogicalLineNumber(offset
);
801 int column
= calcColumnNumber(offset
, line
);
802 return new LogicalPosition(line
, column
);
806 public LogicalPosition
xyToLogicalPosition(@NotNull Point p
) {
808 if (p
.x
>= 0 && p
.y
>= 0) {
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
);
824 public Point
logicalPositionToXY(@NotNull LogicalPosition pos
) {
825 VisualPosition visible
= logicalToVisualPosition(pos
);
826 int y
= visibleLineNumberToYPosition(visible
.line
);
834 if (pos
.line
>= myDocument
.getLineCount()) {
835 lineStartOffset
= myDocument
.getTextLength();
838 lineStartOffset
= myDocument
.getLineStartOffset(pos
.line
);
842 int x
= getTabbedTextWidth(lineStartOffset
, visible
);
843 return new Point(x
, y
);
847 public Point
visualPositionToXY(@NotNull VisualPosition visible
) {
848 int y
= visibleLineNumberToYPosition(visible
.line
);
849 int logLine
= visualToLogicalPosition(new VisualPosition(visible
.line
, 0)).line
;
857 if (logLine
>= myDocument
.getLineCount()) {
858 lineStartOffset
= myDocument
.getTextLength();
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;
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
);
882 while (column
< pos
.column
) {
883 if (offset
>= textLength
) break;
885 if (offset
>= state
.getEndOffset()) {
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
);
897 if (column
>= pos
.column
) break outer
;
899 offset
= region
.getEndOffset();
902 char c
= text
.charAt(offset
);
909 column
+= (x
- prevX
) / spaceSize
;
912 x
+= charWidth(c
, fontType
);
919 if (column
!= pos
.column
) {
920 x
+= getSpaceWidth(fontType
) * (pos
.column
- column
);
926 public int nextTabStop(int x
) {
927 int tabSize
= getTabSize();
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()) {
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
);
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
) {
1013 if (countLineFeeds(e
.getOldFragment()) != countLineFeeds(e
.getNewFragment())) {
1014 // Lines removed. Need to repaint till the end of the screen
1015 repaintToScreenBotton(startLine
);
1020 updateCaretCursor();
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() {
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();
1070 myEditorComponent
.repaintEditorComponent();
1074 public Document
getDocument() {
1079 public JComponent
getComponent() {
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() {
1105 void paint(Graphics g
) {
1106 startOptimizedScrolling();
1108 if (myCursorUpdater
!= null) {
1109 myCursorUpdater
.run();
1110 myCursorUpdater
= null;
1113 Rectangle clip
= getClipBounds(g
);
1119 Rectangle viewRect
= getScrollingModel().getVisibleArea();
1120 if (viewRect
== null) {
1125 g
.setColor(new Color(128, 255, 128));
1126 g
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);
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
);
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;
1162 public JComponent
getHeaderComponent() {
1163 if (hasHeaderComponent()) {
1164 return (JComponent
)myHeaderPanel
.getComponent(0);
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()) {
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
);
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
);
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) {
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()) {
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
) {
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
) {
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) {
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()) {
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();
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
);
1357 paintAfterFileEndBackground(iterationState
, g
, position
, clip
, lineHeight
, defaultBackground
);
1362 if (position
.y
> clip
.y
+ clip
.height
) break;
1363 position
.y
+= lineHeight
;
1367 lIterator
.advance();
1370 FoldRegion collapsedFolderAt
= iterationState
.getCurrentFold();
1371 if (collapsedFolderAt
!= null) {
1372 position
.x
= drawBackground(g
, backColor
, collapsedFolderAt
.getPlaceholderText(), position
, fontType
, defaultBackground
, clip
);
1375 if (hEnd
> lEnd
- lIterator
.getSeparatorLength()) {
1376 position
.x
= drawBackground(g
, backColor
, text
.subSequence(start
, lEnd
- lIterator
.getSeparatorLength()), position
, fontType
,
1377 defaultBackground
, clip
);
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
));
1412 if (start
.y
<= end
.y
) {
1414 height
= end
.y
- y
+ getLineHeight();
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
,
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
,
1443 Color defaultBackground
,
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
;
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
) {
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) {
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()) {
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();
1525 FoldRegion collapsedFolderAt
= myFoldingModel
.getCollapsedRegionAtOffset(start
);
1526 if (collapsedFolderAt
== null) {
1527 drawString(g
, chars
, start
, lEnd
- lIterator
.getSeparatorLength(), position
, clip
, effectColor
, effectType
, fontType
,
1530 if (position
.y
> clip
.y
+ clip
.height
) break;
1531 position
.y
+= lineHeight
;
1535 // myBorderEffect.eolReached(g, this);
1536 lIterator
.advance();
1539 FoldRegion collapsedFolderAt
= iterationState
.getCurrentFold();
1540 if (collapsedFolderAt
!= null) {
1541 int foldingXStart
= 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
);
1548 if (hEnd
> lEnd
- lIterator
.getSeparatorLength()) {
1549 position
.x
= drawString(g
, chars
, start
, lEnd
- lIterator
.getSeparatorLength(), position
, clip
, effectColor
, effectType
,
1550 fontType
, currentColor
);
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
;
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
];
1594 final FontInfo myFontType
;
1596 private char[] myLastData
;
1598 private CachedFontContent(FontInfo fontInfo
) {
1599 myFontType
= fontInfo
;
1602 private void flushContent(Graphics g
) {
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
]);
1625 private void addContent(Graphics g
, char[] _data
, int _start
, int _end
, int _x
, int _y
, Color _color
) {
1626 final int count
= myCount
;
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
;
1638 data
[count
] = _data
;
1641 starts
[count
] = _start
;
1643 color
[count
] = _color
;
1646 if (count
>= CACHED_CHARS_BUFFER_SIZE
- 1) {
1652 private void flushCachedChars(Graphics g
) {
1653 for (CachedFontContent cache
: myFontCache
) {
1654 cache
.flushContent(g
);
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()) {
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()) {
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
,
1708 EffectType effectType
,
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
;
1719 return drawTabbedString(g
, text
, start
, end
, x
, y
, effectColor
, effectType
, fontType
, fontColor
, clip
);
1722 private int drawString(Graphics g
,
1727 EffectType effectType
,
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
;
1737 return drawTabbedString(g
, text
.toCharArray(), 0, text
.length(), x
, y
, effectColor
, effectType
, fontType
, fontColor
, clip
);
1740 private int drawTabbedString(Graphics g
,
1747 EffectType effectType
,
1750 final Rectangle clip
) {
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
);
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);
1771 if (xStart
< clip
.x
&& xEnd
< clip
.x
|| xStart
> clip
.x
+ clip
.width
&& xEnd
> clip
.x
+ clip
.width
) {
1775 if (xEnd
> clip
.x
+ clip
.width
) {
1776 xEnd
= clip
.x
+ clip
.width
;
1778 if (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
);
1809 private int drawTablessString(final char[] text
,
1816 final Color fontColor
,
1817 final Rectangle clip
) {
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
);
1832 if (x
< clip
.x
&& endX
< clip
.x
) {
1837 else if (x
> clip
.x
+ clip
.width
) {
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
);
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);
1880 FontInfo fnt
= fontForChar(data
[start
], fontType
);
1881 CachedFontContent cache
= null;
1882 for (CachedFontContent fontCache
: myFontCache
) {
1883 if (fontCache
.myFontType
== fnt
) {
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;
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
) {
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') {
1951 x
+= charWidth(text
.charAt(i
), fontType
);
1953 if (x
> clip
.x
+ clip
.width
) {
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) {
1974 return myLineHeight
;
1978 if (myDescent
!= -1) {
1981 FontMetrics fontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
1982 myDescent
= fontMetrics
.getDescent();
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
;
2027 draft
.width
+= additionalSpace
;
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);
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
);
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
;
2129 LOG
.assertTrue(line
>= 0);
2131 return new VisualPosition(line
, Math
.max(0, column
));
2135 private FoldRegion
getLastCollapsedBeforePosition(VisualPosition visual
) {
2136 FoldRegion
[] topLevelCollapsed
= myFoldingModel
.fetchTopLevel();
2138 if (topLevelCollapsed
== null) return null;
2141 int end
= topLevelCollapsed
.length
- 1;
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
) {
2153 if (visFoldEnd
.line
> visual
.line
) {
2157 if (visFoldEnd
.column
< visual
.column
) {
2161 if (visFoldEnd
.column
> visual
.column
) {
2173 while (i
>= 0 && i
< topLevelCollapsed
.length
) {
2174 if (topLevelCollapsed
[i
].isValid()) break;
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
) {
2185 return topLevelCollapsed
[i
];
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
);
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());
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
) {
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) {
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
2306 if (event
.isConsumed() && !(event
.getMouseEvent().isPopupTrigger() || event
.getArea() == EditorMouseEventArea
.EDITING_AREA
)) {
2310 if (myCommandProcessor
!= null) {
2311 Runnable runnable
= new Runnable() {
2313 processMousePressed(e
);
2316 myCommandProcessor
.executeCommand(myProject
, runnable
, "", getDocument(), UndoConfirmationPolicy
.DEFAULT
, getDocument());
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()) {
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()) {
2345 if (myCommandProcessor
!= null) {
2346 Runnable runnable
= new Runnable() {
2348 processMouseReleased(e
);
2351 myCommandProcessor
.executeCommand(myProject
, runnable
, "", getDocument(), UndoConfirmationPolicy
.DEFAULT
, getDocument());
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()) {
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()) {
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
);
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() {
2402 myFoldingModel
.flushCaretShift();
2403 range
.setExpanded(expansion
);
2406 getFoldingModel().runBatchFoldingOperation(processor
);
2407 y
= myGutterComponent
.getHeadCenterY(range
);
2408 getScrollingModel().scrollVertically(y
- scrollShift
);
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;
2421 int oldSelectionStart
= mySelectionModel
.getLeadSelectionOffset();
2422 moveCaretToScreenPos(x
, y
);
2424 if (e
.isPopupTrigger()) return;
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;
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();
2453 if (e
.isShiftDown() && !e
.isControlDown() && !e
.isAltDown()) {
2454 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE
) {
2455 if (caretOffset
< mySavedSelectionStart
) {
2456 mySelectionModel
.setSelection(mySavedSelectionEnd
, caretOffset
);
2459 mySelectionModel
.setSelection(mySavedSelectionStart
, caretOffset
);
2463 mySelectionModel
.setSelection(oldSelectionStart
, caretOffset
);
2467 if (!myMousePressedInsideSelection
&& getSelectionModel().hasSelection()) {
2468 setMouseSelectionState(MOUSE_SELECTION_STATE_NONE
);
2469 mySelectionModel
.setSelection(caretOffset
, caretOffset
);
2472 if (!e
.isPopupTrigger()) {
2473 switch (e
.getClickCount()) {
2475 mySelectionModel
.selectWordAtCaret(mySettings
.isMouseClickSelectionHonorsCamelWords());
2476 setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED
);
2477 mySavedSelectionStart
= mySelectionModel
.getSelectionStart();
2478 mySavedSelectionEnd
= mySelectionModel
.getSelectionEnd();
2479 getCaretModel().moveToOffset(mySavedSelectionEnd
);
2483 mySelectionModel
.selectLineAtCaret();
2484 setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED
);
2485 mySavedSelectionStart
= mySelectionModel
.getSelectionStart();
2486 mySavedSelectionEnd
= mySelectionModel
.getSelectionEnd();
2494 private boolean checkIgnore(MouseEvent e
, boolean isFinalCheck
) {
2495 if (!myIgnoreMouseEventsConsequitiveToInitial
) {
2496 myInitialMouseEvent
= null;
2500 if (e
.getComponent() != myInitialMouseEvent
.getComponent() || !e
.getPoint().equals(myInitialMouseEvent
.getPoint())) {
2501 myIgnoreMouseEventsConsequitiveToInitial
= false;
2502 myInitialMouseEvent
= null;
2507 myIgnoreMouseEventsConsequitiveToInitial
= false;
2508 myInitialMouseEvent
= null;
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) {
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() {
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
)) {
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
;
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
));
2601 myGutterComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.DEFAULT_CURSOR
));
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
));
2613 myEditorComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.TEXT_CURSOR
));
2617 private void runMouseDraggedCommand(final MouseEvent e
) {
2618 if (myCommandProcessor
== null || myMousePressedEvent
!= null && myMousePressedEvent
.isConsumed()) {
2621 myCommandProcessor
.executeCommand(myProject
, new Runnable() {
2623 processMouseDragged(e
);
2625 }, "", MOUSE_DRAGGED_GROUP
, UndoConfirmationPolicy
.DEFAULT
, getDocument());
2628 private void processMouseDragged(MouseEvent e
) {
2629 if (SwingUtilities
.isRightMouseButton(e
)) {
2632 Rectangle rect
= getScrollingModel().getVisibleArea();
2636 if (e
.getSource() == myGutterComponent
) {
2645 if (x
> rect
.x
+ rect
.width
) {
2646 dx
= x
- rect
.x
- rect
.width
;
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
);
2678 if (isColumnMode()) {
2679 final LogicalPosition blockStart
= selectionModel
.hasBlockSelection() ? selectionModel
.getBlockStart() : oldLogicalCaret
;
2680 selectionModel
.setBlockSelection(blockStart
, getCaretModel().getLogicalPosition());
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();
2690 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2692 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
, 0)));
2695 if (newSelection
< 0) newSelection
= newCaretOffset
;
2696 selectionModel
.setSelection(mySavedSelectionEnd
, newSelection
);
2697 getCaretModel().moveToOffset(newSelection
);
2700 int newSelection
= newCaretOffset
;
2701 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2702 newSelection
= mySelectionModel
.getWordAtCaretEnd();
2705 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
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
);
2717 if (!myMousePressedInsideSelection
) {
2718 selectionModel
.setSelection(oldSelectionStart
, newCaretOffset
);
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
);
2730 selectionModel
.removeSelection();
2732 myMousePressedEvent
= null;
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
{
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;
2775 private void setBlinkCaret(boolean value
) {
2776 myIsBlinkCaret
= value
;
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
;
2792 toRepaint
= !activeCursor
.isVisible
;
2793 activeCursor
.isVisible
= true;
2797 SwingUtilities
.invokeLater(myRepaintRunnable
);
2804 void updateCaretCursor() {
2805 if (!ourIsUnitTestMode
&& !IJSwingUtilities
.hasFocus(getContentComponent())) {
2806 stopOptimizedScrolling();
2809 if (myCursorUpdater
== null) {
2810 myCursorUpdater
= new Runnable() {
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();
2826 myCaretCursor
.activate();
2829 myCaretCursor
.passivate();
2834 public void addFocusListener(FocusChangeListener listener
) {
2835 myFocusListeners
.add(listener
);
2838 public Project
getProject() {
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);
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
);
2896 public boolean isActive() {
2897 synchronized (ourCaretBlinkingCommand
) {
2902 private void passivate() {
2903 synchronized (ourCaretBlinkingCommand
) {
2908 private void setPosition(Point location
, int width
) {
2909 myStartTime
= System
.currentTimeMillis();
2910 myLocation
= location
;
2912 myWidth
= Math
.max(width
, 2);
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) {
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);
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);
2952 private class ScrollingTimer
{
2954 private static final int TIMER_PERIOD
= 100;
2955 private static final int CYCLE_SIZE
= 20;
2956 private int myXCycles
;
2957 private int myYCycles
;
2960 private int xPassedCycles
= 0;
2961 private int yPassedCycles
= 0;
2963 private void start(int dx
, int dy
) {
2967 myXCycles
= CYCLE_SIZE
/ dx
+ 1;
2968 myDx
= 1 + dx
/ CYCLE_SIZE
;
2972 myXCycles
= -CYCLE_SIZE
/ dx
+ 1;
2973 myDx
= -1 + dx
/ CYCLE_SIZE
;
2978 myYCycles
= CYCLE_SIZE
/ dy
+ 1;
2979 myDy
= 1 + dy
/ CYCLE_SIZE
;
2983 myYCycles
= -CYCLE_SIZE
/ dy
+ 1;
2984 myDy
= -1 + dy
/ CYCLE_SIZE
;
2988 if (myTimer
!= null) {
2991 myTimer
= new Timer(TIMER_PERIOD
, new ActionListener() {
2992 public void actionPerformed(ActionEvent e
) {
2993 myCommandProcessor
.executeCommand(myProject
, new Runnable() {
2995 int oldSelectionStart
= mySelectionModel
.getLeadSelectionOffset();
2996 LogicalPosition caretPosition
= getCaretModel().getLogicalPosition();
2997 int columnNumber
= caretPosition
.column
;
2999 if (xPassedCycles
>= myXCycles
) {
3001 columnNumber
+= myDx
;
3004 int lineNumber
= caretPosition
.line
;
3006 if (yPassedCycles
>= myYCycles
) {
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();
3025 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
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
);
3035 int newSelection
= newCaretOffset
;
3036 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
3037 newSelection
= mySelectionModel
.getWordAtCaretEnd();
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
);
3052 if (mySelectionModel
.hasBlockSelection()) {
3053 mySelectionModel
.setBlockSelection(mySelectionModel
.getBlockStart(), getCaretModel().getLogicalPosition());
3056 mySelectionModel
.setSelection(oldSelectionStart
, getCaretModel().getOffset());
3059 }, EditorBundle
.message("move.cursor.command.name"), getDocument(), UndoConfirmationPolicy
.DEFAULT
, getDocument());
3065 private void stop() {
3066 if (myTimer
!= null) {
3072 private int validateOffset(int offset
) {
3073 if (offset
< 0) return 0;
3074 if (offset
> myDocument
.getTextLength()) return myDocument
.getTextLength();
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
) {
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 :) )
3096 int getDecScrollButtonHeight() {
3097 ScrollBarUI barUI
= getUI();
3098 Insets insets
= getInsets();
3099 if (barUI
instanceof BasicScrollBarUI
) {
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());
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 :) )
3122 int getIncScrollButtonHeight() {
3123 ScrollBarUI barUI
= getUI();
3124 Insets insets
= getInsets();
3125 if (barUI
instanceof BasicScrollBarUI
) {
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;
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();
3165 public CopyProvider
getCopyProvider() {
3169 public CutProvider
getCutProvider() {
3173 public PasteProvider
getPasteProvider() {
3178 public DeleteProvider
getDeleteProvider() {
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
) {
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();
3235 public EditorColorsScheme
getColorsScheme() {
3236 assertIsDispatchThread();
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());
3252 myScrollPane
.setLayout(new ScrollPaneLayout());
3254 myScrollingModel
.scrollHorizontally(currentHorOffset
);
3257 public void setVerticalScrollbarVisible(boolean b
) {
3259 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_ALWAYS
);
3262 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_NEVER
);
3266 public void setHorizontalScrollbarVisible(boolean b
) {
3268 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
3271 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_NEVER
);
3275 int getVerticalScrollbarOrientation() {
3276 return myScrollbarOrientation
;
3279 MyScrollBar
getVerticalScrollBar() {
3280 return myVerticalScrollBar
;
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
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];
3343 GuiUtils
.invokeAndWait(new Runnable() {
3345 r
[0] = myDelegate
.getTextLocation(offset
);
3349 catch (InterruptedException e
) {
3352 catch (InvocationTargetException e
) {
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];
3363 GuiUtils
.invokeAndWait(new Runnable() {
3365 r
[0] = myDelegate
.getLocationOffset(x
, y
);
3369 catch (InterruptedException e
) {
3372 catch (InvocationTargetException e
) {
3378 public int getInsertPositionOffset() {
3379 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getInsertPositionOffset();
3381 final int[] r
= new int[1];
3383 GuiUtils
.invokeAndWait(new Runnable() {
3385 r
[0] = myDelegate
.getInsertPositionOffset();
3389 catch (InterruptedException e
) {
3392 catch (InvocationTargetException e
) {
3398 public AttributedCharacterIterator
getCommittedText(final int beginIndex
,
3400 final AttributedCharacterIterator
.Attribute
[] attributes
) {
3401 if (ApplicationManager
.getApplication().isDispatchThread()) {
3402 return myDelegate
.getCommittedText(beginIndex
, endIndex
, attributes
);
3404 final AttributedCharacterIterator
[] r
= new AttributedCharacterIterator
[1];
3406 GuiUtils
.invokeAndWait(new Runnable() {
3408 r
[0] = myDelegate
.getCommittedText(beginIndex
, endIndex
, attributes
);
3412 catch (InterruptedException e
) {
3415 catch (InvocationTargetException e
) {
3421 public int getCommittedTextLength() {
3422 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getCommittedTextLength();
3423 final int[] r
= new int[1];
3425 GuiUtils
.invokeAndWait(new Runnable() {
3427 r
[0] = myDelegate
.getCommittedTextLength();
3431 catch (InterruptedException e
) {
3434 catch (InvocationTargetException e
) {
3440 public AttributedCharacterIterator
cancelLatestCommittedText(AttributedCharacterIterator
.Attribute
[] attributes
) {
3444 public AttributedCharacterIterator
getSelectedText(final AttributedCharacterIterator
.Attribute
[] attributes
) {
3445 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getSelectedText(attributes
);
3447 final AttributedCharacterIterator
[] r
= new AttributedCharacterIterator
[1];
3449 GuiUtils
.invokeAndWait(new Runnable() {
3451 r
[0] = myDelegate
.getSelectedText(attributes
);
3455 catch (InterruptedException e
) {
3458 catch (InvocationTargetException e
) {
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
);
3479 public TextHitInfo
getLocationOffset(int x
, int y
) {
3480 if (composedText
!= null) {
3481 Point p
= getContentComponent().getLocationOnScreen();
3484 int pos
= logicalPositionToOffset(xyToLogicalPosition(p
));
3485 if (pos
>= composedTextStart
&& pos
<= composedTextEnd
) {
3486 return TextHitInfo
.leading(pos
- composedTextStart
);
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
) {
3506 if (caretIndex
< composedEndIndex
) {
3507 return composedStartIndex
;
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
;
3529 if (beginIndex
< composedStartIndex
) {
3530 if (endIndex
<= composedStartIndex
) {
3531 committed
= getText(beginIndex
, endIndex
- beginIndex
);
3534 int firstPartLength
= composedStartIndex
- beginIndex
;
3535 committed
= getText(beginIndex
, firstPartLength
) + getText(composedEndIndex
, endIndex
- beginIndex
- firstPartLength
);
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();
3553 public AttributedCharacterIterator
cancelLatestCommittedText(AttributedCharacterIterator
.Attribute
[] attributes
) {
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()) {
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() {
3590 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
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() {
3610 doc
.deleteString(Math
.max(0, composedTextStart
), Math
.min(composedTextEnd
, doc
.getTextLength()));
3614 composedText
= null;
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
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() {
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
) {
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();
3708 DocumentFragment range
= createDocumentFragment(fold
);
3710 SwingUtilities
.convertPoint((Component
)e
.getSource(), e
.getPoint(), getComponent().getRootPane().getLayeredPane());
3711 controller
.showTooltip(EditorImpl
.this, p
, new DocumentFragmentTooltipRenderer(range
), false, FOLDING_TOOLTIP_GROUP
);
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
);
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();
3820 public void setEditorFontSize(int fontSize
) {
3821 if (fontSize
< 8) fontSize
= 8;
3822 if (fontSize
> 20) fontSize
= 20;
3823 myFontSize
= fontSize
;
3827 public String
getEditorFontName() {
3828 if (myFaceName
== null) {
3829 return getGlobal().getEditorFontName();
3834 public void setEditorFontName(String fontName
) {
3835 myFaceName
= fontName
;
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) {
3851 myFontsMap
.put(key
, font
);
3855 public float getLineSpacing() {
3856 return getGlobal().getLineSpacing();
3859 public void setLineSpacing(float lineSpacing
) {
3860 getGlobal().setLineSpacing(lineSpacing
);
3863 public Object
clone() {
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()) {
3894 if (myDraggedRange
!= null) {
3895 editor
.getCaretModel().moveToOffset(editor
.mySavedCaretOffsetForDNDUndoHack
);
3898 CommandProcessor
.getInstance().executeCommand(editor
.myProject
, new Runnable() {
3900 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
3903 editor
.getSelectionModel().removeSelection();
3905 if (myDraggedRange
!= null) {
3906 editor
.getCaretModel().moveToOffset(caretOffset
);
3907 offset
= caretOffset
;
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;
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());
3929 if (backup
!= null) {
3930 clipboard
.setContents(backup
, EmptyClipboardOwner
.INSTANCE
);
3933 catch (IllegalStateException 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());
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;
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())){
3997 CommandProcessor
.getInstance().executeCommand(((EditorImpl
)editor
).myProject
, new Runnable() {
3999 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
4001 Document doc
= editor
.getDocument();
4002 doc
.startGuardedBlockChecking();
4004 doc
.deleteString(myDraggedRange
.getStartOffset(), myDraggedRange
.getEndOffset());
4006 catch (ReadOnlyFragmentModificationException e
) {
4007 EditorActionManager
.getInstance().getReadonlyFragmentModificationHandler().handle(e
);
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
) {
4031 public int getPriority() {
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
);
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) {
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
);
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
);
4110 final CharSequence text
= myDocument
.getCharsNoThreadCheck();
4111 int end
= myDocument
.getTextLength();
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;
4119 int offset
= logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line
, 0)));
4121 if (offset
>= myDocument
.getTextLength()) {
4122 myLineWidths
.set(line
, 0);
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()) {
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();
4151 myLineWidths
.set(line
, x
);
4152 if (line
+ 1 >= lineCount
|| myLineWidths
.getQuick(line
+ 1) != -1) break;
4155 //noinspection AssignmentToForLoopParameter
4159 x
+= ComplementaryFontsRegistry
.getFontAbleToDisplay(c
, fontSize
, fontType
, fontName
).charWidth(c
, myEditorComponent
);
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
4173 for (int i
= 0; i
< lineCount
; i
++) {
4174 maxWidth
= Math
.max(maxWidth
, myLineWidths
.getQuick(i
));
4177 mySize
= new Dimension(maxWidth
, getLineHeight() * lineCount
);
4182 public Dimension
getContentSize() {
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();
4198 int spaceSize
= getSpaceWidth(fontType
);
4199 for (int i
= start
; i
< offset
; i
++) {
4200 if (i
>= state
.getEndOffset()) {
4202 fontType
= state
.getMergedAttributes().getFontType();
4205 char c
= text
.charAt(i
);
4209 column
+= (x
- prevX
) / spaceSize
;
4210 //column += Math.max(1, (x - prevX) / spaceSize);
4213 x
+= charWidth(c
, fontType
);