2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.openapi
.editor
.impl
;
18 import com
.intellij
.Patches
;
19 import com
.intellij
.codeInsight
.hint
.DocumentFragmentTooltipRenderer
;
20 import com
.intellij
.codeInsight
.hint
.TooltipController
;
21 import com
.intellij
.codeInsight
.hint
.TooltipGroup
;
22 import com
.intellij
.concurrency
.JobScheduler
;
23 import com
.intellij
.ide
.*;
24 import com
.intellij
.ide
.dnd
.DnDManager
;
25 import com
.intellij
.openapi
.actionSystem
.ActionManager
;
26 import com
.intellij
.openapi
.actionSystem
.DataContext
;
27 import com
.intellij
.openapi
.actionSystem
.IdeActions
;
28 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
29 import com
.intellij
.openapi
.actionSystem
.ex
.ActionManagerEx
;
30 import com
.intellij
.openapi
.application
.Application
;
31 import com
.intellij
.openapi
.application
.ApplicationManager
;
32 import com
.intellij
.openapi
.application
.ModalityState
;
33 import com
.intellij
.openapi
.application
.ex
.ApplicationManagerEx
;
34 import com
.intellij
.openapi
.command
.CommandProcessor
;
35 import com
.intellij
.openapi
.command
.UndoConfirmationPolicy
;
36 import com
.intellij
.openapi
.diagnostic
.Logger
;
37 import com
.intellij
.openapi
.editor
.*;
38 import com
.intellij
.openapi
.editor
.actionSystem
.DocCommandGroupId
;
39 import com
.intellij
.openapi
.editor
.actionSystem
.EditorAction
;
40 import com
.intellij
.openapi
.editor
.actionSystem
.EditorActionHandler
;
41 import com
.intellij
.openapi
.editor
.actionSystem
.EditorActionManager
;
42 import com
.intellij
.openapi
.editor
.colors
.*;
43 import com
.intellij
.openapi
.editor
.event
.*;
44 import com
.intellij
.openapi
.editor
.ex
.*;
45 import com
.intellij
.openapi
.editor
.ex
.util
.EditorUtil
;
46 import com
.intellij
.openapi
.editor
.ex
.util
.EmptyEditorHighlighter
;
47 import com
.intellij
.openapi
.editor
.highlighter
.EditorHighlighter
;
48 import com
.intellij
.openapi
.editor
.highlighter
.HighlighterClient
;
49 import com
.intellij
.openapi
.editor
.impl
.event
.MarkupModelEvent
;
50 import com
.intellij
.openapi
.editor
.impl
.event
.MarkupModelListener
;
51 import com
.intellij
.openapi
.editor
.markup
.*;
52 import com
.intellij
.openapi
.fileEditor
.FileDocumentManager
;
53 import com
.intellij
.openapi
.progress
.ProgressManager
;
54 import com
.intellij
.openapi
.project
.Project
;
55 import com
.intellij
.openapi
.ui
.TestableUi
;
56 import com
.intellij
.openapi
.util
.*;
57 import com
.intellij
.openapi
.util
.text
.StringUtil
;
58 import com
.intellij
.openapi
.vfs
.VirtualFile
;
59 import com
.intellij
.ui
.GuiUtils
;
60 import com
.intellij
.ui
.JScrollPane2
;
61 import com
.intellij
.util
.Alarm
;
62 import com
.intellij
.util
.IJSwingUtilities
;
63 import com
.intellij
.util
.containers
.ContainerUtil
;
64 import com
.intellij
.util
.containers
.HashMap
;
65 import com
.intellij
.util
.ui
.EmptyClipboardOwner
;
66 import com
.intellij
.util
.ui
.UIUtil
;
67 import com
.intellij
.util
.ui
.update
.UiNotifyConnector
;
68 import gnu
.trove
.TIntArrayList
;
69 import org
.jdom
.Element
;
70 import org
.jetbrains
.annotations
.NonNls
;
71 import org
.jetbrains
.annotations
.NotNull
;
72 import org
.jetbrains
.annotations
.Nullable
;
75 import javax
.swing
.Timer
;
76 import javax
.swing
.plaf
.ScrollBarUI
;
77 import javax
.swing
.plaf
.basic
.BasicScrollBarUI
;
79 import java
.awt
.datatransfer
.Clipboard
;
80 import java
.awt
.datatransfer
.DataFlavor
;
81 import java
.awt
.datatransfer
.StringSelection
;
82 import java
.awt
.datatransfer
.Transferable
;
83 import java
.awt
.dnd
.DropTarget
;
84 import java
.awt
.dnd
.DropTargetAdapter
;
85 import java
.awt
.dnd
.DropTargetDragEvent
;
86 import java
.awt
.dnd
.DropTargetDropEvent
;
87 import java
.awt
.event
.*;
88 import java
.awt
.font
.TextHitInfo
;
89 import java
.awt
.im
.InputMethodRequests
;
90 import java
.beans
.PropertyChangeListener
;
91 import java
.beans
.PropertyChangeSupport
;
92 import java
.lang
.reflect
.Field
;
93 import java
.lang
.reflect
.InvocationTargetException
;
94 import java
.text
.AttributedCharacterIterator
;
95 import java
.text
.AttributedString
;
96 import java
.text
.CharacterIterator
;
98 import java
.util
.concurrent
.CopyOnWriteArrayList
;
99 import java
.util
.concurrent
.ScheduledFuture
;
100 import java
.util
.concurrent
.TimeUnit
;
102 public final class EditorImpl
extends UserDataHolderBase
implements EditorEx
, HighlighterClient
, TestableUi
{
103 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
104 private static final Key DND_COMMAND_KEY
= Key
.create("DndCommand");
105 public static final Key
<Boolean
> DO_DOCUMENT_UPDATE_TEST
= Key
.create("DoDocumentUpdateTest");
106 private final DocumentImpl myDocument
;
108 private final JPanel myPanel
;
109 private final JScrollPane myScrollPane
;
110 private final EditorComponentImpl myEditorComponent
;
111 private final EditorGutterComponentImpl myGutterComponent
;
114 @SuppressWarnings({"UnusedDeclaration"})
115 ComplementaryFontsRegistry registry
; // load costly font info
118 private final CommandProcessor myCommandProcessor
;
119 private final MyScrollBar myVerticalScrollBar
;
121 private final CopyOnWriteArrayList
<EditorMouseListener
> myMouseListeners
= ContainerUtil
.createEmptyCOWList();
122 private final CopyOnWriteArrayList
<EditorMouseMotionListener
> myMouseMotionListeners
;
124 private int myCharHeight
= -1;
125 private int myLineHeight
= -1;
126 private int myDescent
= -1;
128 private boolean myIsInsertMode
= true;
130 private final CaretCursor myCaretCursor
;
131 private final ScrollingTimer myScrollingTimer
= new ScrollingTimer();
133 private final Key
<Object
> MOUSE_DRAGGED_GROUP
= Key
.create("MouseDraggedGroup");
135 private final DocumentListener myEditorDocumentAdapter
;
137 private final SettingsImpl mySettings
;
139 private boolean isReleased
= false;
141 private MouseEvent myMousePressedEvent
= null;
143 private int mySavedSelectionStart
= -1;
144 private int mySavedSelectionEnd
= -1;
145 private int myLastColumnNumber
= 0;
147 private final PropertyChangeSupport myPropertyChangeSupport
= new PropertyChangeSupport(this);
148 private MyEditable myEditable
;
150 private EditorColorsScheme myScheme
;
151 private final boolean myIsViewer
;
152 private final SelectionModelImpl mySelectionModel
;
153 private final EditorMarkupModelImpl myMarkupModel
;
154 private final FoldingModelImpl myFoldingModel
;
155 private final ScrollingModelImpl myScrollingModel
;
156 private final CaretModelImpl myCaretModel
;
158 private static final RepaintCursorCommand ourCaretBlinkingCommand
;
160 // private final BorderEffect myBorderEffect = new BorderEffect();
162 private int myMouseSelectionState
= MOUSE_SELECTION_STATE_NONE
;
163 private FoldRegion myMouseSelectedRegion
= null;
165 private static final int MOUSE_SELECTION_STATE_NONE
= 0;
166 private static final int MOUSE_SELECTION_STATE_WORD_SELECTED
= 1;
167 private static final int MOUSE_SELECTION_STATE_LINE_SELECTED
= 2;
169 private final MarkupModelListener myMarkupModelListener
;
171 private EditorHighlighter myHighlighter
;
173 private int myScrollbarOrientation
;
174 private boolean myMousePressedInsideSelection
;
175 private FontMetrics myPlainFontMetrics
;
176 private FontMetrics myBoldFontMetrics
;
177 private FontMetrics myItalicFontMetrics
;
178 private FontMetrics myBoldItalicFontMetrics
;
180 private static final int CACHED_CHARS_BUFFER_SIZE
= 300;
182 private final ArrayList
<CachedFontContent
> myFontCache
= new ArrayList
<CachedFontContent
>();
183 private FontInfo myCurrentFontType
= null;
185 private final EditorSizeContainer mySizeContainer
= new EditorSizeContainer();
187 private Runnable myCursorUpdater
;
188 private int myCaretUpdateVShift
;
189 private final Project myProject
;
190 private long myMouseSelectionChangeTimestamp
;
191 private int mySavedCaretOffsetForDNDUndoHack
;
192 private final ArrayList
<FocusChangeListener
> myFocusListeners
= new ArrayList
<FocusChangeListener
>();
194 private MyInputMethodHandler myInputMethodRequestsHandler
;
195 private InputMethodRequests myInputMethodRequestsSwingWrapper
;
196 private boolean myIsOneLineMode
;
197 private boolean myIsRendererMode
;
198 private VirtualFile myVirtualFile
;
199 private boolean myIsColumnMode
= false;
200 private Color myForcedBackground
= null;
201 private Dimension myPreferredSize
;
202 private Runnable myGutterSizeUpdater
= null;
203 private boolean myGutterNeedsUpdate
= false;
204 private Alarm myAppleRepaintAlarm
;
205 private boolean myEmbeddedIntoDialogWrapper
;
206 private CachedFontContent myLastCache
;
207 private boolean mySpacesHaveSameWidth
;
209 private Point myLastBackgroundPosition
= null;
210 private Color myLastBackgroundColor
= null;
211 private int myLastBackgroundWidth
;
212 private static final boolean ourIsUnitTestMode
= ApplicationManager
.getApplication().isUnitTestMode();
213 private final JPanel myHeaderPanel
;
215 private MouseEvent myInitialMouseEvent
;
216 private boolean myIgnoreMouseEventsConsecutiveToInitial
;
218 private String myReleasedAt
= null;
220 private EditorDropHandler myDropHandler
;
223 ourCaretBlinkingCommand
= new RepaintCursorCommand();
224 ourCaretBlinkingCommand
.start();
228 public EditorImpl(Document document
, boolean viewer
, Project project
) {
230 myDocument
= (DocumentImpl
)document
;
231 myScheme
= new MyColorSchemeDelegate();
233 mySettings
= new SettingsImpl(this);
235 mySelectionModel
= new SelectionModelImpl(this);
236 myMarkupModel
= new EditorMarkupModelImpl(this);
237 myFoldingModel
= new FoldingModelImpl(this);
238 myCaretModel
= new CaretModelImpl(this);
239 mySizeContainer
.reset();
241 myCommandProcessor
= CommandProcessor
.getInstance();
243 myEditorDocumentAdapter
= new EditorDocumentAdapter();
244 myMouseMotionListeners
= ContainerUtil
.createEmptyCOWList();
246 myMarkupModelListener
= new MarkupModelListener() {
247 public void rangeHighlighterChanged(MarkupModelEvent event
) {
248 assertIsDispatchThread();
250 RangeHighlighterImpl rangeHighlighter
= (RangeHighlighterImpl
)event
.getHighlighter();
251 if (rangeHighlighter
.isValid()) {
252 int start
= rangeHighlighter
.getAffectedAreaStartOffset();
253 int end
= rangeHighlighter
.getAffectedAreaEndOffset();
254 int startLine
= myDocument
.getLineNumber(start
);
255 int endLine
= myDocument
.getLineNumber(end
);
256 repaintLines(Math
.max(0, startLine
- 1), Math
.min(endLine
+ 1, getDocument().getLineCount()));
259 repaint(0, getDocument().getTextLength());
261 ((EditorMarkupModelImpl
)getMarkupModel()).repaint();
262 ((EditorMarkupModelImpl
)getMarkupModel()).markDirtied();
263 GutterIconRenderer renderer
= rangeHighlighter
.getGutterIconRenderer();
264 if (renderer
!= null) {
271 ((MarkupModelEx
)myDocument
.getMarkupModel(myProject
)).addMarkupModelListener(myMarkupModelListener
);
272 ((MarkupModelEx
)getMarkupModel()).addMarkupModelListener(myMarkupModelListener
);
274 myDocument
.addDocumentListener(myFoldingModel
);
275 myDocument
.addDocumentListener(myCaretModel
);
276 myDocument
.addDocumentListener(mySelectionModel
);
277 myDocument
.addDocumentListener(myEditorDocumentAdapter
);
279 myCaretCursor
= new CaretCursor();
281 myFoldingModel
.flushCaretShift();
282 myScrollbarOrientation
= VERTICAL_SCROLLBAR_RIGHT
;
284 EditorHighlighter highlighter
= new EmptyEditorHighlighter(myScheme
.getAttributes(HighlighterColors
.TEXT
));
285 setHighlighter(highlighter
);
287 myEditorComponent
= new EditorComponentImpl(this);
288 myScrollPane
= new MyScrollPane();
289 myPanel
= new JPanel() {
290 public void addNotify() {
292 if (((JComponent
)getParent()).getBorder() != null) myScrollPane
.setBorder(null);
296 myHeaderPanel
= new MyHeaderPanel();
297 myVerticalScrollBar
= new MyScrollBar(Adjustable
.VERTICAL
);
298 myGutterComponent
= new EditorGutterComponentImpl(this);
301 myScrollingModel
= new ScrollingModelImpl(this);
303 myGutterComponent
.updateSize();
304 Dimension preferredSize
= getPreferredSize();
305 myEditorComponent
.setSize(preferredSize
);
307 if (Patches
.APPLE_BUG_ID_3716835
) {
308 myScrollingModel
.addVisibleAreaListener(new VisibleAreaListener() {
309 public void visibleAreaChanged(VisibleAreaEvent e
) {
310 if (myAppleRepaintAlarm
== null) {
311 myAppleRepaintAlarm
= new Alarm(Alarm
.ThreadToUse
.SWING_THREAD
);
313 myAppleRepaintAlarm
.cancelAllRequests();
314 myAppleRepaintAlarm
.addRequest(new Runnable() {
316 repaint(0, myDocument
.getTextLength());
318 }, 50, ModalityState
.stateForComponent(myEditorComponent
));
325 // This hacks context layout problem where editor appears scrolled to the right just after it is created.
326 if (!ourIsUnitTestMode
) {
327 UiNotifyConnector
.doWhenFirstShown(myEditorComponent
, new Runnable() {
329 if (!isDisposed() && !myScrollingModel
.isScrollingNow()) {
330 myScrollingModel
.disableAnimation();
331 myScrollingModel
.scrollHorizontally(0);
332 myScrollingModel
.enableAnimation();
339 public boolean isViewer() {
340 return myIsViewer
|| myIsRendererMode
;
343 public boolean isRendererMode() {
344 return myIsRendererMode
;
347 public void setRendererMode(boolean isRendererMode
) {
348 myIsRendererMode
= isRendererMode
;
351 public void setFile(VirtualFile vFile
) {
352 myVirtualFile
= vFile
;
356 public VirtualFile
getVirtualFile() {
357 return myVirtualFile
;
361 public SelectionModel
getSelectionModel() {
362 return mySelectionModel
;
366 public MarkupModel
getMarkupModel() {
367 return myMarkupModel
;
371 public FoldingModel
getFoldingModel() {
372 return myFoldingModel
;
376 public CaretModel
getCaretModel() {
381 public ScrollingModel
getScrollingModel() {
382 return myScrollingModel
;
386 public EditorSettings
getSettings() {
391 public void reinitSettings() {
392 assertIsDispatchThread();
396 myPlainFontMetrics
= null;
398 myCaretModel
.reinitSettings();
399 mySelectionModel
.reinitSettings();
400 mySettings
.reinitSettings();
401 ourCaretBlinkingCommand
.setBlinkCaret(mySettings
.isBlinkCaret());
402 ourCaretBlinkingCommand
.setBlinkPeriod(mySettings
.getCaretBlinkPeriod());
403 mySizeContainer
.reset();
404 myFoldingModel
.refreshSettings();
405 myFoldingModel
.rebuild();
407 if (myScheme
instanceof MyColorSchemeDelegate
) {
408 ((MyColorSchemeDelegate
)myScheme
).updateGlobalScheme();
410 myHighlighter
.setColorScheme(myScheme
);
412 myGutterComponent
.reinitSettings();
413 myGutterComponent
.revalidate();
415 myEditorComponent
.repaint();
419 if (myInitialMouseEvent
!= null) {
420 myIgnoreMouseEventsConsecutiveToInitial
= true;
424 public void release() {
426 LOG
.error("Double release. First released at: =====\n" + myReleasedAt
+"\n======");
429 myReleasedAt
= StringUtil
.getThrowableText(new Throwable());
432 myDocument
.removeDocumentListener(myHighlighter
);
433 myDocument
.removeDocumentListener(myEditorDocumentAdapter
);
434 myDocument
.removeDocumentListener(myFoldingModel
);
435 myDocument
.removeDocumentListener(myCaretModel
);
436 myDocument
.removeDocumentListener(mySelectionModel
);
438 MarkupModelEx markupModel
= (MarkupModelEx
)myDocument
.getMarkupModel(myProject
, false);
439 if (markupModel
instanceof MarkupModelImpl
) {
440 markupModel
.removeMarkupModelListener(myMarkupModelListener
);
443 myMarkupModel
.dispose();
448 myPlainFontMetrics
= null;
449 myScrollingModel
.dispose();
450 myGutterComponent
.dispose();
452 //myFoldingModel.dispose(); TODO rangemarker tree
455 private void clearCaretThread() {
456 synchronized (ourCaretBlinkingCommand
) {
457 if (ourCaretBlinkingCommand
.myEditor
== this) {
458 ourCaretBlinkingCommand
.myEditor
= null;
463 private void initComponent() {
464 // myStatusBar = new EditorStatusBarImpl();
466 //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
467 myPanel
.setLayout(new BorderLayout());
469 myPanel
.add(myHeaderPanel
, BorderLayout
.NORTH
);
471 myGutterComponent
.setOpaque(true);
473 myScrollPane
.setVerticalScrollBar(myVerticalScrollBar
);
474 final MyScrollBar horizontalScrollBar
= new MyScrollBar(Adjustable
.HORIZONTAL
);
475 myScrollPane
.setHorizontalScrollBar(horizontalScrollBar
);
476 myScrollPane
.setViewportView(myEditorComponent
);
477 //myScrollPane.setBorder(null);
478 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_ALWAYS
);
479 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
482 myScrollPane
.setRowHeaderView(myGutterComponent
);
483 stopOptimizedScrolling();
485 myEditorComponent
.setTransferHandler(new MyTransferHandler());
486 myEditorComponent
.setAutoscrolls(true);
488 /* Default mode till 1.4.0
489 * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
492 if (mayShowToolbar()) {
493 JLayeredPane layeredPane
= new JLayeredPane() {
495 public void doLayout() {
496 final Component
[] components
= getComponents();
497 final Rectangle r
= getBounds();
498 for (Component c
: components
) {
499 if (c
instanceof JScrollPane
) {
500 c
.setBounds(0, 0, r
.width
, r
.height
);
503 final Dimension d
= c
.getPreferredSize();
504 final MyScrollBar scrollBar
= getVerticalScrollBar();
505 c
.setBounds(r
.width
- d
.width
- scrollBar
.getWidth() - 30, 20, d
.width
, d
.height
);
511 layeredPane
.add(myScrollPane
, JLayeredPane
.DEFAULT_LAYER
);
512 myPanel
.add(layeredPane
);
514 new ContextMenuImpl(layeredPane
, myScrollPane
, this);
517 myPanel
.add(myScrollPane
);
520 //myPanel.add(myScrollPane);
522 myEditorComponent
.addKeyListener(new KeyAdapter() {
523 public void keyTyped(KeyEvent event
) {
524 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
525 if (event
.isConsumed()) {
528 if (processKeyTyped(event
)) {
534 MyMouseAdapter mouseAdapter
= new MyMouseAdapter();
535 myEditorComponent
.addMouseListener(mouseAdapter
);
536 myGutterComponent
.addMouseListener(mouseAdapter
);
538 MyMouseMotionListener mouseMotionListener
= new MyMouseMotionListener();
539 myEditorComponent
.addMouseMotionListener(mouseMotionListener
);
540 myGutterComponent
.addMouseMotionListener(mouseMotionListener
);
542 myEditorComponent
.addFocusListener(new FocusAdapter() {
543 public void focusGained(FocusEvent e
) {
544 myCaretCursor
.activate();
545 int caretLine
= getCaretModel().getLogicalPosition().line
;
546 repaintLines(caretLine
, caretLine
);
548 if (myGutterNeedsUpdate
) {
553 public void focusLost(FocusEvent e
) {
555 int caretLine
= getCaretModel().getLogicalPosition().line
;
556 repaintLines(caretLine
, caretLine
);
561 // myBorderEffect.reset();
563 final DropTarget dropTarget
= myEditorComponent
.getDropTarget();
564 if (dropTarget
!= null) { // might be null in headless environment
565 dropTarget
.addDropTargetListener(new DropTargetAdapter() {
566 public void drop(DropTargetDropEvent dtde
) {
569 public void dragOver(DropTargetDragEvent dtde
) {
570 Point location
= dtde
.getLocation();
572 moveCaretToScreenPos(location
.x
, location
.y
);
573 getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
578 catch (TooManyListenersException e
) {
582 myPanel
.addComponentListener(new ComponentAdapter() {
583 public void componentResized(ComponentEvent e
) {
584 myMarkupModel
.repaint();
589 private boolean mayShowToolbar() {
590 return !isEmbeddedIntoDialogWrapper() && !isOneLineMode() && ContextMenuImpl
.mayShowToolbar(myDocument
);
593 public void setFontSize(final int fontSize
) {
594 int oldFontSize
= myScheme
.getEditorFontSize();
595 myScheme
.setEditorFontSize(fontSize
);
596 myPropertyChangeSupport
.firePropertyChange(PROP_FONT_SIZE
, oldFontSize
, fontSize
);
599 public ActionCallback
type(final String text
) {
600 final ActionCallback result
= new ActionCallback();
602 Application app
= ApplicationManager
.getApplication();
603 if (!app
.isWriteAccessAllowed()) {
604 result
.setRejected();
606 app
.runWriteAction(new Runnable() {
608 for (int i
= 0; i
< text
.length(); i
++) {
609 if (!processKeyTyped(text
.charAt(i
))) {
610 result
.setRejected();
623 private boolean processKeyTyped(char c
) {
624 // [vova] This is patch for Mac OS X. Under Mac "input methods"
625 // is handled before our EventQueue consume upcoming KeyEvents.
626 IdeEventQueue queue
= IdeEventQueue
.getInstance();
627 if (queue
.shouldNotTypeInEditor() || ProgressManager
.getInstance().hasModalProgressIndicator()) {
630 ActionManagerEx actionManager
= ActionManagerEx
.getInstanceEx();
631 DataContext dataContext
= getDataContext();
632 actionManager
.fireBeforeEditorTyping(c
, dataContext
);
633 EditorActionManager
.getInstance().getTypedAction().actionPerformed(this, c
, dataContext
);
638 private void fireFocusLost() {
639 FocusChangeListener
[] listeners
= getFocusListeners();
640 for (FocusChangeListener listener
: listeners
) {
641 listener
.focusLost(this);
645 private FocusChangeListener
[] getFocusListeners() {
646 return myFocusListeners
.toArray(new FocusChangeListener
[myFocusListeners
.size()]);
649 private void fireFocusGained() {
650 FocusChangeListener
[] listeners
= getFocusListeners();
651 for (FocusChangeListener listener
: listeners
) {
652 listener
.focusGained(this);
656 public void setHighlighter(EditorHighlighter highlighter
) {
657 assertIsDispatchThread();
658 final Document document
= getDocument();
659 if (myHighlighter
!= null) {
660 document
.removeDocumentListener(myHighlighter
);
663 document
.addDocumentListener(highlighter
);
664 highlighter
.setEditor(this);
665 highlighter
.setText(document
.getCharsSequence());
666 myHighlighter
= highlighter
;
667 if (document
instanceof DocumentImpl
) {
668 ((DocumentImpl
)document
).rememberEditorHighlighterForCachesOptimization(highlighter
);
671 if (myPanel
!= null) {
676 public EditorHighlighter
getHighlighter() {
677 assertIsDispatchThread();
678 return myHighlighter
;
682 public JComponent
getContentComponent() {
683 return myEditorComponent
;
686 public EditorGutterComponentEx
getGutterComponentEx() {
687 return myGutterComponent
;
690 public void addPropertyChangeListener(PropertyChangeListener listener
) {
691 myPropertyChangeSupport
.addPropertyChangeListener(listener
);
694 public void removePropertyChangeListener(PropertyChangeListener listener
) {
695 myPropertyChangeSupport
.removePropertyChangeListener(listener
);
698 public void setInsertMode(boolean mode
) {
699 assertIsDispatchThread();
700 boolean oldValue
= myIsInsertMode
;
701 myIsInsertMode
= mode
;
702 myPropertyChangeSupport
.firePropertyChange(PROP_INSERT_MODE
, oldValue
, mode
);
703 //Repaint the caret line by moving caret to the same place
704 LogicalPosition caretPosition
= getCaretModel().getLogicalPosition();
705 getCaretModel().moveToLogicalPosition(caretPosition
);
708 public boolean isInsertMode() {
709 return myIsInsertMode
;
712 public void setColumnMode(boolean mode
) {
713 assertIsDispatchThread();
714 boolean oldValue
= myIsColumnMode
;
715 myIsColumnMode
= mode
;
716 myPropertyChangeSupport
.firePropertyChange(PROP_COLUMN_MODE
, oldValue
, mode
);
719 public boolean isColumnMode() {
720 return myIsColumnMode
;
723 private int yPositionToVisibleLineNumber(int y
) {
724 return y
/ getLineHeight();
728 public VisualPosition
xyToVisualPosition(@NotNull Point p
) {
729 int line
= yPositionToVisibleLineNumber(p
.y
);
731 int offset
= logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line
, 0)));
732 int textLength
= myDocument
.getTextLength();
734 if (offset
>= textLength
) return new VisualPosition(line
, 0);
738 CharSequence text
= myDocument
.getCharsNoThreadCheck();
740 IterationState state
= new IterationState(this, offset
, false);
742 int fontType
= state
.getMergedAttributes().getFontType();
743 int spaceSize
= EditorUtil
.getSpaceWidth(fontType
, this);
748 if (offset
>= textLength
) break;
750 if (offset
>= state
.getEndOffset()) {
752 fontType
= state
.getMergedAttributes().getFontType();
755 FoldRegion region
= state
.getCurrentFold();
756 if (region
!= null) {
757 char[] placeholder
= region
.getPlaceholderText().toCharArray();
758 for (char aPlaceholder
: placeholder
) {
760 x
+= EditorUtil
.charWidth(c
, fontType
, this);
761 if (x
>= p
.x
) break outer
;
764 offset
= region
.getEndOffset();
768 c
= text
.charAt(offset
);
773 x
= EditorUtil
.nextTabStop(x
, this);
776 x
+= EditorUtil
.charWidth(c
, fontType
, this);
782 column
+= (x
- prevX
) / spaceSize
;
792 int charWidth
= EditorUtil
.charWidth(c
, fontType
, this);
794 if (x
>= p
.x
&& c
== '\t') {
795 if (mySettings
.isCaretInsideTabs()) {
796 column
+= (p
.x
- prevX
) / spaceSize
;
797 if ((p
.x
- prevX
) % spaceSize
> spaceSize
/ 2) column
++;
800 if ((x
- p
.x
) * 2 < x
- prevX
) {
801 column
+= (x
- prevX
) / spaceSize
;
807 if ((x
- p
.x
) * 2 < charWidth
) column
++;
810 column
+= (p
.x
- x
) / EditorUtil
.getSpaceWidth(fontType
, this);
814 return new VisualPosition(line
, column
);
818 public VisualPosition
offsetToVisualPosition(int offset
) {
819 return logicalToVisualPosition(offsetToLogicalPosition(offset
));
823 public LogicalPosition
offsetToLogicalPosition(int offset
) {
824 int line
= calcLogicalLineNumber(offset
);
825 int column
= calcColumnNumber(offset
, line
);
826 return new LogicalPosition(line
, column
);
830 public LogicalPosition
xyToLogicalPosition(@NotNull Point p
) {
832 if (p
.x
>= 0 && p
.y
>= 0) {
836 pp
= new Point(Math
.max(p
.x
, 0), Math
.max(p
.y
, 0));
839 return visualToLogicalPosition(xyToVisualPosition(pp
));
842 private int logicalLineToY(int line
) {
843 VisualPosition visible
= logicalToVisualPosition(new LogicalPosition(line
, 0));
844 return visibleLineNumberToYPosition(visible
.line
);
848 public Point
logicalPositionToXY(@NotNull LogicalPosition pos
) {
849 VisualPosition visible
= logicalToVisualPosition(pos
);
850 int y
= visibleLineNumberToYPosition(visible
.line
);
858 if (pos
.line
>= myDocument
.getLineCount()) {
859 lineStartOffset
= myDocument
.getTextLength();
862 lineStartOffset
= myDocument
.getLineStartOffset(pos
.line
);
866 int x
= getTabbedTextWidth(lineStartOffset
, visible
);
867 return new Point(x
, y
);
871 public Point
visualPositionToXY(@NotNull VisualPosition visible
) {
872 int y
= visibleLineNumberToYPosition(visible
.line
);
873 int logLine
= visualToLogicalPosition(new VisualPosition(visible
.line
, 0)).line
;
881 if (logLine
>= myDocument
.getLineCount()) {
882 lineStartOffset
= myDocument
.getTextLength();
885 lineStartOffset
= myDocument
.getLineStartOffset(logLine
);
889 int x
= getTabbedTextWidth(lineStartOffset
, visible
);
890 return new Point(x
, y
);
893 private int getTabbedTextWidth(int lineStartOffset
, VisualPosition pos
) {
894 if (pos
.column
== 0) return 0;
897 int offset
= lineStartOffset
;
898 CharSequence text
= myDocument
.getCharsNoThreadCheck();
899 int textLength
= myDocument
.getTextLength();
900 IterationState state
= new IterationState(this, offset
, false);
901 int fontType
= state
.getMergedAttributes().getFontType();
902 int spaceSize
= EditorUtil
.getSpaceWidth(fontType
, this);
906 while (column
< pos
.column
) {
907 if (offset
>= textLength
) break;
909 if (offset
>= state
.getEndOffset()) {
911 fontType
= state
.getMergedAttributes().getFontType();
914 FoldRegion region
= state
.getCurrentFold();
916 if (region
!= null) {
917 char[] placeholder
= region
.getPlaceholderText().toCharArray();
918 for (char aPlaceholder
: placeholder
) {
919 x
+= EditorUtil
.charWidth(aPlaceholder
, fontType
, this);
921 if (column
>= pos
.column
) break outer
;
923 offset
= region
.getEndOffset();
926 char c
= text
.charAt(offset
);
932 x
= EditorUtil
.nextTabStop(x
, this);
933 column
+= (x
- prevX
) / spaceSize
;
936 x
+= EditorUtil
.charWidth(c
, fontType
, this);
943 if (column
!= pos
.column
) {
944 x
+= EditorUtil
.getSpaceWidth(fontType
, this) * (pos
.column
- column
);
950 public int visibleLineNumberToYPosition(int lineNum
) {
951 if (lineNum
< 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum
);
952 return lineNum
* getLineHeight();
955 public void repaint(int startOffset
, int endOffset
) {
956 if (!isShowing() || myScrollPane
== null || myDocument
.isInBulkUpdate()) {
960 assertIsDispatchThread();
962 if (endOffset
> myDocument
.getTextLength()) {
963 endOffset
= myDocument
.getTextLength();
965 if (startOffset
< endOffset
) {
966 int startLine
= myDocument
.getLineNumber(startOffset
);
967 int endLine
= myDocument
.getLineNumber(endOffset
);
968 repaintLines(startLine
, endLine
);
972 private boolean isShowing() {
973 return myGutterComponent
!= null && myGutterComponent
.isShowing();
976 private void repaintToScreenBotton(int startLine
) {
977 Rectangle visibleRect
= getScrollingModel().getVisibleArea();
978 int yStartLine
= logicalLineToY(startLine
);
979 int yEndLine
= visibleRect
.y
+ visibleRect
.height
;
981 myEditorComponent
.repaintEditorComponent(visibleRect
.x
, yStartLine
, visibleRect
.x
+ visibleRect
.width
, yEndLine
- yStartLine
);
982 myGutterComponent
.repaint(0, yStartLine
, myGutterComponent
.getWidth(), yEndLine
- yStartLine
);
985 public void repaintLines(int startLine
, int endLine
) {
986 if (!isShowing()) return;
988 Rectangle visibleRect
= getScrollingModel().getVisibleArea();
989 int yStartLine
= logicalLineToY(startLine
);
990 int yEndLine
= logicalLineToY(endLine
) + getLineHeight() + WAVE_HEIGHT
;
992 myEditorComponent
.repaintEditorComponent(visibleRect
.x
, yStartLine
, visibleRect
.x
+ visibleRect
.width
, yEndLine
- yStartLine
);
993 myGutterComponent
.repaint(0, yStartLine
, myGutterComponent
.getWidth(), yEndLine
- yStartLine
);
996 private void beforeChangedUpdate(DocumentEvent e
) {
997 if (!myDocument
.isInBulkUpdate()) {
998 Rectangle viewRect
= getScrollingModel().getVisibleArea();
999 Point pos
= visualPositionToXY(getCaretModel().getVisualPosition());
1000 myCaretUpdateVShift
= pos
.y
- viewRect
.y
;
1002 mySizeContainer
.beforeChange(e
);
1005 private void changedUpdate(DocumentEvent e
) {
1006 if (myScrollPane
== null) return;
1008 stopOptimizedScrolling();
1009 mySelectionModel
.removeBlockSelection();
1011 mySizeContainer
.changedUpdate(e
);
1014 int startLine
= calcLogicalLineNumber(e
.getOffset());
1015 int endLine
= calcLogicalLineNumber(e
.getOffset() + e
.getNewLength());
1017 boolean painted
= false;
1018 if (myDocument
.getTextLength() > 0) {
1019 int startDocLine
= myDocument
.getLineNumber(e
.getOffset());
1020 int endDocLine
= myDocument
.getLineNumber(e
.getOffset() + e
.getNewLength());
1021 if (e
.getOldLength() > e
.getNewLength() || startDocLine
!= endDocLine
) {
1025 if (countLineFeeds(e
.getOldFragment()) != countLineFeeds(e
.getNewFragment())) {
1026 // Lines removed. Need to repaint till the end of the screen
1027 repaintToScreenBotton(startLine
);
1032 updateCaretCursor();
1034 repaintLines(startLine
, endLine
);
1037 if (!myDocument
.isInBulkUpdate()) {
1038 Point caretLocation
= visualPositionToXY(getCaretModel().getVisualPosition());
1039 int scrollOffset
= caretLocation
.y
- myCaretUpdateVShift
;
1040 getScrollingModel().scrollVertically(scrollOffset
);
1044 private static int countLineFeeds(CharSequence c
) {
1045 return StringUtil
.countNewLines(c
);
1048 private void updateGutterSize() {
1049 if (myGutterSizeUpdater
!= null) return;
1050 myGutterSizeUpdater
= new Runnable() {
1052 if (!isDisposed()) {
1054 myGutterComponent
.updateSize();
1055 myGutterNeedsUpdate
= false;
1058 myGutterNeedsUpdate
= true;
1061 myGutterSizeUpdater
= null;
1065 SwingUtilities
.invokeLater(myGutterSizeUpdater
);
1068 void validateSize() {
1069 Dimension dim
= getPreferredSize();
1071 if (!dim
.equals(myPreferredSize
) && !myDocument
.isInBulkUpdate()) {
1072 myPreferredSize
= dim
;
1074 stopOptimizedScrolling();
1075 int lineNum
= Math
.max(1, getDocument().getLineCount());
1076 myGutterComponent
.setLineNumberAreaWidth(getFontMetrics(Font
.PLAIN
).stringWidth(Integer
.toString(lineNum
+ 2)) + 6);
1078 myEditorComponent
.setSize(dim
);
1079 myEditorComponent
.fireResized();
1081 myMarkupModel
.repaint();
1085 void recalcSizeAndRepaint() {
1086 mySizeContainer
.reset();
1088 myEditorComponent
.repaintEditorComponent();
1092 public Document
getDocument() {
1097 public JComponent
getComponent() {
1101 public void addEditorMouseListener(@NotNull EditorMouseListener listener
) {
1102 myMouseListeners
.add(listener
);
1105 public void removeEditorMouseListener(@NotNull EditorMouseListener listener
) {
1106 boolean success
= myMouseListeners
.remove(listener
);
1107 LOG
.assertTrue(success
);
1110 public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener
) {
1111 myMouseMotionListeners
.add(listener
);
1114 public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener
) {
1115 boolean success
= myMouseMotionListeners
.remove(listener
);
1116 LOG
.assertTrue(success
);
1119 public boolean isDisposed() {
1123 void paint(Graphics g
) {
1124 startOptimizedScrolling();
1126 if (myCursorUpdater
!= null) {
1127 myCursorUpdater
.run();
1128 myCursorUpdater
= null;
1131 Rectangle clip
= getClipBounds(g
);
1137 Rectangle viewRect
= getScrollingModel().getVisibleArea();
1138 if (viewRect
== null) {
1143 g
.setColor(new Color(128, 255, 128));
1144 g
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);
1149 paintBackgrounds(g
, clip
);
1150 paintRectangularSelection(g
);
1151 paintRightMargin(g
, clip
);
1152 final MarkupModel docMarkup
= myDocument
.getMarkupModel(myProject
);
1153 paintLineMarkersSeparators(g
, clip
, docMarkup
);
1154 paintLineMarkersSeparators(g
, clip
, myMarkupModel
);
1156 paintSegmentHighlightersBorderAndAfterEndOfLine(g
, clip
);
1157 BorderEffect borderEffect
= new BorderEffect(this, g
);
1158 borderEffect
.paintHighlighters(getHighlighter());
1159 borderEffect
.paintHighlighters(docMarkup
.getAllHighlighters());
1160 borderEffect
.paintHighlighters(getMarkupModel().getAllHighlighters());
1161 paintCaretCursor(g
);
1163 paintComposedTextDecoration((Graphics2D
)g
);
1166 public void setHeaderComponent(JComponent header
) {
1167 myHeaderPanel
.removeAll();
1168 if (header
!= null) {
1169 myHeaderPanel
.add(header
);
1172 myHeaderPanel
.revalidate();
1175 public boolean hasHeaderComponent() {
1176 return myHeaderPanel
.getComponentCount() > 0;
1180 public JComponent
getHeaderComponent() {
1181 if (hasHeaderComponent()) {
1182 return (JComponent
)myHeaderPanel
.getComponent(0);
1187 public void setBackgroundColor(Color color
) {
1188 myForcedBackground
= color
;
1191 public void resetBackgourndColor() {
1192 myForcedBackground
= null;
1195 public Color
getForegroundColor() {
1196 return myScheme
.getDefaultForeground();
1199 public Color
getBackroundColor() {
1200 if (myForcedBackground
!= null) return myForcedBackground
;
1202 return getBackgroundIgnoreForced();
1205 private Color
getBackgroundColor(final TextAttributes attributes
) {
1206 final Color attrColor
= attributes
.getBackgroundColor();
1207 return attrColor
== myScheme
.getDefaultBackground() ?
getBackroundColor() : attrColor
;
1210 private Color
getBackgroundIgnoreForced() {
1211 Color color
= myScheme
.getDefaultBackground();
1212 if (myDocument
.isWritable()) {
1215 Color readOnlyColor
= myScheme
.getColor(EditorColors
.READONLY_BACKGROUND_COLOR
);
1216 return readOnlyColor
!= null ? readOnlyColor
: color
;
1219 private void paintComposedTextDecoration(Graphics2D g
) {
1220 if (myInputMethodRequestsHandler
!= null && myInputMethodRequestsHandler
.composedText
!= null) {
1221 VisualPosition visStart
=
1222 offsetToVisualPosition(Math
.min(myInputMethodRequestsHandler
.composedTextStart
, myDocument
.getTextLength()));
1223 int y
= visibleLineNumberToYPosition(visStart
.line
) + getLineHeight() - getDescent() + 1;
1224 Point p1
= visualPositionToXY(visStart
);
1226 logicalPositionToXY(offsetToLogicalPosition(Math
.min(myInputMethodRequestsHandler
.composedTextEnd
, myDocument
.getTextLength())));
1228 Stroke saved
= g
.getStroke();
1229 BasicStroke dotted
= new BasicStroke(1, BasicStroke
.CAP_ROUND
, BasicStroke
.JOIN_ROUND
, 0, new float[]{0, 2, 0, 2}, 0);
1230 g
.setStroke(dotted
);
1231 UIUtil
.drawLine(g
, p1
.x
, y
, p2
.x
, y
);
1236 private static Rectangle
getClipBounds(Graphics g
) {
1237 return g
.getClipBounds();
1240 private void paintRightMargin(Graphics g
, Rectangle clip
) {
1241 Color rightMargin
= myScheme
.getColor(EditorColors
.RIGHT_MARGIN_COLOR
);
1242 if (!mySettings
.isRightMarginShown() || rightMargin
== null) {
1245 int x
= mySettings
.getRightMargin(myProject
) * EditorUtil
.getSpaceWidth(Font
.PLAIN
, this);
1246 if (x
>= clip
.x
&& x
< clip
.x
+ clip
.width
) {
1247 g
.setColor(rightMargin
);
1248 UIUtil
.drawLine(g
, x
, clip
.y
, x
, clip
.y
+ clip
.height
);
1252 private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g
, Rectangle clip
) {
1253 if (myDocument
.getLineCount() == 0) return;
1254 int startLineNumber
= yPositionToVisibleLineNumber(clip
.y
);
1255 int endLineNumber
= yPositionToVisibleLineNumber(clip
.y
+ clip
.height
) + 1;
1257 final MarkupModel docMarkup
= myDocument
.getMarkupModel(myProject
);
1258 RangeHighlighter
[] segmentHighlighters
= docMarkup
.getAllHighlighters();
1259 for (RangeHighlighter segmentHighlighter
: segmentHighlighters
) {
1260 paintSegmentHighlighterAfterEndOfLine(g
, (RangeHighlighterEx
)segmentHighlighter
, startLineNumber
, endLineNumber
);
1263 segmentHighlighters
= getMarkupModel().getAllHighlighters();
1264 for (RangeHighlighter segmentHighlighter
: segmentHighlighters
) {
1265 paintSegmentHighlighterAfterEndOfLine(g
, (RangeHighlighterEx
)segmentHighlighter
, startLineNumber
, endLineNumber
);
1269 private void paintSegmentHighlighterAfterEndOfLine(Graphics g
,
1270 RangeHighlighterEx segmentHighlighter
,
1271 int startLineNumber
,
1272 int endLineNumber
) {
1273 if (!segmentHighlighter
.isValid()) {
1276 if (segmentHighlighter
.isAfterEndOfLine()) {
1277 int startOffset
= segmentHighlighter
.getStartOffset();
1278 int visibleStartLine
= offsetToVisualPosition(startOffset
).line
;
1280 if (!getFoldingModel().isOffsetCollapsed(startOffset
)) {
1281 if (visibleStartLine
>= startLineNumber
&& visibleStartLine
<= endLineNumber
) {
1282 int logStartLine
= offsetToLogicalPosition(startOffset
).line
;
1283 LogicalPosition logPosition
= offsetToLogicalPosition(myDocument
.getLineEndOffset(logStartLine
));
1284 Point end
= logicalPositionToXY(logPosition
);
1285 int charWidth
= EditorUtil
.getSpaceWidth(Font
.PLAIN
, this);
1286 int lineHeight
= getLineHeight();
1287 TextAttributes attributes
= segmentHighlighter
.getTextAttributes();
1288 if (attributes
!= null && getBackgroundColor(attributes
) != null) {
1289 g
.setColor(getBackgroundColor(attributes
));
1290 g
.fillRect(end
.x
, end
.y
, charWidth
, lineHeight
);
1292 if (attributes
!= null && attributes
.getEffectColor() != null) {
1293 int y
= visibleLineNumberToYPosition(visibleStartLine
) + getLineHeight() - getDescent() + 1;
1294 g
.setColor(attributes
.getEffectColor());
1295 if (attributes
.getEffectType() == EffectType
.WAVE_UNDERSCORE
) {
1296 drawWave(g
, end
.x
, end
.x
+ charWidth
- 1, y
);
1298 else if (attributes
.getEffectType() != EffectType
.BOXED
) {
1299 UIUtil
.drawLine(g
, end
.x
, y
, end
.x
+ charWidth
- 1, y
);
1307 public int getMaxWidthInRange(int startOffset
, int endOffset
) {
1309 VisualPosition start
= offsetToVisualPosition(startOffset
);
1310 VisualPosition end
= offsetToVisualPosition(endOffset
);
1312 for (int i
= start
.line
; i
<= end
.line
; i
++) {
1313 int lastColumn
= EditorUtil
.getLastVisualLineColumnNumber(this, i
) + 1;
1314 int lineWidth
= visualPositionToXY(new VisualPosition(i
, lastColumn
)).x
;
1316 if (lineWidth
> width
) {
1324 private void paintBackgrounds(Graphics g
, Rectangle clip
) {
1325 Color defaultBackground
= getBackroundColor();
1326 g
.setColor(defaultBackground
);
1327 g
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);
1329 int lineHeight
= getLineHeight();
1331 int visibleLineNumber
= clip
.y
/ lineHeight
;
1333 int startLineNumber
= xyToLogicalPosition(new Point(0, clip
.y
)).line
;
1335 if (startLineNumber
>= myDocument
.getLineCount() || startLineNumber
< 0) {
1339 int start
= myDocument
.getLineStartOffset(startLineNumber
);
1341 IterationState iterationState
= new IterationState(this, start
, paintSelection());
1343 LineIterator lIterator
= createLineIterator();
1344 lIterator
.start(start
);
1345 if (lIterator
.atEnd()) {
1349 myLastBackgroundPosition
= null;
1350 myLastBackgroundColor
= null;
1352 TextAttributes attributes
= iterationState
.getMergedAttributes();
1353 Color backColor
= getBackgroundColor(attributes
);
1354 Point position
= new Point(0, visibleLineNumber
* lineHeight
);
1355 int fontType
= attributes
.getFontType();
1356 CharSequence text
= myDocument
.getCharsNoThreadCheck();
1357 int lastLineIndex
= Math
.max(0, myDocument
.getLineCount() - 1);
1358 while (!iterationState
.atEnd() && !lIterator
.atEnd()) {
1359 int hEnd
= iterationState
.getEndOffset();
1360 int lEnd
= lIterator
.getEnd();
1363 FoldRegion collapsedFolderAt
= myFoldingModel
.getCollapsedRegionAtOffset(start
);
1364 if (collapsedFolderAt
== null) {
1365 position
.x
= drawBackground(g
, backColor
, text
.subSequence(start
, lEnd
- lIterator
.getSeparatorLength()), position
, fontType
,
1366 defaultBackground
, clip
);
1368 if (lIterator
.getLineNumber() < lastLineIndex
) {
1369 if (backColor
!= null && !backColor
.equals(defaultBackground
)) {
1370 g
.setColor(backColor
);
1371 g
.fillRect(position
.x
, position
.y
, clip
.x
+ clip
.width
- position
.x
, lineHeight
);
1375 paintAfterFileEndBackground(iterationState
, g
, position
, clip
, lineHeight
, defaultBackground
);
1380 if (position
.y
> clip
.y
+ clip
.height
) break;
1381 position
.y
+= lineHeight
;
1385 lIterator
.advance();
1388 FoldRegion collapsedFolderAt
= iterationState
.getCurrentFold();
1389 if (collapsedFolderAt
!= null) {
1390 position
.x
= drawBackground(g
, backColor
, collapsedFolderAt
.getPlaceholderText(), position
, fontType
, defaultBackground
, clip
);
1393 if (hEnd
> lEnd
- lIterator
.getSeparatorLength()) {
1394 position
.x
= drawBackground(g
, backColor
, text
.subSequence(start
, lEnd
- lIterator
.getSeparatorLength()), position
, fontType
,
1395 defaultBackground
, clip
);
1398 position
.x
= drawBackground(g
, backColor
, text
.subSequence(start
, hEnd
), position
, fontType
, defaultBackground
, clip
);
1402 iterationState
.advance();
1403 attributes
= iterationState
.getMergedAttributes();
1404 backColor
= getBackgroundColor(attributes
);
1405 fontType
= attributes
.getFontType();
1406 start
= iterationState
.getStartOffset();
1410 flushBackground(g
, clip
);
1412 if (lIterator
.getLineNumber() >= lastLineIndex
&& position
.y
<= clip
.y
+ clip
.height
) {
1413 paintAfterFileEndBackground(iterationState
, g
, position
, clip
, lineHeight
, defaultBackground
);
1417 private void paintRectangularSelection(Graphics g
) {
1418 final SelectionModel model
= getSelectionModel();
1419 if (!model
.hasBlockSelection()) return;
1420 final LogicalPosition blockStart
= model
.getBlockStart();
1421 final LogicalPosition blockEnd
= model
.getBlockEnd();
1422 assert blockStart
!= null;
1423 assert blockEnd
!= null;
1425 final Point start
= logicalPositionToXY(blockStart
);
1426 final Point end
= logicalPositionToXY(blockEnd
);
1427 g
.setColor(myScheme
.getColor(EditorColors
.SELECTION_BACKGROUND_COLOR
));
1430 if (start
.y
<= end
.y
) {
1432 height
= end
.y
- y
+ getLineHeight();
1436 height
= start
.y
- end
.y
+ getLineHeight();
1438 final int x
= Math
.min(start
.x
, end
.x
);
1439 final int width
= Math
.max(2, Math
.abs(end
.x
- start
.x
));
1440 g
.fillRect(x
, y
, width
, height
);
1443 private static void paintAfterFileEndBackground(IterationState iterationState
,
1448 final Color defaultBackground
) {
1449 Color backColor
= iterationState
.getPastFileEndBackground();
1450 if (backColor
!= null && !backColor
.equals(defaultBackground
)) {
1451 g
.setColor(backColor
);
1452 g
.fillRect(position
.x
, position
.y
, clip
.x
+ clip
.width
- position
.x
, lineHeight
);
1456 private int drawBackground(Graphics g
,
1461 Color defaultBackground
,
1463 int w
= getTextSegmentWidth(text
, position
.x
, fontType
, clip
);
1465 if (backColor
!= null && !backColor
.equals(defaultBackground
) && clip
.intersects(position
.x
, position
.y
, w
, getLineHeight())) {
1466 if (backColor
.equals(myLastBackgroundColor
) && myLastBackgroundPosition
.y
== position
.y
&&
1467 myLastBackgroundPosition
.x
+ myLastBackgroundWidth
== position
.x
) {
1468 myLastBackgroundWidth
+= w
;
1471 flushBackground(g
, clip
);
1472 myLastBackgroundColor
= backColor
;
1473 myLastBackgroundPosition
= new Point(position
);
1474 myLastBackgroundWidth
= w
;
1478 return position
.x
+ w
;
1481 private void flushBackground(Graphics g
, final Rectangle clip
) {
1482 if (myLastBackgroundColor
!= null) {
1483 final Point position
= myLastBackgroundPosition
;
1484 final int w
= myLastBackgroundWidth
;
1485 final int height
= getLineHeight();
1486 if (clip
.intersects(position
.x
, position
.y
, w
, height
)) {
1487 g
.setColor(myLastBackgroundColor
);
1488 g
.fillRect(position
.x
, position
.y
, w
, height
);
1490 myLastBackgroundColor
= null;
1494 private LineIterator
createLineIterator() {
1495 return myDocument
.createLineIterator();
1498 private void paintText(Graphics g
, Rectangle clip
) {
1500 final int plainSpaceWidth
= EditorUtil
.getSpaceWidth(Font
.PLAIN
, this);
1501 final int boldSpaceWidth
= EditorUtil
.getSpaceWidth(Font
.BOLD
, this);
1502 final int italicSpaceWidth
= EditorUtil
.getSpaceWidth(Font
.ITALIC
, this);
1503 final int boldItalicSpaceWidth
= EditorUtil
.getSpaceWidth(Font
.BOLD
| Font
.ITALIC
, this);
1504 mySpacesHaveSameWidth
=
1505 plainSpaceWidth
== boldSpaceWidth
&& plainSpaceWidth
== italicSpaceWidth
&& plainSpaceWidth
== boldItalicSpaceWidth
;
1507 int lineHeight
= getLineHeight();
1509 int visibleLineNumber
= clip
.y
/ lineHeight
;
1511 int startLineNumber
= xyToLogicalPosition(new Point(0, clip
.y
)).line
;
1513 if (startLineNumber
>= myDocument
.getLineCount() || startLineNumber
< 0) {
1517 int start
= myDocument
.getLineStartOffset(startLineNumber
);
1519 IterationState iterationState
= new IterationState(this, start
, paintSelection());
1521 LineIterator lIterator
= createLineIterator();
1522 lIterator
.start(start
);
1523 if (lIterator
.atEnd()) {
1527 TextAttributes attributes
= iterationState
.getMergedAttributes();
1528 Color currentColor
= attributes
.getForegroundColor();
1529 if (currentColor
== null) {
1530 currentColor
= getForegroundColor();
1532 Color effectColor
= attributes
.getEffectColor();
1533 EffectType effectType
= attributes
.getEffectType();
1534 int fontType
= attributes
.getFontType();
1535 myCurrentFontType
= null;
1536 g
.setColor(currentColor
);
1537 Point position
= new Point(0, visibleLineNumber
* lineHeight
);
1538 final char[] chars
= myDocument
.getRawChars();
1539 while (!iterationState
.atEnd() && !lIterator
.atEnd()) {
1540 int hEnd
= iterationState
.getEndOffset();
1541 int lEnd
= lIterator
.getEnd();
1543 FoldRegion collapsedFolderAt
= myFoldingModel
.getCollapsedRegionAtOffset(start
);
1544 if (collapsedFolderAt
== null) {
1545 drawString(g
, chars
, start
, lEnd
- lIterator
.getSeparatorLength(), position
, clip
, effectColor
, effectType
, fontType
,
1548 if (position
.y
> clip
.y
+ clip
.height
) break;
1549 position
.y
+= lineHeight
;
1553 // myBorderEffect.eolReached(g, this);
1554 lIterator
.advance();
1557 FoldRegion collapsedFolderAt
= iterationState
.getCurrentFold();
1558 if (collapsedFolderAt
!= null) {
1559 int foldingXStart
= position
.x
;
1561 drawString(g
, collapsedFolderAt
.getPlaceholderText(), position
, clip
, effectColor
, effectType
, fontType
, currentColor
);
1562 BorderEffect
.paintFoldedEffect(g
, foldingXStart
, position
.y
, position
.x
, getLineHeight(), effectColor
, effectType
);
1566 if (hEnd
> lEnd
- lIterator
.getSeparatorLength()) {
1567 position
.x
= drawString(g
, chars
, start
, lEnd
- lIterator
.getSeparatorLength(), position
, clip
, effectColor
, effectType
,
1568 fontType
, currentColor
);
1571 position
.x
= drawString(g
, chars
, start
, hEnd
, position
, clip
, effectColor
, effectType
, fontType
, currentColor
);
1575 iterationState
.advance();
1576 attributes
= iterationState
.getMergedAttributes();
1578 currentColor
= attributes
.getForegroundColor();
1579 if (currentColor
== null) {
1580 currentColor
= getForegroundColor();
1583 effectColor
= attributes
.getEffectColor();
1584 effectType
= attributes
.getEffectType();
1585 fontType
= attributes
.getFontType();
1587 start
= iterationState
.getStartOffset();
1591 FoldRegion collapsedFolderAt
= iterationState
.getCurrentFold();
1592 if (collapsedFolderAt
!= null) {
1593 int foldingXStart
= position
.x
;
1595 drawString(g
, collapsedFolderAt
.getPlaceholderText(), position
, clip
, effectColor
, effectType
, fontType
, currentColor
);
1596 BorderEffect
.paintFoldedEffect(g
, foldingXStart
, position
.y
, foldingXEnd
, getLineHeight(), effectColor
, effectType
);
1597 // myBorderEffect.collapsedFolderReached(g, this);
1600 flushCachedChars(g
);
1603 private boolean paintSelection() {
1604 return !isOneLineMode() || IJSwingUtilities
.hasFocus(getContentComponent());
1607 private class CachedFontContent
{
1608 final char[][] data
= new char[CACHED_CHARS_BUFFER_SIZE
][];
1609 final int[] starts
= new int[CACHED_CHARS_BUFFER_SIZE
];
1610 final int[] ends
= new int[CACHED_CHARS_BUFFER_SIZE
];
1611 final int[] x
= new int[CACHED_CHARS_BUFFER_SIZE
];
1612 final int[] y
= new int[CACHED_CHARS_BUFFER_SIZE
];
1613 final Color
[] color
= new Color
[CACHED_CHARS_BUFFER_SIZE
];
1616 final FontInfo myFontType
;
1618 private char[] myLastData
;
1620 private CachedFontContent(FontInfo fontInfo
) {
1621 myFontType
= fontInfo
;
1624 private void flushContent(Graphics g
) {
1626 if (myCurrentFontType
!= myFontType
) {
1627 myCurrentFontType
= myFontType
;
1628 g
.setFont(myFontType
.getFont());
1630 Color currentColor
= null;
1631 for (int i
= 0; i
< myCount
; i
++) {
1632 if (!Comparing
.equal(color
[i
], currentColor
)) {
1633 currentColor
= color
[i
];
1634 g
.setColor(currentColor
!= null ? currentColor
: Color
.black
);
1637 drawChars(g
, data
[i
], starts
[i
], ends
[i
], x
[i
], y
[i
]);
1647 private void addContent(Graphics g
, char[] _data
, int _start
, int _end
, int _x
, int _y
, Color _color
) {
1648 final int count
= myCount
;
1650 final int lastCount
= count
- 1;
1651 final Color lastColor
= color
[lastCount
];
1652 if (_data
== myLastData
&& _start
== ends
[lastCount
] && (_color
== null || lastColor
== null || _color
== lastColor
)) {
1653 ends
[lastCount
] = _end
;
1654 if (lastColor
== null) color
[lastCount
] = _color
;
1660 data
[count
] = _data
;
1663 starts
[count
] = _start
;
1665 color
[count
] = _color
;
1668 if (count
>= CACHED_CHARS_BUFFER_SIZE
- 1) {
1674 private void flushCachedChars(Graphics g
) {
1675 for (CachedFontContent cache
: myFontCache
) {
1676 cache
.flushContent(g
);
1681 private void paintCaretCursor(Graphics g
) {
1682 myCaretCursor
.paint(g
);
1685 private void paintLineMarkersSeparators(Graphics g
, Rectangle clip
, MarkupModel markupModel
) {
1686 if (markupModel
== null) return;
1687 RangeHighlighter
[] lineMarkers
= markupModel
.getAllHighlighters();
1688 for (RangeHighlighter lineMarker
: lineMarkers
) {
1689 paintLineMarkerSeparator(lineMarker
, clip
, g
);
1693 private void paintLineMarkerSeparator(RangeHighlighter marker
, Rectangle clip
, Graphics g
) {
1694 if (!marker
.isValid()) {
1697 Color separatorColor
= marker
.getLineSeparatorColor();
1698 if (separatorColor
!= null) {
1699 int lineNumber
= marker
.getLineSeparatorPlacement() == SeparatorPlacement
.TOP ? marker
.getDocument()
1700 .getLineNumber(marker
.getStartOffset()) : marker
.getDocument().getLineNumber(marker
.getEndOffset());
1701 if (lineNumber
< 0 || lineNumber
>= myDocument
.getLineCount()) {
1705 int y
= visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber
, 0)).line
);
1706 if (marker
.getLineSeparatorPlacement() != SeparatorPlacement
.TOP
) {
1707 y
+= getLineHeight();
1710 if (y
< clip
.y
|| y
> clip
.y
+ clip
.height
) return;
1712 int endShift
= clip
.x
+ clip
.width
;
1713 g
.setColor(separatorColor
);
1715 if (mySettings
.isRightMarginShown() && myScheme
.getColor(EditorColors
.RIGHT_MARGIN_COLOR
) != null) {
1716 endShift
= Math
.min(endShift
, mySettings
.getRightMargin(myProject
) * EditorUtil
.getSpaceWidth(Font
.PLAIN
, this));
1719 UIUtil
.drawLine(g
, 0, y
- 1, endShift
, y
- 1);
1723 private int drawString(Graphics g
,
1730 EffectType effectType
,
1733 if (start
>= end
) return position
.x
;
1735 boolean isInClip
= getLineHeight() + position
.y
>= clip
.y
&& position
.y
<= clip
.y
+ clip
.height
;
1737 if (!isInClip
) return position
.x
;
1739 int y
= getLineHeight() - getDescent() + position
.y
;
1741 return drawTabbedString(g
, text
, start
, end
, x
, y
, effectColor
, effectType
, fontType
, fontColor
, clip
);
1744 private int drawString(Graphics g
,
1749 EffectType effectType
,
1752 boolean isInClip
= getLineHeight() + position
.y
>= clip
.y
&& position
.y
<= clip
.y
+ clip
.height
;
1754 if (!isInClip
) return position
.x
;
1756 int y
= getLineHeight() - getDescent() + position
.y
;
1759 return drawTabbedString(g
, text
.toCharArray(), 0, text
.length(), x
, y
, effectColor
, effectType
, fontType
, fontColor
, clip
);
1762 private int drawTabbedString(Graphics g
,
1769 EffectType effectType
,
1772 final Rectangle clip
) {
1775 for (int i
= start
; i
< end
; i
++) {
1776 if (text
[i
] != '\t') continue;
1778 x
= drawTablessString(text
, start
, i
, g
, x
, y
, fontType
, fontColor
, clip
);
1780 int x1
= EditorUtil
.nextTabStop(x
, this);
1781 drawTabPlacer(g
, y
, x
, x1
);
1786 x
= drawTablessString(text
, start
, end
, g
, x
, y
, fontType
, fontColor
, clip
);
1788 if (effectColor
!= null) {
1789 final Color savedColor
= g
.getColor();
1791 // myBorderEffect.flushIfCantProlong(g, this, effectType, effectColor);
1793 if (xStart
< clip
.x
&& xEnd
< clip
.x
|| xStart
> clip
.x
+ clip
.width
&& xEnd
> clip
.x
+ clip
.width
) {
1797 if (xEnd
> clip
.x
+ clip
.width
) {
1798 xEnd
= clip
.x
+ clip
.width
;
1800 if (xStart
< clip
.x
) {
1804 if (effectType
== EffectType
.LINE_UNDERSCORE
) {
1805 g
.setColor(effectColor
);
1806 UIUtil
.drawLine(g
, xStart
, y
+ 1, xEnd
, y
+ 1);
1807 g
.setColor(savedColor
);
1809 else if (effectType
== EffectType
.BOLD_LINE_UNDERSCORE
) {
1810 g
.setColor(effectColor
);
1811 UIUtil
.drawLine(g
, xStart
, y
, xEnd
, y
);
1812 UIUtil
.drawLine(g
, xStart
, y
+ 1, xEnd
, y
+ 1);
1813 g
.setColor(savedColor
);
1815 else if (effectType
== EffectType
.STRIKEOUT
) {
1816 g
.setColor(effectColor
);
1817 int y1
= y
- getCharHeight() / 2;
1818 UIUtil
.drawLine(g
, xStart
, y1
, xEnd
, y1
);
1819 g
.setColor(savedColor
);
1821 else if (effectType
== EffectType
.WAVE_UNDERSCORE
) {
1822 g
.setColor(effectColor
);
1823 drawWave(g
, xStart
, xEnd
, y
+ 1);
1824 g
.setColor(savedColor
);
1831 private int drawTablessString(final char[] text
,
1838 final Color fontColor
,
1839 final Rectangle clip
) {
1842 FontInfo font
= EditorUtil
.fontForChar(text
[start
], fontType
, this);
1843 for (int j
= start
; j
< end
; j
++) {
1844 final char c
= text
[j
];
1845 FontInfo newFont
= EditorUtil
.fontForChar(c
, fontType
, this);
1846 if (font
!= newFont
|| endX
> clip
.x
+ clip
.width
) {
1847 if (!(x
< clip
.x
&& endX
< clip
.x
|| x
> clip
.x
+ clip
.width
&& endX
> clip
.x
+ clip
.width
)) {
1848 drawCharsCached(g
, text
, start
, j
, x
, y
, fontType
, fontColor
);
1854 if (x
< clip
.x
&& endX
< clip
.x
) {
1859 else if (x
> clip
.x
+ clip
.width
) {
1862 endX
+= font
.charWidth(c
, myEditorComponent
);
1865 if (!(x
< clip
.x
&& endX
< clip
.x
|| x
> clip
.x
+ clip
.width
&& endX
> clip
.x
+ clip
.width
)) {
1866 drawCharsCached(g
, text
, start
, end
, x
, y
, fontType
, fontColor
);
1873 private void drawTabPlacer(Graphics g
, int y
, int start
, int stop
) {
1874 if (mySettings
.isWhitespacesShown()) {
1875 stop
-= g
.getFontMetrics().charWidth(' ') / 2;
1876 Color oldColor
= g
.getColor();
1877 g
.setColor(myScheme
.getColor(EditorColors
.WHITESPACES_COLOR
));
1878 final int charHeight
= getCharHeight();
1879 final int halfCharHeight
= charHeight
/ 2;
1880 int mid
= y
- halfCharHeight
;
1881 int top
= y
- charHeight
;
1882 UIUtil
.drawLine(g
, start
, mid
, stop
, mid
);
1883 UIUtil
.drawLine(g
, stop
, y
, stop
, top
);
1884 g
.fillPolygon(new int[]{stop
- halfCharHeight
, stop
- halfCharHeight
, stop
}, new int[]{y
, y
- charHeight
, y
- halfCharHeight
}, 3);
1885 g
.setColor(oldColor
);
1889 private void drawCharsCached(Graphics g
, char[] data
, int start
, int end
, int x
, int y
, int fontType
, Color color
) {
1890 if (mySpacesHaveSameWidth
&& myLastCache
!= null && spacesOnly(data
, start
, end
)) {
1891 myLastCache
.addContent(g
, data
, start
, end
, x
, y
, null);
1894 FontInfo fnt
= EditorUtil
.fontForChar(data
[start
], fontType
, this);
1895 CachedFontContent cache
= null;
1896 for (CachedFontContent fontCache
: myFontCache
) {
1897 if (fontCache
.myFontType
== fnt
) {
1902 if (cache
== null) {
1903 cache
= new CachedFontContent(fnt
);
1904 myFontCache
.add(cache
);
1907 myLastCache
= cache
;
1908 cache
.addContent(g
, data
, start
, end
, x
, y
, color
);
1912 private static boolean spacesOnly(char[] chars
, int start
, int end
) {
1913 for (int i
= start
; i
< end
; i
++) {
1914 if (chars
[i
] != ' ') return false;
1919 private void drawChars(Graphics g
, char[] data
, int start
, int end
, int x
, int y
) {
1920 g
.drawChars(data
, start
, end
- start
, x
, y
);
1922 if (mySettings
.isWhitespacesShown()) {
1923 Color oldColor
= g
.getColor();
1924 g
.setColor(myScheme
.getColor(EditorColors
.WHITESPACES_COLOR
));
1925 final FontMetrics metrics
= g
.getFontMetrics();
1926 int halfSpaceWidth
= metrics
.charWidth(' ') / 2;
1927 for (int i
= start
; i
< end
; i
++) {
1928 if (data
[i
] == ' ') {
1929 g
.fillRect(x
+ halfSpaceWidth
, y
, 1, 1);
1931 x
+= metrics
.charWidth(data
[i
]);
1933 g
.setColor(oldColor
);
1937 private static final int WAVE_HEIGHT
= 2;
1938 private static final int WAVE_SEGMENT_LENGTH
= 4;
1940 private static void drawWave(Graphics g
, int xStart
, int xEnd
, int y
) {
1941 int startSegment
= xStart
/ WAVE_SEGMENT_LENGTH
;
1942 int endSegment
= xEnd
/ WAVE_SEGMENT_LENGTH
;
1943 for (int i
= startSegment
; i
< endSegment
; i
++) {
1944 drawWaveSegment(g
, WAVE_SEGMENT_LENGTH
* i
, y
);
1947 int x
= WAVE_SEGMENT_LENGTH
* endSegment
;
1948 UIUtil
.drawLine(g
, x
, y
+ WAVE_HEIGHT
, x
+ WAVE_SEGMENT_LENGTH
/ 2, y
);
1951 private static void drawWaveSegment(Graphics g
, int x
, int y
) {
1952 UIUtil
.drawLine(g
, x
, y
+ WAVE_HEIGHT
, x
+ WAVE_SEGMENT_LENGTH
/ 2, y
);
1953 UIUtil
.drawLine(g
, x
+ WAVE_SEGMENT_LENGTH
/ 2, y
, x
+ WAVE_SEGMENT_LENGTH
, y
+ WAVE_HEIGHT
);
1956 private int getTextSegmentWidth(CharSequence text
, int xStart
, int fontType
, Rectangle clip
) {
1959 final int textLength
= text
.length();
1960 for (int i
= 0; i
< textLength
&& xStart
< clip
.x
+ clip
.width
; i
++) {
1961 if (text
.charAt(i
) == '\t') {
1962 x
= EditorUtil
.nextTabStop(x
, this);
1965 x
+= EditorUtil
.charWidth(text
.charAt(i
), fontType
, this);
1967 if (x
> clip
.x
+ clip
.width
) {
1974 public int getLineHeight() {
1975 if (myLineHeight
!= -1) return myLineHeight
;
1979 FontMetrics fontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
1980 myLineHeight
= (int)(fontMetrics
.getHeight() * (isOneLineMode() ?
1 : myScheme
.getLineSpacing()));
1981 if (myLineHeight
== 0) {
1982 myLineHeight
= fontMetrics
.getHeight();
1983 if (myLineHeight
== 0) {
1988 return myLineHeight
;
1992 if (myDescent
!= -1) {
1995 FontMetrics fontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
1996 myDescent
= fontMetrics
.getDescent();
2000 FontMetrics
getFontMetrics(int fontType
) {
2001 if (myPlainFontMetrics
== null) {
2002 assertIsDispatchThread();
2003 myPlainFontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
2004 myBoldFontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.BOLD
));
2005 myItalicFontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.ITALIC
));
2006 myBoldItalicFontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.BOLD_ITALIC
));
2009 if (fontType
== Font
.PLAIN
) return myPlainFontMetrics
;
2010 if (fontType
== Font
.BOLD
) return myBoldFontMetrics
;
2011 if (fontType
== Font
.ITALIC
) return myItalicFontMetrics
;
2012 if (fontType
== Font
.BOLD
+ Font
.ITALIC
) return myBoldItalicFontMetrics
;
2014 LOG
.assertTrue(false, "Unknown font type: " + fontType
);
2016 return myPlainFontMetrics
;
2019 private int getCharHeight() {
2020 if (myCharHeight
== -1) {
2021 assertIsDispatchThread();
2022 FontMetrics fontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
2023 myCharHeight
= fontMetrics
.charWidth('a');
2025 return myCharHeight
;
2028 public Dimension
getPreferredSize() {
2029 if (ourIsUnitTestMode
&& getUserData(DO_DOCUMENT_UPDATE_TEST
) == null) {
2030 return new Dimension(1, 1);
2033 final Dimension draft
= getSizeWithoutCaret();
2034 final int additionalSpace
= mySettings
.getAdditionalColumnsCount() * EditorUtil
.getSpaceWidth(Font
.PLAIN
, this);
2036 if (!myDocument
.isInBulkUpdate()) {
2037 int caretX
= visualPositionToXY(getCaretModel().getVisualPosition()).x
;
2038 draft
.width
= Math
.max(caretX
, draft
.width
) + additionalSpace
;
2041 draft
.width
+= additionalSpace
;
2046 private Dimension
getSizeWithoutCaret() {
2047 Dimension size
= mySizeContainer
.getContentSize();
2048 if (isOneLineMode()) return new Dimension(size
.width
, getLineHeight());
2049 if (mySettings
.isAdditionalPageAtBottom()) {
2050 int lineHeight
= getLineHeight();
2051 return new Dimension(size
.width
, size
.height
+ Math
.max(getScrollingModel().getVisibleArea().height
- 2 * lineHeight
, lineHeight
));
2054 return getContentSize();
2057 public Dimension
getContentSize() {
2058 Dimension size
= mySizeContainer
.getContentSize();
2059 return new Dimension(size
.width
, size
.height
+ mySettings
.getAdditionalLinesCount() * getLineHeight());
2062 public JScrollPane
getScrollPane() {
2063 return myScrollPane
;
2066 public int logicalPositionToOffset(@NotNull LogicalPosition pos
) {
2068 if (myDocument
.getLineCount() == 0) return 0;
2070 if (pos
.line
< 0) throw new IndexOutOfBoundsException("Wrong line: " + pos
.line
);
2071 if (pos
.column
< 0) throw new IndexOutOfBoundsException("Wrong column:" + pos
.column
);
2073 if (pos
.line
>= myDocument
.getLineCount()) {
2074 return myDocument
.getTextLength();
2077 int start
= myDocument
.getLineStartOffset(pos
.line
);
2078 int end
= myDocument
.getLineEndOffset(pos
.line
);
2080 CharSequence text
= myDocument
.getCharsNoThreadCheck();
2082 if (pos
.column
== 0) return start
;
2083 return EditorUtil
.calcOffset(this, text
, start
, end
, pos
.column
, EditorUtil
.getTabSize(this));
2086 public void setLastColumnNumber(int val
) {
2087 assertIsDispatchThread();
2088 myLastColumnNumber
= val
;
2091 public int getLastColumnNumber() {
2093 return myLastColumnNumber
;
2096 int getVisibleLineCount() {
2097 int line
= getDocument().getLineCount();
2098 line
-= myFoldingModel
.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
2103 public VisualPosition
logicalToVisualPosition(@NotNull LogicalPosition logicalPos
) {
2105 if (!myFoldingModel
.isFoldingEnabled()) return new VisualPosition(logicalPos
.line
, logicalPos
.column
);
2107 int offset
= logicalPositionToOffset(logicalPos
);
2109 FoldRegion outermostCollapsed
= myFoldingModel
.getCollapsedRegionAtOffset(offset
);
2110 if (outermostCollapsed
!= null && offset
> outermostCollapsed
.getStartOffset()) {
2111 if (offset
< getDocument().getTextLength()) {
2112 offset
= outermostCollapsed
.getStartOffset();
2113 LogicalPosition foldStart
= offsetToLogicalPosition(offset
);
2114 return logicalToVisualPosition(foldStart
);
2117 offset
= outermostCollapsed
.getEndOffset() + 3; // WTF?
2121 int line
= logicalPos
.line
;
2122 int column
= logicalPos
.column
;
2124 line
-= myFoldingModel
.getFoldedLinesCountBefore(offset
);
2126 FoldRegion
[] toplevel
= myFoldingModel
.fetchTopLevel();
2127 for (int idx
= myFoldingModel
.getLastTopLevelIndexBefore(offset
); idx
>= 0; idx
--) {
2128 FoldRegion region
= toplevel
[idx
];
2129 if (region
.isValid()) {
2130 if (region
.getDocument().getLineNumber(region
.getEndOffset()) == logicalPos
.line
&& region
.getEndOffset() <= offset
) {
2131 LogicalPosition foldStart
= offsetToLogicalPosition(region
.getStartOffset());
2132 LogicalPosition foldEnd
= offsetToLogicalPosition(region
.getEndOffset());
2133 column
+= foldStart
.column
+ region
.getPlaceholderText().length() - foldEnd
.column
;
2134 offset
= region
.getStartOffset();
2135 logicalPos
= foldStart
;
2143 LOG
.assertTrue(line
>= 0);
2145 return new VisualPosition(line
, Math
.max(0, column
));
2149 private FoldRegion
getLastCollapsedBeforePosition(VisualPosition visual
) {
2150 FoldRegion
[] topLevelCollapsed
= myFoldingModel
.fetchTopLevel();
2152 if (topLevelCollapsed
== null) return null;
2155 int end
= topLevelCollapsed
.length
- 1;
2158 while (start
<= end
) {
2159 i
= (start
+ end
) / 2;
2160 FoldRegion region
= topLevelCollapsed
[i
];
2161 LogicalPosition logFoldEnd
= offsetToLogicalPosition(region
.getEndOffset() - 1);
2162 VisualPosition visFoldEnd
= logicalToVisualPosition(logFoldEnd
);
2163 if (visFoldEnd
.line
< visual
.line
) {
2167 if (visFoldEnd
.line
> visual
.line
) {
2171 if (visFoldEnd
.column
< visual
.column
) {
2175 if (visFoldEnd
.column
> visual
.column
) {
2187 while (i
>= 0 && i
< topLevelCollapsed
.length
) {
2188 if (topLevelCollapsed
[i
].isValid()) break;
2192 if (i
>= 0 && i
< topLevelCollapsed
.length
) {
2193 FoldRegion region
= topLevelCollapsed
[i
];
2194 LogicalPosition logFoldEnd
= offsetToLogicalPosition(region
.getEndOffset() - 1);
2195 VisualPosition visFoldEnd
= logicalToVisualPosition(logFoldEnd
);
2196 if (visFoldEnd
.line
> visual
.line
|| visFoldEnd
.line
== visual
.line
&& visFoldEnd
.column
> visual
.column
) {
2199 return topLevelCollapsed
[i
];
2212 public LogicalPosition
visualToLogicalPosition(@NotNull VisualPosition visiblePos
) {
2214 if (!myFoldingModel
.isFoldingEnabled()) return new LogicalPosition(visiblePos
.line
, visiblePos
.column
);
2216 int line
= visiblePos
.line
;
2217 int column
= visiblePos
.column
;
2219 FoldRegion lastCollapsedBefore
= getLastCollapsedBeforePosition(visiblePos
);
2221 if (lastCollapsedBefore
!= null) {
2222 LogicalPosition logFoldEnd
= offsetToLogicalPosition(lastCollapsedBefore
.getEndOffset());
2223 VisualPosition visFoldEnd
= logicalToVisualPosition(logFoldEnd
);
2225 line
= logFoldEnd
.line
+ (visiblePos
.line
- visFoldEnd
.line
);
2226 if (visFoldEnd
.line
== visiblePos
.line
) {
2227 if (visiblePos
.column
>= visFoldEnd
.column
) {
2228 column
= logFoldEnd
.column
+ (visiblePos
.column
- visFoldEnd
.column
);
2231 return offsetToLogicalPosition(lastCollapsedBefore
.getStartOffset());
2236 if (column
< 0) column
= 0;
2238 return new LogicalPosition(line
, column
);
2241 private int calcLogicalLineNumber(int offset
) {
2242 int textLength
= myDocument
.getTextLength();
2243 if (textLength
== 0) return 0;
2245 if (offset
> textLength
|| offset
< 0) {
2246 throw new IndexOutOfBoundsException("Wrong offset: " + offset
+ " textLength: " + textLength
);
2249 int lineIndex
= myDocument
.getLineNumber(offset
);
2251 LOG
.assertTrue(lineIndex
>= 0 && lineIndex
< myDocument
.getLineCount());
2256 private int calcColumnNumber(int offset
, int lineIndex
) {
2257 if (myDocument
.getTextLength() == 0) return 0;
2259 CharSequence text
= myDocument
.getCharsSequence();
2260 int start
= myDocument
.getLineStartOffset(lineIndex
);
2261 if (start
== offset
) return 0;
2262 return EditorUtil
.calcColumnNumber(this, text
, start
, offset
, EditorUtil
.getTabSize(this));
2265 private void moveCaretToScreenPos(int x
, int y
) {
2270 LogicalPosition pos
= xyToLogicalPosition(new Point(x
, y
));
2272 int columnNumber
= pos
.column
;
2273 int lineNumber
= pos
.line
;
2275 if (lineNumber
< 0) {
2280 final int totalLines
= myDocument
.getLineCount();
2281 if (totalLines
<= 0) {
2282 getCaretModel().moveToOffset(0);
2286 if (lineNumber
>= totalLines
) {
2287 moveCaretToScreenPos(x
, logicalLineToY(totalLines
- 1));
2291 if (!mySettings
.isVirtualSpace()) {
2292 int lineEndOffset
= myDocument
.getLineEndOffset(lineNumber
);
2293 int lineEndColumnNumber
= calcColumnNumber(lineEndOffset
, lineNumber
);
2294 if (columnNumber
> lineEndColumnNumber
) {
2295 columnNumber
= lineEndColumnNumber
;
2299 if (!mySettings
.isCaretInsideTabs()) {
2300 int offset
= logicalPositionToOffset(new LogicalPosition(lineNumber
, columnNumber
));
2301 CharSequence text
= myDocument
.getCharsSequence();
2302 if (offset
>= 0 && offset
< myDocument
.getTextLength()) {
2303 if (text
.charAt(offset
) == '\t') {
2304 columnNumber
= calcColumnNumber(offset
, lineNumber
);
2308 LogicalPosition pos1
= new LogicalPosition(lineNumber
, columnNumber
);
2309 getCaretModel().moveToLogicalPosition(pos1
);
2312 private boolean checkIgnore(MouseEvent e
, boolean isFinalCheck
) {
2313 if (!myIgnoreMouseEventsConsecutiveToInitial
) {
2314 myInitialMouseEvent
= null;
2318 if (e
.getComponent() != myInitialMouseEvent
.getComponent() || !e
.getPoint().equals(myInitialMouseEvent
.getPoint())) {
2319 myIgnoreMouseEventsConsecutiveToInitial
= false;
2320 myInitialMouseEvent
= null;
2325 myIgnoreMouseEventsConsecutiveToInitial
= false;
2326 myInitialMouseEvent
= null;
2334 private void processMouseReleased(MouseEvent e
) {
2335 if (checkIgnore(e
, true)) return;
2337 if (e
.getSource() == myGutterComponent
) {
2338 myGutterComponent
.mouseReleased(e
);
2341 if (getMouseEventArea(e
) != EditorMouseEventArea
.EDITING_AREA
|| e
.getY() < 0 || e
.getX() < 0) {
2345 // if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
2346 final FoldRegion region
= ((FoldingModelEx
)getFoldingModel()).getFoldingPlaceholderAt(e
.getPoint());
2347 if (e
.getX() >= 0 && e
.getY() >= 0 && region
!= null && region
== myMouseSelectedRegion
) {
2348 getFoldingModel().runBatchFoldingOperation(new Runnable() {
2350 myFoldingModel
.flushCaretShift();
2351 region
.setExpanded(true);
2356 if (myMousePressedEvent
!= null && myMousePressedEvent
.getClickCount() == 1 && myMousePressedInsideSelection
) {
2357 getSelectionModel().removeSelection();
2361 public DataContext
getDataContext() {
2362 return getProjectAwareDataContext(DataManager
.getInstance().getDataContext(getContentComponent()));
2365 private DataContext
getProjectAwareDataContext(final DataContext original
) {
2366 if (PlatformDataKeys
.PROJECT
.getData(original
) == myProject
) return original
;
2368 return new DataContext() {
2369 public Object
getData(String dataId
) {
2370 if (PlatformDataKeys
.PROJECT
.is(dataId
)) {
2373 return original
.getData(dataId
);
2379 public EditorMouseEventArea
getMouseEventArea(@NotNull MouseEvent e
) {
2380 if (myGutterComponent
!= e
.getSource()) return EditorMouseEventArea
.EDITING_AREA
;
2382 int x
= myGutterComponent
.convertX(e
.getX());
2384 if (x
>= myGutterComponent
.getLineNumberAreaOffset() &&
2385 x
< myGutterComponent
.getLineNumberAreaOffset() + myGutterComponent
.getLineNumberAreaWidth()) {
2386 return EditorMouseEventArea
.LINE_NUMBERS_AREA
;
2389 if (x
>= myGutterComponent
.getAnnotationsAreaOffset() &&
2390 x
<= myGutterComponent
.getAnnotationsAreaOffset() + myGutterComponent
.getAnnotationsAreaWidth()) {
2391 return EditorMouseEventArea
.ANNOTATIONS_AREA
;
2394 if (x
>= myGutterComponent
.getLineMarkerAreaOffset() &&
2395 x
< myGutterComponent
.getLineMarkerAreaOffset() + myGutterComponent
.getLineMarkerAreaWidth()) {
2396 return EditorMouseEventArea
.LINE_MARKERS_AREA
;
2399 if (x
>= myGutterComponent
.getFoldingAreaOffset() &&
2400 x
< myGutterComponent
.getFoldingAreaOffset() + myGutterComponent
.getFoldingAreaWidth()) {
2401 return EditorMouseEventArea
.FOLDING_OUTLINE_AREA
;
2407 private void requestFocus() {
2408 myEditorComponent
.requestFocus();
2411 private void validateMousePointer(MouseEvent e
) {
2412 if (e
.getSource() == myGutterComponent
) {
2413 FoldRegion foldingAtCursor
= myGutterComponent
.findFoldingAnchorAt(e
.getX(), e
.getY());
2414 myGutterComponent
.setActiveFoldRegion(foldingAtCursor
);
2415 if (foldingAtCursor
!= null) {
2416 myGutterComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
2419 myGutterComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.DEFAULT_CURSOR
));
2423 myGutterComponent
.setActiveFoldRegion(null);
2424 if (getSelectionModel().hasSelection() && (e
.getModifiersEx() & (InputEvent
.BUTTON1_DOWN_MASK
| InputEvent
.BUTTON2_DOWN_MASK
)) == 0) {
2425 int offset
= logicalPositionToOffset(xyToLogicalPosition(e
.getPoint()));
2426 if (getSelectionModel().getSelectionStart() <= offset
&& offset
< getSelectionModel().getSelectionEnd()) {
2427 myEditorComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.DEFAULT_CURSOR
));
2431 myEditorComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.TEXT_CURSOR
));
2435 private void runMouseDraggedCommand(final MouseEvent e
) {
2436 if (myCommandProcessor
== null || myMousePressedEvent
!= null && myMousePressedEvent
.isConsumed()) {
2439 myCommandProcessor
.executeCommand(myProject
, new Runnable() {
2441 processMouseDragged(e
);
2443 }, "", MOUSE_DRAGGED_GROUP
, UndoConfirmationPolicy
.DEFAULT
, getDocument());
2446 private void processMouseDragged(MouseEvent e
) {
2447 if (SwingUtilities
.isRightMouseButton(e
)) {
2450 Rectangle rect
= getScrollingModel().getVisibleArea();
2454 if (e
.getSource() == myGutterComponent
) {
2459 if (x
< rect
.x
&& rect
.x
> 0) {
2463 if (x
> rect
.x
+ rect
.width
) {
2464 dx
= x
- rect
.x
- rect
.width
;
2470 if (y
< rect
.y
&& rect
.y
> 0) {
2474 if (y
> rect
.y
+ rect
.height
) {
2475 dy
= y
- rect
.y
- rect
.height
;
2478 if (dx
== 0 && dy
== 0) {
2479 myScrollingTimer
.stop();
2481 SelectionModel selectionModel
= getSelectionModel();
2482 int oldSelectionStart
= selectionModel
.getLeadSelectionOffset();
2483 int oldCaretOffset
= getCaretModel().getOffset();
2484 LogicalPosition oldLogicalCaret
= getCaretModel().getLogicalPosition();
2485 moveCaretToScreenPos(x
, y
);
2486 getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
2488 int newCaretOffset
= getCaretModel().getOffset();
2489 int caretShift
= newCaretOffset
- mySavedSelectionStart
;
2491 if (myMousePressedEvent
!= null && getMouseEventArea(myMousePressedEvent
) != EditorMouseEventArea
.EDITING_AREA
&&
2492 getMouseEventArea(myMousePressedEvent
) != EditorMouseEventArea
.LINE_NUMBERS_AREA
) {
2493 selectionModel
.setSelection(oldSelectionStart
, newCaretOffset
);
2496 if (isColumnMode() || e
.isAltDown()) {
2497 final LogicalPosition blockStart
= selectionModel
.hasBlockSelection() ? selectionModel
.getBlockStart() : oldLogicalCaret
;
2498 selectionModel
.setBlockSelection(blockStart
, getCaretModel().getLogicalPosition());
2501 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE
) {
2502 if (caretShift
< 0) {
2503 int newSelection
= newCaretOffset
;
2504 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2505 newSelection
= mySelectionModel
.getWordAtCaretStart();
2508 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2510 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
, 0)));
2513 if (newSelection
< 0) newSelection
= newCaretOffset
;
2514 selectionModel
.setSelection(mySavedSelectionEnd
, newSelection
);
2515 getCaretModel().moveToOffset(newSelection
);
2518 int newSelection
= newCaretOffset
;
2519 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2520 newSelection
= mySelectionModel
.getWordAtCaretEnd();
2523 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2525 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
+ 1, 0)));
2528 if (newSelection
< 0) newSelection
= newCaretOffset
;
2529 selectionModel
.setSelection(mySavedSelectionStart
, newSelection
);
2530 getCaretModel().moveToOffset(newSelection
);
2535 if (!myMousePressedInsideSelection
) {
2536 selectionModel
.setSelection(oldSelectionStart
, newCaretOffset
);
2539 if (caretShift
!= 0) {
2540 if (myMousePressedEvent
!= null) {
2541 if (mySettings
.isDndEnabled()) {
2542 boolean isCopy
= UIUtil
.isControlKeyDown(e
) || isViewer() || !getDocument().isWritable();
2543 mySavedCaretOffsetForDNDUndoHack
= oldCaretOffset
;
2544 getContentComponent().getTransferHandler()
2545 .exportAsDrag(getContentComponent(), e
, isCopy ? TransferHandler
.COPY
: TransferHandler
.MOVE
);
2548 selectionModel
.removeSelection();
2550 myMousePressedEvent
= null;
2558 myScrollingTimer
.start(dx
, dy
);
2562 private static class RepaintCursorCommand
implements Runnable
{
2563 private long mySleepTime
= 500;
2564 private boolean myIsBlinkCaret
= true;
2565 private EditorImpl myEditor
= null;
2566 private final MyRepaintRunnable myRepaintRunnable
;
2567 private ScheduledFuture
<?
> mySchedulerHandle
;
2569 private RepaintCursorCommand() {
2570 myRepaintRunnable
= new MyRepaintRunnable();
2573 private class MyRepaintRunnable
implements Runnable
{
2575 if (myEditor
!= null) {
2576 myEditor
.myCaretCursor
.repaint();
2581 public void start() {
2582 if (mySchedulerHandle
!= null) {
2583 mySchedulerHandle
.cancel(false);
2585 mySchedulerHandle
= JobScheduler
.getScheduler().scheduleAtFixedRate(this, mySleepTime
, mySleepTime
, TimeUnit
.MILLISECONDS
);
2588 private void setBlinkPeriod(int blinkPeriod
) {
2589 mySleepTime
= blinkPeriod
> 10 ? blinkPeriod
: 10;
2593 private void setBlinkCaret(boolean value
) {
2594 myIsBlinkCaret
= value
;
2598 if (myEditor
!= null) {
2599 CaretCursor activeCursor
= myEditor
.myCaretCursor
;
2601 long time
= System
.currentTimeMillis();
2602 time
-= activeCursor
.myStartTime
;
2604 if (time
> mySleepTime
) {
2605 boolean toRepaint
= true;
2606 if (myIsBlinkCaret
) {
2607 activeCursor
.isVisible
= !activeCursor
.isVisible
;
2610 toRepaint
= !activeCursor
.isVisible
;
2611 activeCursor
.isVisible
= true;
2615 SwingUtilities
.invokeLater(myRepaintRunnable
);
2622 void updateCaretCursor() {
2623 if (!ourIsUnitTestMode
&& !IJSwingUtilities
.hasFocus(getContentComponent())) {
2624 stopOptimizedScrolling();
2627 if (myCursorUpdater
== null) {
2628 myCursorUpdater
= new Runnable() {
2630 if (myCursorUpdater
== null) return;
2631 myCursorUpdater
= null;
2632 VisualPosition caretPosition
= getCaretModel().getVisualPosition();
2633 Point pos1
= visualPositionToXY(caretPosition
);
2634 Point pos2
= visualPositionToXY(new VisualPosition(caretPosition
.line
, caretPosition
.column
+ 1));
2635 myCaretCursor
.setPosition(pos1
, pos2
.x
- pos1
.x
);
2641 public boolean setCaretVisible(boolean b
) {
2642 boolean old
= myCaretCursor
.isActive();
2644 myCaretCursor
.activate();
2647 myCaretCursor
.passivate();
2652 public void addFocusListener(FocusChangeListener listener
) {
2653 myFocusListeners
.add(listener
);
2656 public Project
getProject() {
2660 public boolean isOneLineMode() {
2661 return myIsOneLineMode
;
2664 public boolean isEmbeddedIntoDialogWrapper() {
2665 return myEmbeddedIntoDialogWrapper
;
2668 public void setEmbeddedIntoDialogWrapper(boolean b
) {
2669 assertIsDispatchThread();
2671 myEmbeddedIntoDialogWrapper
= b
;
2672 myScrollPane
.setFocusable(!b
);
2673 myEditorComponent
.setFocusCycleRoot(!b
);
2674 myEditorComponent
.setFocusable(b
);
2677 public void setOneLineMode(boolean isOneLineMode
) {
2678 myIsOneLineMode
= isOneLineMode
;
2679 getScrollPane().setInputMap(JComponent
.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
, null);
2683 public void stopOptimizedScrolling() {
2684 myEditorComponent
.setOpaque(false);
2687 private void startOptimizedScrolling() {
2688 myEditorComponent
.setOpaque(true);
2691 private class CaretCursor
{
2692 private Point myLocation
;
2693 private int myWidth
;
2695 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
2696 private boolean isVisible
= false;
2697 private long myStartTime
= 0;
2699 private CaretCursor() {
2700 myLocation
= new Point(0, 0);
2703 private void activate() {
2704 final boolean blink
= mySettings
.isBlinkCaret();
2705 final int blinkPeriod
= mySettings
.getCaretBlinkPeriod();
2706 synchronized (ourCaretBlinkingCommand
) {
2707 ourCaretBlinkingCommand
.myEditor
= EditorImpl
.this;
2708 ourCaretBlinkingCommand
.setBlinkCaret(blink
);
2709 ourCaretBlinkingCommand
.setBlinkPeriod(blinkPeriod
);
2714 public boolean isActive() {
2715 synchronized (ourCaretBlinkingCommand
) {
2720 private void passivate() {
2721 synchronized (ourCaretBlinkingCommand
) {
2726 private void setPosition(Point location
, int width
) {
2727 myStartTime
= System
.currentTimeMillis();
2728 myLocation
= location
;
2730 myWidth
= Math
.max(width
, 2);
2734 private void repaint() {
2735 myEditorComponent
.repaintEditorComponent(myLocation
.x
, myLocation
.y
, myWidth
, getLineHeight());
2738 private void paint(Graphics g
) {
2739 if (!isVisible
|| !IJSwingUtilities
.hasFocus(getContentComponent()) || isRendererMode()) return;
2741 int x
= myLocation
.x
;
2742 int lineHeight
= getLineHeight();
2743 int y
= myLocation
.y
;
2745 Rectangle viewRect
= getScrollingModel().getVisibleArea();
2746 if (x
- viewRect
.x
< 0) {
2751 g
.setColor(myScheme
.getColor(EditorColors
.CARET_COLOR
));
2753 if (myIsInsertMode
!= mySettings
.isBlockCursor()) {
2754 for (int i
= 0; i
< mySettings
.getLineCursorWidth(); i
++) {
2755 UIUtil
.drawLine(g
, x
+ i
, y
, x
+ i
, y
+ lineHeight
- 1);
2759 Color background
= myScheme
.getColor(EditorColors
.CARET_ROW_COLOR
);
2760 if (background
== null) background
= getBackroundColor();
2761 g
.setXORMode(background
);
2763 g
.fillRect(x
, y
, myWidth
, lineHeight
- 1);
2770 private class ScrollingTimer
{
2772 private static final int TIMER_PERIOD
= 100;
2773 private static final int CYCLE_SIZE
= 20;
2774 private int myXCycles
;
2775 private int myYCycles
;
2778 private int xPassedCycles
= 0;
2779 private int yPassedCycles
= 0;
2781 private void start(int dx
, int dy
) {
2785 myXCycles
= CYCLE_SIZE
/ dx
+ 1;
2786 myDx
= 1 + dx
/ CYCLE_SIZE
;
2790 myXCycles
= -CYCLE_SIZE
/ dx
+ 1;
2791 myDx
= -1 + dx
/ CYCLE_SIZE
;
2796 myYCycles
= CYCLE_SIZE
/ dy
+ 1;
2797 myDy
= 1 + dy
/ CYCLE_SIZE
;
2801 myYCycles
= -CYCLE_SIZE
/ dy
+ 1;
2802 myDy
= -1 + dy
/ CYCLE_SIZE
;
2806 if (myTimer
!= null) {
2811 myTimer
= new Timer(TIMER_PERIOD
, new ActionListener() {
2812 public void actionPerformed(ActionEvent e
) {
2813 myCommandProcessor
.executeCommand(myProject
, new DocumentRunnable(myDocument
, myProject
) {
2815 int oldSelectionStart
= mySelectionModel
.getLeadSelectionOffset();
2816 LogicalPosition caretPosition
= getCaretModel().getLogicalPosition();
2817 int columnNumber
= caretPosition
.column
;
2819 if (xPassedCycles
>= myXCycles
) {
2821 columnNumber
+= myDx
;
2824 int lineNumber
= caretPosition
.line
;
2826 if (yPassedCycles
>= myYCycles
) {
2831 LogicalPosition pos
= new LogicalPosition(lineNumber
, columnNumber
);
2832 getCaretModel().moveToLogicalPosition(pos
);
2833 getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
2835 int newCaretOffset
= getCaretModel().getOffset();
2836 int caretShift
= newCaretOffset
- mySavedSelectionStart
;
2838 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE
) {
2839 if (caretShift
< 0) {
2840 int newSelection
= newCaretOffset
;
2841 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2842 newSelection
= mySelectionModel
.getWordAtCaretStart();
2845 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2847 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
, 0)));
2850 if (newSelection
< 0) newSelection
= newCaretOffset
;
2851 mySelectionModel
.setSelection(validateOffset(mySavedSelectionEnd
), newSelection
);
2852 getCaretModel().moveToOffset(newSelection
);
2855 int newSelection
= newCaretOffset
;
2856 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2857 newSelection
= mySelectionModel
.getWordAtCaretEnd();
2860 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2861 newSelection
= logicalPositionToOffset(
2862 visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
+ 1, 0)));
2865 if (newSelection
< 0) newSelection
= newCaretOffset
;
2866 mySelectionModel
.setSelection(validateOffset(mySavedSelectionStart
), newSelection
);
2867 getCaretModel().moveToOffset(newSelection
);
2872 if (mySelectionModel
.hasBlockSelection()) {
2873 mySelectionModel
.setBlockSelection(mySelectionModel
.getBlockStart(), getCaretModel().getLogicalPosition());
2876 mySelectionModel
.setSelection(oldSelectionStart
, getCaretModel().getOffset());
2879 }, EditorBundle
.message("move.cursor.command.name"), DocCommandGroupId
.noneGroupId(getDocument()), UndoConfirmationPolicy
.DEFAULT
, getDocument());
2885 private void stop() {
2886 if (myTimer
!= null) {
2892 private int validateOffset(int offset
) {
2893 if (offset
< 0) return 0;
2894 if (offset
> myDocument
.getTextLength()) return myDocument
.getTextLength();
2899 class MyScrollBar
extends JScrollBar
{
2900 @NonNls private static final String DECR_BUTTON_FIELD
= "decrButton";
2901 @NonNls private static final String INCR_BUTTON_FIELD
= "incrButton";
2902 @NonNls private static final String APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS
= "apple.laf.AquaScrollBarUI";
2904 private MyScrollBar(int orientation
) {
2906 setFocusable(false);
2907 putClientProperty("JScrollBar.fastWheelScrolling", Boolean
.TRUE
); // fast scrolling for JDK 6
2911 * This is helper method. It returns height of the top (descrease) scrollbar
2912 * button. Please note, that it's possible to return real height only if scrollbar
2913 * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
2916 int getDecScrollButtonHeight() {
2917 ScrollBarUI barUI
= getUI();
2918 Insets insets
= getInsets();
2919 if (barUI
instanceof BasicScrollBarUI
) {
2921 Field decrButtonField
= BasicScrollBarUI
.class.getDeclaredField(DECR_BUTTON_FIELD
);
2922 decrButtonField
.setAccessible(true);
2923 JButton decrButtonValue
= (JButton
)decrButtonField
.get(barUI
);
2924 LOG
.assertTrue(decrButtonValue
!= null);
2925 return insets
.top
+ decrButtonValue
.getHeight();
2927 catch (Exception exc
) {
2928 throw new IllegalStateException(exc
.getMessage());
2932 return insets
.top
+ 15;
2937 * This is helper method. It returns height of the bottom (increase) scrollbar
2938 * button. Please note, that it's possible to return real height only if scrollbar
2939 * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
2942 int getIncScrollButtonHeight() {
2943 ScrollBarUI barUI
= getUI();
2944 Insets insets
= getInsets();
2945 if (barUI
instanceof BasicScrollBarUI
) {
2947 Field incrButtonField
= BasicScrollBarUI
.class.getDeclaredField(INCR_BUTTON_FIELD
);
2948 incrButtonField
.setAccessible(true);
2949 JButton incrButtonValue
= (JButton
)incrButtonField
.get(barUI
);
2950 LOG
.assertTrue(incrButtonValue
!= null);
2951 return insets
.bottom
+ incrButtonValue
.getHeight();
2953 catch (Exception exc
) {
2954 throw new IllegalStateException(exc
.getMessage());
2957 else if (APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS
.equals(barUI
.getClass().getName())) {
2958 return insets
.bottom
+ 30;
2961 return insets
.bottom
+ 15;
2965 public int getUnitIncrement(int direction
) {
2966 JViewport vp
= myScrollPane
.getViewport();
2967 Rectangle vr
= vp
.getViewRect();
2968 return myEditorComponent
.getScrollableUnitIncrement(vr
, SwingConstants
.VERTICAL
, direction
);
2971 public int getBlockIncrement(int direction
) {
2972 JViewport vp
= myScrollPane
.getViewport();
2973 Rectangle vr
= vp
.getViewRect();
2974 return myEditorComponent
.getScrollableBlockIncrement(vr
, SwingConstants
.VERTICAL
, direction
);
2978 private MyEditable
getViewer() {
2979 if (myEditable
== null) {
2980 myEditable
= new MyEditable();
2985 public CopyProvider
getCopyProvider() {
2989 public CutProvider
getCutProvider() {
2993 public PasteProvider
getPasteProvider() {
2998 public DeleteProvider
getDeleteProvider() {
3002 private class MyEditable
implements CutProvider
, CopyProvider
, PasteProvider
, DeleteProvider
{
3003 public void performCopy(DataContext dataContext
) {
3004 executeAction(IdeActions
.ACTION_EDITOR_COPY
, dataContext
);
3007 public boolean isCopyEnabled(DataContext dataContext
) {
3011 public boolean isCopyVisible(DataContext dataContext
) {
3012 return getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection();
3015 public void performCut(DataContext dataContext
) {
3016 executeAction(IdeActions
.ACTION_EDITOR_CUT
, dataContext
);
3019 public boolean isCutEnabled(DataContext dataContext
) {
3020 return !isViewer() && getDocument().isWritable();
3023 public boolean isCutVisible(DataContext dataContext
) {
3024 return getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection();
3027 public void performPaste(DataContext dataContext
) {
3028 executeAction(IdeActions
.ACTION_EDITOR_PASTE
, dataContext
);
3031 public boolean isPastePossible(DataContext dataContext
) {
3032 // Copy of isPasteEnabled. See interface method javadoc.
3033 return !isViewer() && getDocument().isWritable();
3036 public boolean isPasteEnabled(DataContext dataContext
) {
3037 return !isViewer() && getDocument().isWritable();
3040 public void deleteElement(DataContext dataContext
) {
3041 executeAction(IdeActions
.ACTION_EDITOR_DELETE
, dataContext
);
3044 public boolean canDeleteElement(DataContext dataContext
) {
3045 return !isViewer() && getDocument().isWritable();
3048 private void executeAction(String actionId
, DataContext dataContext
) {
3049 EditorAction action
= (EditorAction
)ActionManager
.getInstance().getAction(actionId
);
3050 if (action
!= null) {
3051 action
.actionPerformed(EditorImpl
.this, dataContext
);
3056 public void setColorsScheme(@NotNull EditorColorsScheme scheme
) {
3057 assertIsDispatchThread();
3063 public EditorColorsScheme
getColorsScheme() {
3068 void assertIsDispatchThread() {
3069 ApplicationManagerEx
.getApplicationEx().assertIsDispatchThread(myEditorComponent
);
3071 private static void assertReadAccess() {
3072 ApplicationManagerEx
.getApplicationEx().assertReadAccessAllowed();
3075 public void setVerticalScrollbarOrientation(int type
) {
3076 assertIsDispatchThread();
3077 int currentHorOffset
= myScrollingModel
.getHorizontalScrollOffset();
3078 myScrollbarOrientation
= type
;
3079 if (type
== VERTICAL_SCROLLBAR_LEFT
) {
3080 myScrollPane
.setLayout(new LeftHandScrollbarLayout());
3083 myScrollPane
.setLayout(new ScrollPaneLayout());
3085 myScrollingModel
.scrollHorizontally(currentHorOffset
);
3088 public void setVerticalScrollbarVisible(boolean b
) {
3090 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_ALWAYS
);
3093 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_NEVER
);
3097 public void setHorizontalScrollbarVisible(boolean b
) {
3099 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
3102 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_NEVER
);
3106 int getVerticalScrollbarOrientation() {
3107 return myScrollbarOrientation
;
3110 MyScrollBar
getVerticalScrollBar() {
3111 return myVerticalScrollBar
;
3118 private int getMouseSelectionState() {
3119 return myMouseSelectionState
;
3122 private void setMouseSelectionState(int mouseSelectionState
) {
3123 myMouseSelectionState
= mouseSelectionState
;
3124 myMouseSelectionChangeTimestamp
= System
.currentTimeMillis();
3128 void replaceInputMethodText(InputMethodEvent e
) {
3129 getInputMethodRequests();
3130 myInputMethodRequestsHandler
.replaceInputMethodText(e
);
3133 void inputMethodCaretPositionChanged(InputMethodEvent e
) {
3134 getInputMethodRequests();
3135 myInputMethodRequestsHandler
.setInputMethodCaretPosition(e
);
3138 InputMethodRequests
getInputMethodRequests() {
3139 if (myInputMethodRequestsHandler
== null) {
3140 myInputMethodRequestsHandler
= new MyInputMethodHandler();
3141 myInputMethodRequestsSwingWrapper
= new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler
);
3143 return myInputMethodRequestsSwingWrapper
;
3146 public boolean processKeyTyped(KeyEvent e
) {
3147 if (e
.getID() != KeyEvent
.KEY_TYPED
) return false;
3148 char c
= e
.getKeyChar();
3149 if (UIUtil
.isReallyTypedEvent(e
)) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
3158 void beforeModalityStateChanged() {
3159 myScrollingModel
.beforeModalityStateChanged();
3162 public EditorDropHandler
getDropHandler() {
3163 return myDropHandler
;
3166 public void setDropHandler(EditorDropHandler dropHandler
) {
3167 myDropHandler
= dropHandler
;
3170 private static class MyInputMethodHandleSwingThreadWrapper
implements InputMethodRequests
{
3171 private final InputMethodRequests myDelegate
;
3173 private MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate
) {
3174 myDelegate
= delegate
;
3177 public Rectangle
getTextLocation(final TextHitInfo offset
) {
3178 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getTextLocation(offset
);
3180 final Rectangle
[] r
= new Rectangle
[1];
3182 GuiUtils
.invokeAndWait(new Runnable() {
3184 r
[0] = myDelegate
.getTextLocation(offset
);
3188 catch (InterruptedException e
) {
3191 catch (InvocationTargetException e
) {
3197 public TextHitInfo
getLocationOffset(final int x
, final int y
) {
3198 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getLocationOffset(x
, y
);
3200 final TextHitInfo
[] r
= new TextHitInfo
[1];
3202 GuiUtils
.invokeAndWait(new Runnable() {
3204 r
[0] = myDelegate
.getLocationOffset(x
, y
);
3208 catch (InterruptedException e
) {
3211 catch (InvocationTargetException e
) {
3217 public int getInsertPositionOffset() {
3218 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getInsertPositionOffset();
3220 final int[] r
= new int[1];
3222 GuiUtils
.invokeAndWait(new Runnable() {
3224 r
[0] = myDelegate
.getInsertPositionOffset();
3228 catch (InterruptedException e
) {
3231 catch (InvocationTargetException e
) {
3237 public AttributedCharacterIterator
getCommittedText(final int beginIndex
,
3239 final AttributedCharacterIterator
.Attribute
[] attributes
) {
3240 if (ApplicationManager
.getApplication().isDispatchThread()) {
3241 return myDelegate
.getCommittedText(beginIndex
, endIndex
, attributes
);
3243 final AttributedCharacterIterator
[] r
= new AttributedCharacterIterator
[1];
3245 GuiUtils
.invokeAndWait(new Runnable() {
3247 r
[0] = myDelegate
.getCommittedText(beginIndex
, endIndex
, attributes
);
3251 catch (InterruptedException e
) {
3254 catch (InvocationTargetException e
) {
3260 public int getCommittedTextLength() {
3261 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getCommittedTextLength();
3262 final int[] r
= new int[1];
3264 GuiUtils
.invokeAndWait(new Runnable() {
3266 r
[0] = myDelegate
.getCommittedTextLength();
3270 catch (InterruptedException e
) {
3273 catch (InvocationTargetException e
) {
3279 public AttributedCharacterIterator
cancelLatestCommittedText(AttributedCharacterIterator
.Attribute
[] attributes
) {
3283 public AttributedCharacterIterator
getSelectedText(final AttributedCharacterIterator
.Attribute
[] attributes
) {
3284 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getSelectedText(attributes
);
3286 final AttributedCharacterIterator
[] r
= new AttributedCharacterIterator
[1];
3288 GuiUtils
.invokeAndWait(new Runnable() {
3290 r
[0] = myDelegate
.getSelectedText(attributes
);
3294 catch (InterruptedException e
) {
3297 catch (InvocationTargetException e
) {
3304 private class MyInputMethodHandler
implements InputMethodRequests
{
3305 private String composedText
;
3306 private int composedTextStart
;
3307 private int composedTextEnd
;
3309 public Rectangle
getTextLocation(TextHitInfo offset
) {
3310 Point caret
= logicalPositionToXY(getCaretModel().getLogicalPosition());
3311 Rectangle r
= new Rectangle(caret
, new Dimension(1, getLineHeight()));
3312 Point p
= getContentComponent().getLocationOnScreen();
3313 r
.translate(p
.x
, p
.y
);
3318 public TextHitInfo
getLocationOffset(int x
, int y
) {
3319 if (composedText
!= null) {
3320 Point p
= getContentComponent().getLocationOnScreen();
3323 int pos
= logicalPositionToOffset(xyToLogicalPosition(p
));
3324 if (pos
>= composedTextStart
&& pos
<= composedTextEnd
) {
3325 return TextHitInfo
.leading(pos
- composedTextStart
);
3331 public int getInsertPositionOffset() {
3332 int composedStartIndex
= 0;
3333 int composedEndIndex
= 0;
3334 if (composedText
!= null) {
3335 composedStartIndex
= composedTextStart
;
3336 composedEndIndex
= composedTextEnd
;
3339 int caretIndex
= getCaretModel().getOffset();
3341 if (caretIndex
< composedStartIndex
) {
3345 if (caretIndex
< composedEndIndex
) {
3346 return composedStartIndex
;
3349 return caretIndex
- (composedEndIndex
- composedStartIndex
);
3354 private String
getText(int startIdx
, int endIdx
) {
3355 CharSequence chars
= getDocument().getCharsSequence();
3356 return chars
.subSequence(startIdx
, endIdx
).toString();
3359 public AttributedCharacterIterator
getCommittedText(int beginIndex
, int endIndex
, AttributedCharacterIterator
.Attribute
[] attributes
) {
3360 int composedStartIndex
= 0;
3361 int composedEndIndex
= 0;
3362 if (composedText
!= null) {
3363 composedStartIndex
= composedTextStart
;
3364 composedEndIndex
= composedTextEnd
;
3368 if (beginIndex
< composedStartIndex
) {
3369 if (endIndex
<= composedStartIndex
) {
3370 committed
= getText(beginIndex
, endIndex
- beginIndex
);
3373 int firstPartLength
= composedStartIndex
- beginIndex
;
3374 committed
= getText(beginIndex
, firstPartLength
) + getText(composedEndIndex
, endIndex
- beginIndex
- firstPartLength
);
3378 committed
= getText(beginIndex
+ (composedEndIndex
- composedStartIndex
), endIndex
- beginIndex
);
3381 return new AttributedString(committed
).getIterator();
3384 public int getCommittedTextLength() {
3385 int length
= getDocument().getTextLength();
3386 if (composedText
!= null) {
3387 length
-= composedText
.length();
3392 public AttributedCharacterIterator
cancelLatestCommittedText(AttributedCharacterIterator
.Attribute
[] attributes
) {
3396 public AttributedCharacterIterator
getSelectedText(AttributedCharacterIterator
.Attribute
[] attributes
) {
3397 String text
= getSelectionModel().getSelectedText();
3398 return text
== null ?
null : new AttributedString(text
).getIterator();
3401 private void createComposedString(int composedIndex
, AttributedCharacterIterator text
) {
3402 StringBuffer strBuf
= new StringBuffer();
3404 // create attributed string with no attributes
3405 for (char c
= text
.setIndex(composedIndex
); c
!= CharacterIterator
.DONE
; c
= text
.next()) {
3409 composedText
= new String(strBuf
);
3412 private void setInputMethodCaretPosition(InputMethodEvent e
) {
3413 if (composedText
!= null) {
3414 int dot
= composedTextStart
;
3416 TextHitInfo caretPos
= e
.getCaret();
3417 if (caretPos
!= null) {
3418 dot
+= caretPos
.getInsertionIndex();
3421 getCaretModel().moveToOffset(dot
);
3422 getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
3426 private void runUndoTransparent(final Runnable runnable
) {
3427 CommandProcessor
.getInstance().runUndoTransparentAction(new Runnable() {
3429 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
3431 ApplicationManager
.getApplication().runWriteAction(runnable
);
3433 }, "", getDocument(), UndoConfirmationPolicy
.DEFAULT
, getDocument());
3438 private void replaceInputMethodText(InputMethodEvent e
) {
3439 int commitCount
= e
.getCommittedCharacterCount();
3440 AttributedCharacterIterator text
= e
.getText();
3442 // old composed text deletion
3443 final Document doc
= getDocument();
3445 if (composedText
!= null) {
3446 if (!isViewer() && doc
.isWritable()) {
3447 runUndoTransparent(new Runnable() {
3449 doc
.deleteString(Math
.max(0, composedTextStart
), Math
.min(composedTextEnd
, doc
.getTextLength()));
3453 composedText
= null;
3459 // committed text insertion
3460 if (commitCount
> 0) {
3461 //noinspection ForLoopThatDoesntUseLoopVariable
3462 for (char c
= text
.current(); commitCount
> 0; c
= text
.next(), commitCount
--) {
3463 if (c
>= 0x20 && c
!= 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
3469 // new composed text insertion
3470 if (!isViewer() && doc
.isWritable()) {
3471 int composedTextIndex
= text
.getIndex();
3472 if (composedTextIndex
< text
.getEndIndex()) {
3473 createComposedString(composedTextIndex
, text
);
3475 runUndoTransparent(new Runnable() {
3477 EditorModificationUtil
.insertStringAtCaret(EditorImpl
.this, composedText
, false, false);
3481 composedTextStart
= getCaretModel().getOffset();
3482 composedTextEnd
= getCaretModel().getOffset() + composedText
.length();
3489 private class MyMouseAdapter
extends MouseAdapter
{
3490 public void mousePressed(MouseEvent e
) {
3492 runMousePressedCommand(e
);
3495 public void mouseReleased(MouseEvent e
) {
3496 runMouseReleasedCommand(e
);
3497 if (!e
.isConsumed() && myMousePressedEvent
!= null && !myMousePressedEvent
.isConsumed() &&
3498 Math
.abs(e
.getX() - myMousePressedEvent
.getX()) < EditorUtil
.getSpaceWidth(Font
.PLAIN
, EditorImpl
.this) &&
3499 Math
.abs(e
.getY() - myMousePressedEvent
.getY()) < getLineHeight()) {
3500 runMouseClickedCommand(e
);
3502 myMousePressedEvent
= null;
3505 public void mouseEntered(MouseEvent e
) {
3506 runMouseEnteredCommand(e
);
3509 public void mouseExited(MouseEvent e
) {
3510 runMouseExitedCommand(e
);
3511 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3512 if (event
.getArea() == EditorMouseEventArea
.LINE_MARKERS_AREA
) {
3513 myGutterComponent
.mouseExited(e
);
3516 TooltipController
.getInstance().cancelTooltip(FOLDING_TOOLTIP_GROUP
);
3518 private void runMousePressedCommand(final MouseEvent e
) {
3519 myMousePressedEvent
= e
;
3520 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3522 for (EditorMouseListener mouseListener
: myMouseListeners
) {
3523 mouseListener
.mousePressed(event
);
3526 // On some systems (for example on Linux) popup trigger is MOUSE_PRESSED event.
3527 // But this trigger is always consumed by popup handler. In that case we have to
3529 if (event
.isConsumed() && !(event
.getMouseEvent().isPopupTrigger() || event
.getArea() == EditorMouseEventArea
.EDITING_AREA
)) {
3533 if (myCommandProcessor
!= null) {
3534 Runnable runnable
= new Runnable() {
3536 processMousePressed(e
);
3539 myCommandProcessor
.executeCommand(myProject
, runnable
, "", DocCommandGroupId
.noneGroupId(getDocument()), UndoConfirmationPolicy
.DEFAULT
, getDocument());
3542 processMousePressed(e
);
3546 private void runMouseClickedCommand(final MouseEvent e
) {
3547 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3548 for (EditorMouseListener listener
: myMouseListeners
) {
3549 listener
.mouseClicked(event
);
3550 if (event
.isConsumed()) {
3557 private void runMouseReleasedCommand(final MouseEvent e
) {
3558 myScrollingTimer
.stop();
3559 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3560 for (EditorMouseListener listener
: myMouseListeners
) {
3561 listener
.mouseReleased(event
);
3562 if (event
.isConsumed()) {
3568 if (myCommandProcessor
!= null) {
3569 Runnable runnable
= new Runnable() {
3571 processMouseReleased(e
);
3574 myCommandProcessor
.executeCommand(myProject
, runnable
, "", DocCommandGroupId
.noneGroupId(getDocument()), UndoConfirmationPolicy
.DEFAULT
, getDocument());
3577 processMouseReleased(e
);
3581 private void runMouseEnteredCommand(MouseEvent e
) {
3582 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3583 for (EditorMouseListener listener
: myMouseListeners
) {
3584 listener
.mouseEntered(event
);
3585 if (event
.isConsumed()) {
3592 private void runMouseExitedCommand(MouseEvent e
) {
3593 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3594 for (EditorMouseListener listener
: myMouseListeners
) {
3595 listener
.mouseExited(event
);
3596 if (event
.isConsumed()) {
3603 private void processMousePressed(MouseEvent e
) {
3604 myInitialMouseEvent
= e
;
3606 if (myMouseSelectionState
!= MOUSE_SELECTION_STATE_NONE
&& System
.currentTimeMillis() - myMouseSelectionChangeTimestamp
> 1000) {
3607 setMouseSelectionState(MOUSE_SELECTION_STATE_NONE
);
3616 final EditorMouseEventArea eventArea
= getMouseEventArea(e
);
3617 if (eventArea
== EditorMouseEventArea
.FOLDING_OUTLINE_AREA
) {
3618 final FoldRegion range
= myGutterComponent
.findFoldingAnchorAt(x
, y
);
3619 if (range
!= null) {
3620 final boolean expansion
= !range
.isExpanded();
3622 int scrollShift
= y
- getScrollingModel().getVerticalScrollOffset();
3623 Runnable processor
= new Runnable() {
3625 myFoldingModel
.flushCaretShift();
3626 range
.setExpanded(expansion
);
3629 getFoldingModel().runBatchFoldingOperation(processor
);
3630 y
= myGutterComponent
.getHeadCenterY(range
);
3631 getScrollingModel().scrollVertically(y
- scrollShift
);
3636 if (e
.getSource() == myGutterComponent
) {
3637 if (eventArea
== EditorMouseEventArea
.LINE_MARKERS_AREA
|| eventArea
== EditorMouseEventArea
.ANNOTATIONS_AREA
|| eventArea
== EditorMouseEventArea
.LINE_NUMBERS_AREA
) {
3638 myGutterComponent
.mousePressed(e
);
3639 if (e
.isConsumed()) return;
3644 int oldSelectionStart
= mySelectionModel
.getLeadSelectionOffset();
3645 moveCaretToScreenPos(x
, y
);
3647 if (e
.isPopupTrigger()) return;
3651 int caretOffset
= getCaretModel().getOffset();
3653 myMouseSelectedRegion
= myFoldingModel
.getFoldingPlaceholderAt(new Point(x
, y
));
3654 myMousePressedInsideSelection
= mySelectionModel
.hasSelection() && caretOffset
>= mySelectionModel
.getSelectionStart() &&
3655 caretOffset
<= mySelectionModel
.getSelectionEnd();
3657 if (!myMousePressedInsideSelection
&& mySelectionModel
.hasBlockSelection()) {
3658 int[] starts
= mySelectionModel
.getBlockSelectionStarts();
3659 int[] ends
= mySelectionModel
.getBlockSelectionEnds();
3660 for (int i
= 0; i
< starts
.length
; i
++) {
3661 if (caretOffset
>= starts
[i
] && caretOffset
< ends
[i
]) {
3662 myMousePressedInsideSelection
= true;
3668 if (getMouseEventArea(e
) == EditorMouseEventArea
.LINE_NUMBERS_AREA
&& e
.getClickCount() == 1) {
3669 mySelectionModel
.selectLineAtCaret();
3670 setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED
);
3671 mySavedSelectionStart
= mySelectionModel
.getSelectionStart();
3672 mySavedSelectionEnd
= mySelectionModel
.getSelectionEnd();
3676 if (e
.isShiftDown() && !e
.isControlDown() && !e
.isAltDown()) {
3677 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE
) {
3678 if (caretOffset
< mySavedSelectionStart
) {
3679 mySelectionModel
.setSelection(mySavedSelectionEnd
, caretOffset
);
3682 mySelectionModel
.setSelection(mySavedSelectionStart
, caretOffset
);
3686 mySelectionModel
.setSelection(oldSelectionStart
, caretOffset
);
3690 if (!myMousePressedInsideSelection
&& (getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection())) {
3691 setMouseSelectionState(MOUSE_SELECTION_STATE_NONE
);
3692 mySelectionModel
.setSelection(caretOffset
, caretOffset
);
3695 if (!e
.isPopupTrigger()) {
3696 switch (e
.getClickCount()) {
3698 mySelectionModel
.selectWordAtCaret(mySettings
.isMouseClickSelectionHonorsCamelWords());
3699 setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED
);
3700 mySavedSelectionStart
= mySelectionModel
.getSelectionStart();
3701 mySavedSelectionEnd
= mySelectionModel
.getSelectionEnd();
3702 getCaretModel().moveToOffset(mySavedSelectionEnd
);
3706 mySelectionModel
.selectLineAtCaret();
3707 setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED
);
3708 mySavedSelectionStart
= mySelectionModel
.getSelectionStart();
3709 mySavedSelectionEnd
= mySelectionModel
.getSelectionEnd();
3718 private static final TooltipGroup FOLDING_TOOLTIP_GROUP
= new TooltipGroup("FOLDING_TOOLTIP_GROUP", 10);
3720 private class MyMouseMotionListener
implements MouseMotionListener
{
3721 public void mouseDragged(MouseEvent e
) {
3722 validateMousePointer(e
);
3723 runMouseDraggedCommand(e
);
3724 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3725 if (event
.getArea() == EditorMouseEventArea
.LINE_MARKERS_AREA
) {
3726 myGutterComponent
.mouseDragged(e
);
3729 for (EditorMouseMotionListener listener
: myMouseMotionListeners
) {
3730 listener
.mouseDragged(event
);
3734 public void mouseMoved(MouseEvent e
) {
3735 validateMousePointer(e
);
3736 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3737 if (e
.getSource() == myGutterComponent
) {
3738 myGutterComponent
.mouseMoved(e
);
3741 if (event
.getArea() == EditorMouseEventArea
.EDITING_AREA
) {
3742 FoldRegion fold
= myFoldingModel
.getFoldingPlaceholderAt(e
.getPoint());
3743 TooltipController controller
= TooltipController
.getInstance();
3745 DocumentFragment range
= createDocumentFragment(fold
);
3747 SwingUtilities
.convertPoint((Component
)e
.getSource(), e
.getPoint(), getComponent().getRootPane().getLayeredPane());
3748 controller
.showTooltip(EditorImpl
.this, p
, new DocumentFragmentTooltipRenderer(range
), false, FOLDING_TOOLTIP_GROUP
);
3751 controller
.cancelTooltip(FOLDING_TOOLTIP_GROUP
);
3755 for (EditorMouseMotionListener listener
: myMouseMotionListeners
) {
3756 listener
.mouseMoved(event
);
3760 private DocumentFragment
createDocumentFragment(FoldRegion fold
) {
3761 final FoldingGroup group
= fold
.getGroup();
3762 final int foldStart
= fold
.getStartOffset();
3763 if (group
!= null) {
3764 final int endOffset
= myFoldingModel
.getEndOffset(group
);
3765 if (offsetToVisualPosition(endOffset
).line
== offsetToVisualPosition(foldStart
).line
) {
3766 return new DocumentFragment(myDocument
, foldStart
, endOffset
);
3770 final int oldEnd
= fold
.getEndOffset();
3771 return new DocumentFragment(myDocument
, foldStart
, oldEnd
);
3775 private class MyColorSchemeDelegate
implements EditorColorsScheme
{
3776 private final HashMap
<TextAttributesKey
, TextAttributes
> myOwnAttributes
= new HashMap
<TextAttributesKey
, TextAttributes
>();
3777 private final HashMap
<ColorKey
, Color
> myOwnColors
= new HashMap
<ColorKey
, Color
>();
3778 private Map
<EditorFontType
, Font
> myFontsMap
= null;
3779 private int myFontSize
= -1;
3780 private String myFaceName
= null;
3781 private EditorColorsScheme myGlobalScheme
;
3783 private MyColorSchemeDelegate() {
3784 updateGlobalScheme();
3787 private EditorColorsScheme
getGlobal() {
3788 return myGlobalScheme
;
3791 public String
getName() {
3792 return getGlobal().getName();
3796 protected void initFonts() {
3797 String editorFontName
= getEditorFontName();
3798 int editorFontSize
= getEditorFontSize();
3800 myFontsMap
= new EnumMap
<EditorFontType
, Font
>(EditorFontType
.class);
3802 Font plainFont
= new Font(editorFontName
, Font
.PLAIN
, editorFontSize
);
3803 Font boldFont
= new Font(editorFontName
, Font
.BOLD
, editorFontSize
);
3804 Font italicFont
= new Font(editorFontName
, Font
.ITALIC
, editorFontSize
);
3805 Font boldItalicFont
= new Font(editorFontName
, Font
.BOLD
+ Font
.ITALIC
, editorFontSize
);
3807 myFontsMap
.put(EditorFontType
.PLAIN
, plainFont
);
3808 myFontsMap
.put(EditorFontType
.BOLD
, boldFont
);
3809 myFontsMap
.put(EditorFontType
.ITALIC
, italicFont
);
3810 myFontsMap
.put(EditorFontType
.BOLD_ITALIC
, boldItalicFont
);
3815 public void setName(String name
) {
3816 getGlobal().setName(name
);
3819 public TextAttributes
getAttributes(TextAttributesKey key
) {
3820 if (myOwnAttributes
.containsKey(key
)) return myOwnAttributes
.get(key
);
3821 return getGlobal().getAttributes(key
);
3824 public void setAttributes(TextAttributesKey key
, TextAttributes attributes
) {
3825 myOwnAttributes
.put(key
, attributes
);
3828 public Color
getDefaultBackground() {
3829 return getGlobal().getDefaultBackground();
3832 public Color
getDefaultForeground() {
3833 return getGlobal().getDefaultForeground();
3836 public Color
getColor(ColorKey key
) {
3837 if (myOwnColors
.containsKey(key
)) return myOwnColors
.get(key
);
3838 return getGlobal().getColor(key
);
3841 public void setColor(ColorKey key
, Color color
) {
3842 myOwnColors
.put(key
, color
);
3844 // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
3845 // settings in this case.
3846 myCaretModel
.reinitSettings();
3847 mySelectionModel
.reinitSettings();
3850 public int getEditorFontSize() {
3851 if (myFontSize
== -1) {
3852 return getGlobal().getEditorFontSize();
3857 public void setEditorFontSize(int fontSize
) {
3858 if (fontSize
< 8) fontSize
= 8;
3859 if (fontSize
> 20) fontSize
= 20;
3860 myFontSize
= fontSize
;
3864 public String
getEditorFontName() {
3865 if (myFaceName
== null) {
3866 return getGlobal().getEditorFontName();
3871 public void setEditorFontName(String fontName
) {
3872 myFaceName
= fontName
;
3876 public Font
getFont(EditorFontType key
) {
3877 if (myFontsMap
!= null) {
3878 Font font
= myFontsMap
.get(key
);
3879 if (font
!= null) return font
;
3881 return getGlobal().getFont(key
);
3884 public void setFont(EditorFontType key
, Font font
) {
3885 if (myFontsMap
== null) {
3888 myFontsMap
.put(key
, font
);
3892 public float getLineSpacing() {
3893 return getGlobal().getLineSpacing();
3896 public void setLineSpacing(float lineSpacing
) {
3897 getGlobal().setLineSpacing(lineSpacing
);
3900 public Object
clone() {
3904 public void readExternal(Element element
) throws InvalidDataException
{
3907 public void writeExternal(Element element
) throws WriteExternalException
{
3910 public void updateGlobalScheme() {
3911 myGlobalScheme
= EditorColorsManager
.getInstance().getGlobalScheme();
3915 private static class MyTransferHandler
extends TransferHandler
{
3916 private RangeMarker myDraggedRange
= null;
3918 private static Editor
getEditor(JComponent comp
) {
3919 EditorComponentImpl editorComponent
= (EditorComponentImpl
)comp
;
3920 return editorComponent
.getEditor();
3923 public boolean importData(final JComponent comp
, final Transferable t
) {
3924 final EditorImpl editor
= (EditorImpl
)getEditor(comp
);
3926 final EditorDropHandler dropHandler
= editor
.getDropHandler();
3927 if (dropHandler
!= null && dropHandler
.canHandleDrop(t
.getTransferDataFlavors())) {
3928 dropHandler
.handleDrop(t
, editor
.getProject());
3932 final int caretOffset
= editor
.getCaretModel().getOffset();
3933 if (myDraggedRange
!= null && myDraggedRange
.getStartOffset() <= caretOffset
&& caretOffset
< myDraggedRange
.getEndOffset()) {
3937 if (myDraggedRange
!= null) {
3938 editor
.getCaretModel().moveToOffset(editor
.mySavedCaretOffsetForDNDUndoHack
);
3941 CommandProcessor
.getInstance().executeCommand(editor
.myProject
, new Runnable() {
3943 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
3946 editor
.getSelectionModel().removeSelection();
3948 if (myDraggedRange
!= null) {
3949 editor
.getCaretModel().moveToOffset(caretOffset
);
3950 offset
= caretOffset
;
3953 offset
= editor
.getCaretModel().getOffset();
3955 if (editor
.getDocument().getRangeGuard(offset
, offset
) != null) return;
3957 EditorActionHandler pasteHandler
= EditorActionManager
.getInstance().getActionHandler(IdeActions
.ACTION_EDITOR_PASTE
);
3958 Clipboard clipboard
= Toolkit
.getDefaultToolkit().getSystemClipboard();
3960 Transferable backup
= null;
3962 backup
= clipboard
.getContents(this);
3963 clipboard
.setContents(t
, EmptyClipboardOwner
.INSTANCE
);
3965 catch (Exception e
) {
3966 LOG
.info("Error communicating with system clipboard", e
);
3969 editor
.putUserData(LAST_PASTED_REGION
, null);
3970 pasteHandler
.execute(editor
, editor
.getDataContext());
3972 if (backup
!= null) {
3973 clipboard
.setContents(backup
, EmptyClipboardOwner
.INSTANCE
);
3976 catch (IllegalStateException e
) {
3980 TextRange range
= editor
.getUserData(LAST_PASTED_REGION
);
3981 if (range
!= null) {
3982 editor
.getCaretModel().moveToOffset(range
.getStartOffset());
3983 editor
.getSelectionModel().setSelection(range
.getStartOffset(), range
.getEndOffset());
3986 catch (Exception exception
) {
3987 LOG
.error(exception
);
3992 }, EditorBundle
.message("paste.command.name"), DND_COMMAND_KEY
, UndoConfirmationPolicy
.DEFAULT
, editor
.getDocument());
3997 public boolean canImport(JComponent comp
, DataFlavor
[] transferFlavors
) {
3998 Editor editor
= getEditor(comp
);
3999 final EditorDropHandler dropHandler
= ((EditorImpl
)editor
).getDropHandler();
4000 if (dropHandler
!= null && dropHandler
.canHandleDrop(transferFlavors
)) {
4003 if (editor
.isViewer()) return false;
4005 int offset
= editor
.getCaretModel().getOffset();
4006 if (editor
.getDocument().getRangeGuard(offset
, offset
) != null) return false;
4008 for (DataFlavor transferFlavor
: transferFlavors
) {
4009 if (transferFlavor
.equals(DataFlavor
.stringFlavor
)) return true;
4015 protected Transferable
createTransferable(JComponent c
) {
4016 Editor editor
= getEditor(c
);
4017 String s
= editor
.getSelectionModel().getSelectedText();
4018 if (s
== null) return null;
4019 int selectionStart
= editor
.getSelectionModel().getSelectionStart();
4020 int selectionEnd
= editor
.getSelectionModel().getSelectionEnd();
4021 myDraggedRange
= editor
.getDocument().createRangeMarker(selectionStart
, selectionEnd
);
4023 return new StringSelection(s
);
4026 public int getSourceActions(JComponent c
) {
4027 return COPY_OR_MOVE
;
4030 protected void exportDone(final JComponent source
, Transferable data
, int action
) {
4031 if (data
== null) return;
4033 final Component last
= DnDManager
.getInstance().getLastDropHandler();
4035 if (last
!= null && !(last
instanceof EditorComponentImpl
)) return;
4037 final Editor editor
= getEditor(source
);
4038 if (action
== MOVE
&& !editor
.isViewer()) {
4039 if (!FileDocumentManager
.getInstance().requestWriting(editor
.getDocument(), editor
.getProject())) {
4042 CommandProcessor
.getInstance().executeCommand(((EditorImpl
)editor
).myProject
, new Runnable() {
4044 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
4046 Document doc
= editor
.getDocument();
4047 doc
.startGuardedBlockChecking();
4049 doc
.deleteString(myDraggedRange
.getStartOffset(), myDraggedRange
.getEndOffset());
4051 catch (ReadOnlyFragmentModificationException e
) {
4052 EditorActionManager
.getInstance().getReadonlyFragmentModificationHandler(doc
).handle(e
);
4055 doc
.stopGuardedBlockChecking();
4060 }, EditorBundle
.message("move.selection.command.name"), DND_COMMAND_KEY
, UndoConfirmationPolicy
.DEFAULT
, editor
.getDocument());
4063 myDraggedRange
= null;
4067 class EditorDocumentAdapter
implements PrioritizedDocumentListener
{
4068 public void beforeDocumentChange(DocumentEvent e
) {
4069 beforeChangedUpdate(e
);
4072 public void documentChanged(DocumentEvent e
) {
4076 public int getPriority() {
4081 private class EditorSizeContainer
{
4082 private TIntArrayList myLineWidths
;
4083 private volatile boolean myIsDirty
;
4084 private int myOldEndLine
;
4085 private Dimension mySize
;
4086 private int myMaxWidth
= -1;
4088 public synchronized void reset() {
4089 int visLinesCount
= getVisibleLineCount();
4090 myLineWidths
= new TIntArrayList(visLinesCount
+ 300);
4091 int[] values
= new int[visLinesCount
];
4092 Arrays
.fill(values
, -1);
4093 myLineWidths
.add(values
);
4097 public synchronized void beforeChange(DocumentEvent e
) {
4098 if (myDocument
.isInBulkUpdate()) {
4099 myMaxWidth
= mySize
!= null ? mySize
.width
: -1;
4102 myOldEndLine
= getVisualPositionLine(e
.getOffset() + e
.getOldLength());
4105 private int getVisualPositionLine(int offset
) {
4106 // Do round up of offset to the nearest line start (valid since we need only line)
4107 // This is needed for preventing access to lexer editor highlighter regions [that are reset] during bulk mode operation
4108 final int startLineOffset
= myDocument
.getLineStartOffset(calcLogicalLineNumber(offset
));
4109 return offsetToVisualPosition(startLineOffset
).line
;
4112 public synchronized void changedUpdate(DocumentEvent e
) {
4113 int startLine
= e
.getOldLength() == 0 ? myOldEndLine
: getVisualPositionLine(e
.getOffset());
4114 int newEndLine
= e
.getNewLength() == 0 ? startLine
: getVisualPositionLine(e
.getOffset() + e
.getNewLength());
4115 int oldEndLine
= myOldEndLine
;
4117 final int lineWidthSize
= myLineWidths
.size();
4118 if (lineWidthSize
== 0) {
4122 final int min
= Math
.min(oldEndLine
, newEndLine
);
4123 final boolean toAddNewLines
= min
>= lineWidthSize
;
4125 if (toAddNewLines
) {
4126 final int[] delta
= new int[min
- lineWidthSize
+ 1];
4127 myLineWidths
.insert(lineWidthSize
, delta
);
4130 for (int i
= startLine
; i
<= min
; i
++) myLineWidths
.set(i
, -1);
4131 if (newEndLine
> oldEndLine
) {
4132 int[] delta
= new int[newEndLine
- oldEndLine
];
4133 Arrays
.fill(delta
, -1);
4134 myLineWidths
.insert(oldEndLine
+ 1, delta
);
4136 else if (oldEndLine
> newEndLine
&& !toAddNewLines
&& newEndLine
+ 1 < lineWidthSize
) {
4137 myLineWidths
.remove(newEndLine
+ 1, Math
.min(oldEndLine
, lineWidthSize
) - newEndLine
);
4143 private void validateSizes() {
4144 if (!myIsDirty
) return;
4146 synchronized (this) {
4147 if (!myIsDirty
) return;
4148 int lineCount
= myLineWidths
.size();
4150 if (myMaxWidth
!= -1 && myDocument
.isInBulkUpdate()) {
4151 mySize
= new Dimension(myMaxWidth
, getLineHeight() * lineCount
);
4156 final CharSequence text
= myDocument
.getCharsNoThreadCheck();
4157 int end
= myDocument
.getTextLength();
4159 final int fontSize
= myScheme
.getEditorFontSize();
4160 final String fontName
= myScheme
.getEditorFontName();
4162 for (int line
= 0; line
< lineCount
; line
++) {
4163 if (myLineWidths
.getQuick(line
) != -1) continue;
4165 int offset
= logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line
, 0)));
4167 if (offset
>= myDocument
.getTextLength()) {
4168 myLineWidths
.set(line
, 0);
4172 IterationState state
= new IterationState(EditorImpl
.this, offset
, false);
4173 int fontType
= state
.getMergedAttributes().getFontType();
4175 while (offset
< end
&& line
< lineCount
) {
4176 char c
= text
.charAt(offset
);
4177 if (offset
>= state
.getEndOffset()) {
4179 fontType
= state
.getMergedAttributes().getFontType();
4182 FoldRegion collapsed
= state
.getCurrentFold();
4183 if (collapsed
!= null) {
4184 String placeholder
= collapsed
.getPlaceholderText();
4185 for (int i
= 0; i
< placeholder
.length(); i
++) {
4186 x
+= EditorUtil
.charWidth(placeholder
.charAt(i
), fontType
, EditorImpl
.this);
4188 offset
= collapsed
.getEndOffset();
4192 x
= EditorUtil
.nextTabStop(x
, EditorImpl
.this);
4197 myLineWidths
.set(line
, x
);
4198 if (line
+ 1 >= lineCount
|| myLineWidths
.getQuick(line
+ 1) != -1) break;
4201 //noinspection AssignmentToForLoopParameter
4205 x
+= ComplementaryFontsRegistry
.getFontAbleToDisplay(c
, fontSize
, fontType
, fontName
).charWidth(c
, myEditorComponent
);
4213 if (lineCount
> 0) {
4214 myLineWidths
.set(lineCount
- 1,
4215 x
); // Last line can be non-zero length and won't be caught by in-loop procedure since latter only react on \n's
4219 for (int i
= 0; i
< lineCount
; i
++) {
4220 maxWidth
= Math
.max(maxWidth
, myLineWidths
.getQuick(i
));
4223 mySize
= new Dimension(maxWidth
, getLineHeight() * lineCount
);
4229 public Dimension
getContentSize() {
4236 public EditorGutter
getGutter() {
4237 return getGutterComponentEx();
4240 public int calcColumnNumber(CharSequence text
, int start
, int offset
, int tabSize
) {
4241 IterationState state
= new IterationState(this, start
, false);
4242 int fontType
= state
.getMergedAttributes().getFontType();
4245 int spaceSize
= EditorUtil
.getSpaceWidth(fontType
, this);
4246 for (int i
= start
; i
< offset
; i
++) {
4247 if (i
>= state
.getEndOffset()) {
4249 fontType
= state
.getMergedAttributes().getFontType();
4252 char c
= text
.charAt(i
);
4255 x
= EditorUtil
.nextTabStop(x
, this);
4256 column
+= (x
- prevX
) / spaceSize
;
4257 //column += Math.max(1, (x - prevX) / spaceSize);
4260 x
+= EditorUtil
.charWidth(c
, fontType
, this);
4268 public void putInfo(Map
<String
, String
> info
) {
4269 final VisualPosition visual
= getCaretModel().getVisualPosition();
4270 info
.put("caret", visual
.getLine() + ":" + visual
.getColumn());
4273 private class MyScrollPane
extends JScrollPane2
{
4274 protected void processMouseWheelEvent(MouseWheelEvent e
) {
4275 if (mySettings
.isWheelFontChangeEnabled()) {
4276 boolean changeFontSize
= SystemInfo
.isMac
4277 ?
!e
.isControlDown() && e
.isMetaDown() && !e
.isAltDown() && !e
.isShiftDown()
4278 : e
.isControlDown() && !e
.isMetaDown() && !e
.isAltDown() && !e
.isShiftDown();
4279 if (changeFontSize
) {
4280 setFontSize(myScheme
.getEditorFontSize() + e
.getWheelRotation());
4285 super.processMouseWheelEvent(e
);
4289 private class MyHeaderPanel
extends JPanel
{
4290 private int myOldHeight
= 0;
4292 private MyHeaderPanel() {
4293 super(new BorderLayout());
4296 public void revalidate() {
4297 myOldHeight
= getHeight();
4301 protected void validateTree() {
4302 int height
= myOldHeight
;
4303 super.validateTree();
4304 height
-= getHeight();
4307 myVerticalScrollBar
.setValue(myVerticalScrollBar
.getValue() - height
);
4309 myOldHeight
= getHeight();