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
;
222 private char[] myPrefixText
;
223 private TextAttributes myPrefixAttributes
;
226 ourCaretBlinkingCommand
= new RepaintCursorCommand();
227 ourCaretBlinkingCommand
.start();
231 public EditorImpl(Document document
, boolean viewer
, Project project
) {
233 myDocument
= (DocumentImpl
)document
;
234 myScheme
= new MyColorSchemeDelegate();
236 mySettings
= new SettingsImpl(this);
238 mySelectionModel
= new SelectionModelImpl(this);
239 myMarkupModel
= new EditorMarkupModelImpl(this);
240 myFoldingModel
= new FoldingModelImpl(this);
241 myCaretModel
= new CaretModelImpl(this);
242 mySizeContainer
.reset();
244 myCommandProcessor
= CommandProcessor
.getInstance();
246 myEditorDocumentAdapter
= new EditorDocumentAdapter();
247 myMouseMotionListeners
= ContainerUtil
.createEmptyCOWList();
249 myMarkupModelListener
= new MarkupModelListener() {
250 public void rangeHighlighterChanged(MarkupModelEvent event
) {
251 assertIsDispatchThread();
253 RangeHighlighterImpl rangeHighlighter
= (RangeHighlighterImpl
)event
.getHighlighter();
254 if (rangeHighlighter
.isValid()) {
255 int start
= rangeHighlighter
.getAffectedAreaStartOffset();
256 int end
= rangeHighlighter
.getAffectedAreaEndOffset();
257 int startLine
= myDocument
.getLineNumber(start
);
258 int endLine
= myDocument
.getLineNumber(end
);
259 repaintLines(Math
.max(0, startLine
- 1), Math
.min(endLine
+ 1, getDocument().getLineCount()));
262 repaint(0, getDocument().getTextLength());
264 ((EditorMarkupModelImpl
)getMarkupModel()).repaint();
265 ((EditorMarkupModelImpl
)getMarkupModel()).markDirtied();
266 GutterIconRenderer renderer
= rangeHighlighter
.getGutterIconRenderer();
267 if (renderer
!= null) {
274 ((MarkupModelEx
)myDocument
.getMarkupModel(myProject
)).addMarkupModelListener(myMarkupModelListener
);
275 ((MarkupModelEx
)getMarkupModel()).addMarkupModelListener(myMarkupModelListener
);
277 myDocument
.addDocumentListener(myFoldingModel
);
278 myDocument
.addDocumentListener(myCaretModel
);
279 myDocument
.addDocumentListener(mySelectionModel
);
280 myDocument
.addDocumentListener(myEditorDocumentAdapter
);
282 myCaretCursor
= new CaretCursor();
284 myFoldingModel
.flushCaretShift();
285 myScrollbarOrientation
= VERTICAL_SCROLLBAR_RIGHT
;
287 EditorHighlighter highlighter
= new EmptyEditorHighlighter(myScheme
.getAttributes(HighlighterColors
.TEXT
));
288 setHighlighter(highlighter
);
290 myEditorComponent
= new EditorComponentImpl(this);
291 myScrollPane
= new MyScrollPane();
292 myPanel
= new JPanel() {
293 public void addNotify() {
295 if (((JComponent
)getParent()).getBorder() != null) myScrollPane
.setBorder(null);
299 myHeaderPanel
= new MyHeaderPanel();
300 myVerticalScrollBar
= new MyScrollBar(Adjustable
.VERTICAL
);
301 myGutterComponent
= new EditorGutterComponentImpl(this);
304 myScrollingModel
= new ScrollingModelImpl(this);
306 myGutterComponent
.updateSize();
307 Dimension preferredSize
= getPreferredSize();
308 myEditorComponent
.setSize(preferredSize
);
310 if (Patches
.APPLE_BUG_ID_3716835
) {
311 myScrollingModel
.addVisibleAreaListener(new VisibleAreaListener() {
312 public void visibleAreaChanged(VisibleAreaEvent e
) {
313 if (myAppleRepaintAlarm
== null) {
314 myAppleRepaintAlarm
= new Alarm(Alarm
.ThreadToUse
.SWING_THREAD
);
316 myAppleRepaintAlarm
.cancelAllRequests();
317 myAppleRepaintAlarm
.addRequest(new Runnable() {
319 repaint(0, myDocument
.getTextLength());
321 }, 50, ModalityState
.stateForComponent(myEditorComponent
));
328 // This hacks context layout problem where editor appears scrolled to the right just after it is created.
329 if (!ourIsUnitTestMode
) {
330 UiNotifyConnector
.doWhenFirstShown(myEditorComponent
, new Runnable() {
332 if (!isDisposed() && !myScrollingModel
.isScrollingNow()) {
333 myScrollingModel
.disableAnimation();
334 myScrollingModel
.scrollHorizontally(0);
335 myScrollingModel
.enableAnimation();
342 public void setPrefixTextAndAttributes(String prefixText
, TextAttributes attributes
) {
343 myPrefixText
= prefixText
== null?
null: prefixText
.toCharArray();
344 myPrefixAttributes
= attributes
;
347 public boolean isViewer() {
348 return myIsViewer
|| myIsRendererMode
;
351 public boolean isRendererMode() {
352 return myIsRendererMode
;
355 public void setRendererMode(boolean isRendererMode
) {
356 myIsRendererMode
= isRendererMode
;
359 public void setFile(VirtualFile vFile
) {
360 myVirtualFile
= vFile
;
364 public VirtualFile
getVirtualFile() {
365 return myVirtualFile
;
369 public SelectionModel
getSelectionModel() {
370 return mySelectionModel
;
374 public MarkupModel
getMarkupModel() {
375 return myMarkupModel
;
379 public FoldingModel
getFoldingModel() {
380 return myFoldingModel
;
384 public CaretModel
getCaretModel() {
389 public ScrollingModel
getScrollingModel() {
390 return myScrollingModel
;
394 public EditorSettings
getSettings() {
399 public void reinitSettings() {
400 assertIsDispatchThread();
404 myPlainFontMetrics
= null;
406 myCaretModel
.reinitSettings();
407 mySelectionModel
.reinitSettings();
408 mySettings
.reinitSettings();
409 ourCaretBlinkingCommand
.setBlinkCaret(mySettings
.isBlinkCaret());
410 ourCaretBlinkingCommand
.setBlinkPeriod(mySettings
.getCaretBlinkPeriod());
411 mySizeContainer
.reset();
412 myFoldingModel
.refreshSettings();
413 myFoldingModel
.rebuild();
415 if (myScheme
instanceof MyColorSchemeDelegate
) {
416 ((MyColorSchemeDelegate
)myScheme
).updateGlobalScheme();
418 myHighlighter
.setColorScheme(myScheme
);
420 myGutterComponent
.reinitSettings();
421 myGutterComponent
.revalidate();
423 myEditorComponent
.repaint();
427 if (myInitialMouseEvent
!= null) {
428 myIgnoreMouseEventsConsecutiveToInitial
= true;
432 public void release() {
434 LOG
.error("Double release. First released at: =====\n" + myReleasedAt
+"\n======");
437 myReleasedAt
= StringUtil
.getThrowableText(new Throwable());
440 myDocument
.removeDocumentListener(myHighlighter
);
441 myDocument
.removeDocumentListener(myEditorDocumentAdapter
);
442 myDocument
.removeDocumentListener(myFoldingModel
);
443 myDocument
.removeDocumentListener(myCaretModel
);
444 myDocument
.removeDocumentListener(mySelectionModel
);
446 MarkupModelEx markupModel
= (MarkupModelEx
)myDocument
.getMarkupModel(myProject
, false);
447 if (markupModel
instanceof MarkupModelImpl
) {
448 markupModel
.removeMarkupModelListener(myMarkupModelListener
);
451 myMarkupModel
.dispose();
456 myPlainFontMetrics
= null;
457 myScrollingModel
.dispose();
458 myGutterComponent
.dispose();
460 //myFoldingModel.dispose(); TODO rangemarker tree
463 private void clearCaretThread() {
464 synchronized (ourCaretBlinkingCommand
) {
465 if (ourCaretBlinkingCommand
.myEditor
== this) {
466 ourCaretBlinkingCommand
.myEditor
= null;
471 private void initComponent() {
472 // myStatusBar = new EditorStatusBarImpl();
474 //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
475 myPanel
.setLayout(new BorderLayout());
477 myPanel
.add(myHeaderPanel
, BorderLayout
.NORTH
);
479 myGutterComponent
.setOpaque(true);
481 myScrollPane
.setVerticalScrollBar(myVerticalScrollBar
);
482 final MyScrollBar horizontalScrollBar
= new MyScrollBar(Adjustable
.HORIZONTAL
);
483 myScrollPane
.setHorizontalScrollBar(horizontalScrollBar
);
484 myScrollPane
.setViewportView(myEditorComponent
);
485 //myScrollPane.setBorder(null);
486 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_ALWAYS
);
487 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
490 myScrollPane
.setRowHeaderView(myGutterComponent
);
491 stopOptimizedScrolling();
493 myEditorComponent
.setTransferHandler(new MyTransferHandler());
494 myEditorComponent
.setAutoscrolls(true);
496 /* Default mode till 1.4.0
497 * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
500 if (mayShowToolbar()) {
501 JLayeredPane layeredPane
= new JLayeredPane() {
503 public void doLayout() {
504 final Component
[] components
= getComponents();
505 final Rectangle r
= getBounds();
506 for (Component c
: components
) {
507 if (c
instanceof JScrollPane
) {
508 c
.setBounds(0, 0, r
.width
, r
.height
);
511 final Dimension d
= c
.getPreferredSize();
512 final MyScrollBar scrollBar
= getVerticalScrollBar();
513 c
.setBounds(r
.width
- d
.width
- scrollBar
.getWidth() - 30, 20, d
.width
, d
.height
);
519 layeredPane
.add(myScrollPane
, JLayeredPane
.DEFAULT_LAYER
);
520 myPanel
.add(layeredPane
);
522 new ContextMenuImpl(layeredPane
, myScrollPane
, this);
525 myPanel
.add(myScrollPane
);
528 //myPanel.add(myScrollPane);
530 myEditorComponent
.addKeyListener(new KeyAdapter() {
531 public void keyTyped(KeyEvent event
) {
532 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
533 if (event
.isConsumed()) {
536 if (processKeyTyped(event
)) {
542 MyMouseAdapter mouseAdapter
= new MyMouseAdapter();
543 myEditorComponent
.addMouseListener(mouseAdapter
);
544 myGutterComponent
.addMouseListener(mouseAdapter
);
546 MyMouseMotionListener mouseMotionListener
= new MyMouseMotionListener();
547 myEditorComponent
.addMouseMotionListener(mouseMotionListener
);
548 myGutterComponent
.addMouseMotionListener(mouseMotionListener
);
550 myEditorComponent
.addFocusListener(new FocusAdapter() {
551 public void focusGained(FocusEvent e
) {
552 myCaretCursor
.activate(false);
553 int caretLine
= getCaretModel().getLogicalPosition().line
;
554 repaintLines(caretLine
, caretLine
);
556 if (myGutterNeedsUpdate
) {
561 public void focusLost(FocusEvent e
) {
563 int caretLine
= getCaretModel().getLogicalPosition().line
;
564 repaintLines(caretLine
, caretLine
);
569 // myBorderEffect.reset();
571 final DropTarget dropTarget
= myEditorComponent
.getDropTarget();
572 if (dropTarget
!= null) { // might be null in headless environment
573 dropTarget
.addDropTargetListener(new DropTargetAdapter() {
574 public void drop(DropTargetDropEvent dtde
) {
577 public void dragOver(DropTargetDragEvent dtde
) {
578 Point location
= dtde
.getLocation();
580 moveCaretToScreenPos(location
.x
, location
.y
);
581 getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
586 catch (TooManyListenersException e
) {
590 myPanel
.addComponentListener(new ComponentAdapter() {
591 public void componentResized(ComponentEvent e
) {
592 myMarkupModel
.repaint();
597 private boolean mayShowToolbar() {
598 return !isEmbeddedIntoDialogWrapper() && !isOneLineMode() && ContextMenuImpl
.mayShowToolbar(myDocument
);
601 public void setFontSize(final int fontSize
) {
602 int oldFontSize
= myScheme
.getEditorFontSize();
603 myScheme
.setEditorFontSize(fontSize
);
604 myPropertyChangeSupport
.firePropertyChange(PROP_FONT_SIZE
, oldFontSize
, fontSize
);
607 public ActionCallback
type(final String text
) {
608 final ActionCallback result
= new ActionCallback();
610 Application app
= ApplicationManager
.getApplication();
611 if (!app
.isWriteAccessAllowed()) {
612 result
.setRejected();
614 app
.runWriteAction(new Runnable() {
616 for (int i
= 0; i
< text
.length(); i
++) {
617 if (!processKeyTyped(text
.charAt(i
))) {
618 result
.setRejected();
631 private boolean processKeyTyped(char c
) {
632 // [vova] This is patch for Mac OS X. Under Mac "input methods"
633 // is handled before our EventQueue consume upcoming KeyEvents.
634 IdeEventQueue queue
= IdeEventQueue
.getInstance();
635 if (queue
.shouldNotTypeInEditor() || ProgressManager
.getInstance().hasModalProgressIndicator()) {
638 ActionManagerEx actionManager
= ActionManagerEx
.getInstanceEx();
639 DataContext dataContext
= getDataContext();
640 actionManager
.fireBeforeEditorTyping(c
, dataContext
);
641 EditorActionManager
.getInstance().getTypedAction().actionPerformed(this, c
, dataContext
);
646 private void fireFocusLost() {
647 FocusChangeListener
[] listeners
= getFocusListeners();
648 for (FocusChangeListener listener
: listeners
) {
649 listener
.focusLost(this);
653 private FocusChangeListener
[] getFocusListeners() {
654 return myFocusListeners
.toArray(new FocusChangeListener
[myFocusListeners
.size()]);
657 private void fireFocusGained() {
658 FocusChangeListener
[] listeners
= getFocusListeners();
659 for (FocusChangeListener listener
: listeners
) {
660 listener
.focusGained(this);
664 public void setHighlighter(EditorHighlighter highlighter
) {
665 assertIsDispatchThread();
666 final Document document
= getDocument();
667 if (myHighlighter
!= null) {
668 document
.removeDocumentListener(myHighlighter
);
671 document
.addDocumentListener(highlighter
);
672 highlighter
.setEditor(this);
673 highlighter
.setText(document
.getCharsSequence());
674 myHighlighter
= highlighter
;
675 if (document
instanceof DocumentImpl
) {
676 ((DocumentImpl
)document
).rememberEditorHighlighterForCachesOptimization(highlighter
);
679 if (myPanel
!= null) {
684 public EditorHighlighter
getHighlighter() {
685 assertIsDispatchThread();
686 return myHighlighter
;
690 public JComponent
getContentComponent() {
691 return myEditorComponent
;
694 public EditorGutterComponentEx
getGutterComponentEx() {
695 return myGutterComponent
;
698 public void addPropertyChangeListener(PropertyChangeListener listener
) {
699 myPropertyChangeSupport
.addPropertyChangeListener(listener
);
702 public void removePropertyChangeListener(PropertyChangeListener listener
) {
703 myPropertyChangeSupport
.removePropertyChangeListener(listener
);
706 public void setInsertMode(boolean mode
) {
707 assertIsDispatchThread();
708 boolean oldValue
= myIsInsertMode
;
709 myIsInsertMode
= mode
;
710 myPropertyChangeSupport
.firePropertyChange(PROP_INSERT_MODE
, oldValue
, mode
);
711 //Repaint the caret line by moving caret to the same place
712 LogicalPosition caretPosition
= getCaretModel().getLogicalPosition();
713 getCaretModel().moveToLogicalPosition(caretPosition
);
716 public boolean isInsertMode() {
717 return myIsInsertMode
;
720 public void setColumnMode(boolean mode
) {
721 assertIsDispatchThread();
722 boolean oldValue
= myIsColumnMode
;
723 myIsColumnMode
= mode
;
724 myPropertyChangeSupport
.firePropertyChange(PROP_COLUMN_MODE
, oldValue
, mode
);
727 public boolean isColumnMode() {
728 return myIsColumnMode
;
731 private int yPositionToVisibleLineNumber(int y
) {
732 return y
/ getLineHeight();
736 public VisualPosition
xyToVisualPosition(@NotNull Point p
) {
737 int line
= yPositionToVisibleLineNumber(p
.y
);
739 if (line
== 0 && myPrefixText
!= null) {
740 for (char c
: myPrefixText
) {
741 px
-= EditorUtil
.charWidth(c
, myPrefixAttributes
.getFontType(), this);
745 int offset
= logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line
, 0)));
746 int textLength
= myDocument
.getTextLength();
748 if (offset
>= textLength
) return new VisualPosition(line
, 0);
752 CharSequence text
= myDocument
.getCharsNoThreadCheck();
754 IterationState state
= new IterationState(this, offset
, false);
756 int fontType
= state
.getMergedAttributes().getFontType();
757 int spaceSize
= EditorUtil
.getSpaceWidth(fontType
, this);
762 if (offset
>= textLength
) break;
764 if (offset
>= state
.getEndOffset()) {
766 fontType
= state
.getMergedAttributes().getFontType();
769 FoldRegion region
= state
.getCurrentFold();
770 if (region
!= null) {
771 char[] placeholder
= region
.getPlaceholderText().toCharArray();
772 for (char aPlaceholder
: placeholder
) {
774 x
+= EditorUtil
.charWidth(c
, fontType
, this);
775 if (x
>= px
) break outer
;
778 offset
= region
.getEndOffset();
782 c
= text
.charAt(offset
);
787 x
= EditorUtil
.nextTabStop(x
, this);
790 x
+= EditorUtil
.charWidth(c
, fontType
, this);
796 column
+= (x
- prevX
) / spaceSize
;
806 int charWidth
= EditorUtil
.charWidth(c
, fontType
, this);
808 if (x
>= px
&& c
== '\t') {
809 if (mySettings
.isCaretInsideTabs()) {
810 column
+= (px
- prevX
) / spaceSize
;
811 if ((px
- prevX
) % spaceSize
> spaceSize
/ 2) column
++;
814 if ((x
- px
) * 2 < x
- prevX
) {
815 column
+= (x
- prevX
) / spaceSize
;
821 if ((x
- px
) * 2 < charWidth
) column
++;
824 column
+= (px
- x
) / EditorUtil
.getSpaceWidth(fontType
, this);
828 return new VisualPosition(line
, column
);
832 public VisualPosition
offsetToVisualPosition(int offset
) {
833 return logicalToVisualPosition(offsetToLogicalPosition(offset
));
837 public LogicalPosition
offsetToLogicalPosition(int offset
) {
838 int line
= calcLogicalLineNumber(offset
);
839 int column
= calcColumnNumber(offset
, line
);
840 return new LogicalPosition(line
, column
);
844 public LogicalPosition
xyToLogicalPosition(@NotNull Point p
) {
846 if (p
.x
>= 0 && p
.y
>= 0) {
850 pp
= new Point(Math
.max(p
.x
, 0), Math
.max(p
.y
, 0));
853 return visualToLogicalPosition(xyToVisualPosition(pp
));
856 private int logicalLineToY(int line
) {
857 VisualPosition visible
= logicalToVisualPosition(new LogicalPosition(line
, 0));
858 return visibleLineNumberToYPosition(visible
.line
);
862 public Point
logicalPositionToXY(@NotNull LogicalPosition pos
) {
863 VisualPosition visible
= logicalToVisualPosition(pos
);
864 int y
= visibleLineNumberToYPosition(visible
.line
);
872 if (pos
.line
>= myDocument
.getLineCount()) {
873 lineStartOffset
= myDocument
.getTextLength();
876 lineStartOffset
= myDocument
.getLineStartOffset(pos
.line
);
880 int x
= getTabbedTextWidth(lineStartOffset
, visible
);
881 return new Point(x
, y
);
885 public Point
visualPositionToXY(@NotNull VisualPosition visible
) {
886 int y
= visibleLineNumberToYPosition(visible
.line
);
887 int logLine
= visualToLogicalPosition(new VisualPosition(visible
.line
, 0)).line
;
895 if (logLine
>= myDocument
.getLineCount()) {
896 lineStartOffset
= myDocument
.getTextLength();
899 lineStartOffset
= myDocument
.getLineStartOffset(logLine
);
903 int x
= getTabbedTextWidth(lineStartOffset
, visible
);
904 return new Point(x
, y
);
907 private int getTabbedTextWidth(int lineStartOffset
, VisualPosition pos
) {
909 if (lineStartOffset
== 0 && myPrefixText
!= null) {
910 for (char c
: myPrefixText
) {
911 x
+= EditorUtil
.charWidth(c
, myPrefixAttributes
.getFontType(), this);
914 if (pos
.column
== 0) return x
;
915 int offset
= lineStartOffset
;
916 CharSequence text
= myDocument
.getCharsNoThreadCheck();
917 int textLength
= myDocument
.getTextLength();
918 IterationState state
= new IterationState(this, offset
, false);
919 int fontType
= state
.getMergedAttributes().getFontType();
920 int spaceSize
= EditorUtil
.getSpaceWidth(fontType
, this);
924 while (column
< pos
.column
) {
925 if (offset
>= textLength
) break;
927 if (offset
>= state
.getEndOffset()) {
929 fontType
= state
.getMergedAttributes().getFontType();
932 FoldRegion region
= state
.getCurrentFold();
934 if (region
!= null) {
935 char[] placeholder
= region
.getPlaceholderText().toCharArray();
936 for (char aPlaceholder
: placeholder
) {
937 x
+= EditorUtil
.charWidth(aPlaceholder
, fontType
, this);
939 if (column
>= pos
.column
) break outer
;
941 offset
= region
.getEndOffset();
944 char c
= text
.charAt(offset
);
950 x
= EditorUtil
.nextTabStop(x
, this);
951 column
+= (x
- prevX
) / spaceSize
;
954 x
+= EditorUtil
.charWidth(c
, fontType
, this);
961 if (column
!= pos
.column
) {
962 x
+= EditorUtil
.getSpaceWidth(fontType
, this) * (pos
.column
- column
);
968 public int visibleLineNumberToYPosition(int lineNum
) {
969 if (lineNum
< 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum
);
970 return lineNum
* getLineHeight();
973 public void repaint(int startOffset
, int endOffset
) {
974 if (!isShowing() || myScrollPane
== null || myDocument
.isInBulkUpdate()) {
978 assertIsDispatchThread();
980 if (endOffset
> myDocument
.getTextLength()) {
981 endOffset
= myDocument
.getTextLength();
983 if (startOffset
< endOffset
) {
984 int startLine
= myDocument
.getLineNumber(startOffset
);
985 int endLine
= myDocument
.getLineNumber(endOffset
);
986 repaintLines(startLine
, endLine
);
990 private boolean isShowing() {
991 return myGutterComponent
!= null && myGutterComponent
.isShowing();
994 private void repaintToScreenBotton(int startLine
) {
995 Rectangle visibleRect
= getScrollingModel().getVisibleArea();
996 int yStartLine
= logicalLineToY(startLine
);
997 int yEndLine
= visibleRect
.y
+ visibleRect
.height
;
999 myEditorComponent
.repaintEditorComponent(visibleRect
.x
, yStartLine
, visibleRect
.x
+ visibleRect
.width
, yEndLine
- yStartLine
);
1000 myGutterComponent
.repaint(0, yStartLine
, myGutterComponent
.getWidth(), yEndLine
- yStartLine
);
1003 public void repaintLines(int startLine
, int endLine
) {
1004 if (!isShowing()) return;
1006 Rectangle visibleRect
= getScrollingModel().getVisibleArea();
1007 int yStartLine
= logicalLineToY(startLine
);
1008 int yEndLine
= logicalLineToY(endLine
) + getLineHeight() + WAVE_HEIGHT
;
1010 myEditorComponent
.repaintEditorComponent(visibleRect
.x
, yStartLine
, visibleRect
.x
+ visibleRect
.width
, yEndLine
- yStartLine
);
1011 myGutterComponent
.repaint(0, yStartLine
, myGutterComponent
.getWidth(), yEndLine
- yStartLine
);
1014 private void beforeChangedUpdate(DocumentEvent e
) {
1015 if (!myDocument
.isInBulkUpdate()) {
1016 Rectangle viewRect
= getScrollingModel().getVisibleArea();
1017 Point pos
= visualPositionToXY(getCaretModel().getVisualPosition());
1018 myCaretUpdateVShift
= pos
.y
- viewRect
.y
;
1020 mySizeContainer
.beforeChange(e
);
1023 private void changedUpdate(DocumentEvent e
) {
1024 if (myScrollPane
== null) return;
1026 stopOptimizedScrolling();
1027 mySelectionModel
.removeBlockSelection();
1029 mySizeContainer
.changedUpdate(e
);
1032 int startLine
= calcLogicalLineNumber(e
.getOffset());
1033 int endLine
= calcLogicalLineNumber(e
.getOffset() + e
.getNewLength());
1035 boolean painted
= false;
1036 if (myDocument
.getTextLength() > 0) {
1037 int startDocLine
= myDocument
.getLineNumber(e
.getOffset());
1038 int endDocLine
= myDocument
.getLineNumber(e
.getOffset() + e
.getNewLength());
1039 if (e
.getOldLength() > e
.getNewLength() || startDocLine
!= endDocLine
) {
1043 if (countLineFeeds(e
.getOldFragment()) != countLineFeeds(e
.getNewFragment())) {
1044 // Lines removed. Need to repaint till the end of the screen
1045 repaintToScreenBotton(startLine
);
1050 updateCaretCursor();
1052 repaintLines(startLine
, endLine
);
1055 if (!myDocument
.isInBulkUpdate()) {
1056 Point caretLocation
= visualPositionToXY(getCaretModel().getVisualPosition());
1057 int scrollOffset
= caretLocation
.y
- myCaretUpdateVShift
;
1058 getScrollingModel().scrollVertically(scrollOffset
);
1062 private static int countLineFeeds(CharSequence c
) {
1063 return StringUtil
.countNewLines(c
);
1066 private void updateGutterSize() {
1067 if (myGutterSizeUpdater
!= null) return;
1068 myGutterSizeUpdater
= new Runnable() {
1070 if (!isDisposed()) {
1072 myGutterComponent
.updateSize();
1073 myGutterNeedsUpdate
= false;
1076 myGutterNeedsUpdate
= true;
1079 myGutterSizeUpdater
= null;
1083 SwingUtilities
.invokeLater(myGutterSizeUpdater
);
1086 void validateSize() {
1087 Dimension dim
= getPreferredSize();
1089 if (!dim
.equals(myPreferredSize
) && !myDocument
.isInBulkUpdate()) {
1090 myPreferredSize
= dim
;
1092 stopOptimizedScrolling();
1093 int lineNum
= Math
.max(1, getDocument().getLineCount());
1094 myGutterComponent
.setLineNumberAreaWidth(getFontMetrics(Font
.PLAIN
).stringWidth(Integer
.toString(lineNum
+ 2)) + 6);
1096 myEditorComponent
.setSize(dim
);
1097 myEditorComponent
.fireResized();
1099 myMarkupModel
.repaint();
1103 void recalcSizeAndRepaint() {
1104 mySizeContainer
.reset();
1106 myEditorComponent
.repaintEditorComponent();
1110 public Document
getDocument() {
1115 public JComponent
getComponent() {
1119 public void addEditorMouseListener(@NotNull EditorMouseListener listener
) {
1120 myMouseListeners
.add(listener
);
1123 public void removeEditorMouseListener(@NotNull EditorMouseListener listener
) {
1124 boolean success
= myMouseListeners
.remove(listener
);
1125 LOG
.assertTrue(success
);
1128 public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener
) {
1129 myMouseMotionListeners
.add(listener
);
1132 public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener
) {
1133 boolean success
= myMouseMotionListeners
.remove(listener
);
1134 LOG
.assertTrue(success
);
1137 public boolean isDisposed() {
1141 void paint(Graphics g
) {
1142 startOptimizedScrolling();
1144 if (myCursorUpdater
!= null) {
1145 myCursorUpdater
.run();
1146 myCursorUpdater
= null;
1149 Rectangle clip
= getClipBounds(g
);
1155 Rectangle viewRect
= getScrollingModel().getVisibleArea();
1156 if (viewRect
== null) {
1161 g
.setColor(new Color(128, 255, 128));
1162 g
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);
1167 paintBackgrounds(g
, clip
);
1168 paintRectangularSelection(g
);
1169 paintRightMargin(g
, clip
);
1170 final MarkupModel docMarkup
= myDocument
.getMarkupModel(myProject
);
1171 paintLineMarkersSeparators(g
, clip
, docMarkup
);
1172 paintLineMarkersSeparators(g
, clip
, myMarkupModel
);
1174 paintSegmentHighlightersBorderAndAfterEndOfLine(g
, clip
);
1175 BorderEffect borderEffect
= new BorderEffect(this, g
);
1176 borderEffect
.paintHighlighters(getHighlighter());
1177 borderEffect
.paintHighlighters(docMarkup
.getAllHighlighters());
1178 borderEffect
.paintHighlighters(getMarkupModel().getAllHighlighters());
1179 paintCaretCursor(g
);
1181 paintComposedTextDecoration((Graphics2D
)g
);
1184 public void setHeaderComponent(JComponent header
) {
1185 myHeaderPanel
.removeAll();
1186 if (header
!= null) {
1187 myHeaderPanel
.add(header
);
1190 myHeaderPanel
.revalidate();
1193 public boolean hasHeaderComponent() {
1194 return myHeaderPanel
.getComponentCount() > 0;
1198 public JComponent
getHeaderComponent() {
1199 if (hasHeaderComponent()) {
1200 return (JComponent
)myHeaderPanel
.getComponent(0);
1205 public void setBackgroundColor(Color color
) {
1206 myForcedBackground
= color
;
1209 public void resetBackgourndColor() {
1210 myForcedBackground
= null;
1213 public Color
getForegroundColor() {
1214 return myScheme
.getDefaultForeground();
1217 public Color
getBackroundColor() {
1218 if (myForcedBackground
!= null) return myForcedBackground
;
1220 return getBackgroundIgnoreForced();
1223 private Color
getBackgroundColor(final TextAttributes attributes
) {
1224 final Color attrColor
= attributes
.getBackgroundColor();
1225 return attrColor
== myScheme
.getDefaultBackground() ?
getBackroundColor() : attrColor
;
1228 private Color
getBackgroundIgnoreForced() {
1229 Color color
= myScheme
.getDefaultBackground();
1230 if (myDocument
.isWritable()) {
1233 Color readOnlyColor
= myScheme
.getColor(EditorColors
.READONLY_BACKGROUND_COLOR
);
1234 return readOnlyColor
!= null ? readOnlyColor
: color
;
1237 private void paintComposedTextDecoration(Graphics2D g
) {
1238 if (myInputMethodRequestsHandler
!= null && myInputMethodRequestsHandler
.composedText
!= null) {
1239 VisualPosition visStart
=
1240 offsetToVisualPosition(Math
.min(myInputMethodRequestsHandler
.composedTextStart
, myDocument
.getTextLength()));
1241 int y
= visibleLineNumberToYPosition(visStart
.line
) + getLineHeight() - getDescent() + 1;
1242 Point p1
= visualPositionToXY(visStart
);
1244 logicalPositionToXY(offsetToLogicalPosition(Math
.min(myInputMethodRequestsHandler
.composedTextEnd
, myDocument
.getTextLength())));
1246 Stroke saved
= g
.getStroke();
1247 BasicStroke dotted
= new BasicStroke(1, BasicStroke
.CAP_ROUND
, BasicStroke
.JOIN_ROUND
, 0, new float[]{0, 2, 0, 2}, 0);
1248 g
.setStroke(dotted
);
1249 UIUtil
.drawLine(g
, p1
.x
, y
, p2
.x
, y
);
1254 private static Rectangle
getClipBounds(Graphics g
) {
1255 return g
.getClipBounds();
1258 private void paintRightMargin(Graphics g
, Rectangle clip
) {
1259 Color rightMargin
= myScheme
.getColor(EditorColors
.RIGHT_MARGIN_COLOR
);
1260 if (!mySettings
.isRightMarginShown() || rightMargin
== null) {
1263 int x
= mySettings
.getRightMargin(myProject
) * EditorUtil
.getSpaceWidth(Font
.PLAIN
, this);
1264 if (x
>= clip
.x
&& x
< clip
.x
+ clip
.width
) {
1265 g
.setColor(rightMargin
);
1266 UIUtil
.drawLine(g
, x
, clip
.y
, x
, clip
.y
+ clip
.height
);
1270 private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g
, Rectangle clip
) {
1271 if (myDocument
.getLineCount() == 0) return;
1272 int startLineNumber
= yPositionToVisibleLineNumber(clip
.y
);
1273 int endLineNumber
= yPositionToVisibleLineNumber(clip
.y
+ clip
.height
) + 1;
1275 final MarkupModel docMarkup
= myDocument
.getMarkupModel(myProject
);
1276 RangeHighlighter
[] segmentHighlighters
= docMarkup
.getAllHighlighters();
1277 for (RangeHighlighter segmentHighlighter
: segmentHighlighters
) {
1278 paintSegmentHighlighterAfterEndOfLine(g
, (RangeHighlighterEx
)segmentHighlighter
, startLineNumber
, endLineNumber
);
1281 segmentHighlighters
= getMarkupModel().getAllHighlighters();
1282 for (RangeHighlighter segmentHighlighter
: segmentHighlighters
) {
1283 paintSegmentHighlighterAfterEndOfLine(g
, (RangeHighlighterEx
)segmentHighlighter
, startLineNumber
, endLineNumber
);
1287 private void paintSegmentHighlighterAfterEndOfLine(Graphics g
,
1288 RangeHighlighterEx segmentHighlighter
,
1289 int startLineNumber
,
1290 int endLineNumber
) {
1291 if (!segmentHighlighter
.isValid()) {
1294 if (segmentHighlighter
.isAfterEndOfLine()) {
1295 int startOffset
= segmentHighlighter
.getStartOffset();
1296 int visibleStartLine
= offsetToVisualPosition(startOffset
).line
;
1298 if (!getFoldingModel().isOffsetCollapsed(startOffset
)) {
1299 if (visibleStartLine
>= startLineNumber
&& visibleStartLine
<= endLineNumber
) {
1300 int logStartLine
= offsetToLogicalPosition(startOffset
).line
;
1301 LogicalPosition logPosition
= offsetToLogicalPosition(myDocument
.getLineEndOffset(logStartLine
));
1302 Point end
= logicalPositionToXY(logPosition
);
1303 int charWidth
= EditorUtil
.getSpaceWidth(Font
.PLAIN
, this);
1304 int lineHeight
= getLineHeight();
1305 TextAttributes attributes
= segmentHighlighter
.getTextAttributes();
1306 if (attributes
!= null && getBackgroundColor(attributes
) != null) {
1307 g
.setColor(getBackgroundColor(attributes
));
1308 g
.fillRect(end
.x
, end
.y
, charWidth
, lineHeight
);
1310 if (attributes
!= null && attributes
.getEffectColor() != null) {
1311 int y
= visibleLineNumberToYPosition(visibleStartLine
) + getLineHeight() - getDescent() + 1;
1312 g
.setColor(attributes
.getEffectColor());
1313 if (attributes
.getEffectType() == EffectType
.WAVE_UNDERSCORE
) {
1314 drawWave(g
, end
.x
, end
.x
+ charWidth
- 1, y
);
1316 else if (attributes
.getEffectType() != EffectType
.BOXED
) {
1317 UIUtil
.drawLine(g
, end
.x
, y
, end
.x
+ charWidth
- 1, y
);
1325 public int getMaxWidthInRange(int startOffset
, int endOffset
) {
1327 VisualPosition start
= offsetToVisualPosition(startOffset
);
1328 VisualPosition end
= offsetToVisualPosition(endOffset
);
1330 for (int i
= start
.line
; i
<= end
.line
; i
++) {
1331 int lastColumn
= EditorUtil
.getLastVisualLineColumnNumber(this, i
) + 1;
1332 int lineWidth
= visualPositionToXY(new VisualPosition(i
, lastColumn
)).x
;
1334 if (lineWidth
> width
) {
1342 private void paintBackgrounds(Graphics g
, Rectangle clip
) {
1343 Color defaultBackground
= getBackroundColor();
1344 g
.setColor(defaultBackground
);
1345 g
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);
1347 int lineHeight
= getLineHeight();
1349 int visibleLineNumber
= clip
.y
/ lineHeight
;
1351 int startLineNumber
= xyToLogicalPosition(new Point(0, clip
.y
)).line
;
1353 Point position
= new Point(0, visibleLineNumber
* lineHeight
);
1354 if (startLineNumber
== 0 && myPrefixText
!= null) {
1355 position
.x
= drawBackground(g
, myPrefixAttributes
.getBackgroundColor(), new String(myPrefixText
), position
, myPrefixAttributes
.getFontType(),
1356 defaultBackground
, clip
);
1359 if (startLineNumber
>= myDocument
.getLineCount() || startLineNumber
< 0) {
1360 if (position
.x
> 0) flushBackground(g
, clip
);
1364 int start
= myDocument
.getLineStartOffset(startLineNumber
);
1366 IterationState iterationState
= new IterationState(this, start
, paintSelection());
1368 LineIterator lIterator
= createLineIterator();
1369 lIterator
.start(start
);
1370 if (lIterator
.atEnd()) {
1374 myLastBackgroundPosition
= null;
1375 myLastBackgroundColor
= null;
1377 TextAttributes attributes
= iterationState
.getMergedAttributes();
1378 Color backColor
= getBackgroundColor(attributes
);
1379 int fontType
= attributes
.getFontType();
1380 CharSequence text
= myDocument
.getCharsNoThreadCheck();
1381 int lastLineIndex
= Math
.max(0, myDocument
.getLineCount() - 1);
1382 while (!iterationState
.atEnd() && !lIterator
.atEnd()) {
1383 int hEnd
= iterationState
.getEndOffset();
1384 int lEnd
= lIterator
.getEnd();
1387 FoldRegion collapsedFolderAt
= myFoldingModel
.getCollapsedRegionAtOffset(start
);
1388 if (collapsedFolderAt
== null) {
1389 position
.x
= drawBackground(g
, backColor
, text
.subSequence(start
, lEnd
- lIterator
.getSeparatorLength()), position
, fontType
,
1390 defaultBackground
, clip
);
1392 if (lIterator
.getLineNumber() < lastLineIndex
) {
1393 if (backColor
!= null && !backColor
.equals(defaultBackground
)) {
1394 g
.setColor(backColor
);
1395 g
.fillRect(position
.x
, position
.y
, clip
.x
+ clip
.width
- position
.x
, lineHeight
);
1399 paintAfterFileEndBackground(iterationState
, g
, position
, clip
, lineHeight
, defaultBackground
);
1404 if (position
.y
> clip
.y
+ clip
.height
) break;
1405 position
.y
+= lineHeight
;
1409 lIterator
.advance();
1412 FoldRegion collapsedFolderAt
= iterationState
.getCurrentFold();
1413 if (collapsedFolderAt
!= null) {
1414 position
.x
= drawBackground(g
, backColor
, collapsedFolderAt
.getPlaceholderText(), position
, fontType
, defaultBackground
, clip
);
1417 if (hEnd
> lEnd
- lIterator
.getSeparatorLength()) {
1418 position
.x
= drawBackground(g
, backColor
, text
.subSequence(start
, lEnd
- lIterator
.getSeparatorLength()), position
, fontType
,
1419 defaultBackground
, clip
);
1422 position
.x
= drawBackground(g
, backColor
, text
.subSequence(start
, hEnd
), position
, fontType
, defaultBackground
, clip
);
1426 iterationState
.advance();
1427 attributes
= iterationState
.getMergedAttributes();
1428 backColor
= getBackgroundColor(attributes
);
1429 fontType
= attributes
.getFontType();
1430 start
= iterationState
.getStartOffset();
1434 flushBackground(g
, clip
);
1436 if (lIterator
.getLineNumber() >= lastLineIndex
&& position
.y
<= clip
.y
+ clip
.height
) {
1437 paintAfterFileEndBackground(iterationState
, g
, position
, clip
, lineHeight
, defaultBackground
);
1441 private void paintRectangularSelection(Graphics g
) {
1442 final SelectionModel model
= getSelectionModel();
1443 if (!model
.hasBlockSelection()) return;
1444 final LogicalPosition blockStart
= model
.getBlockStart();
1445 final LogicalPosition blockEnd
= model
.getBlockEnd();
1446 assert blockStart
!= null;
1447 assert blockEnd
!= null;
1449 final Point start
= logicalPositionToXY(blockStart
);
1450 final Point end
= logicalPositionToXY(blockEnd
);
1451 g
.setColor(myScheme
.getColor(EditorColors
.SELECTION_BACKGROUND_COLOR
));
1454 if (start
.y
<= end
.y
) {
1456 height
= end
.y
- y
+ getLineHeight();
1460 height
= start
.y
- end
.y
+ getLineHeight();
1462 final int x
= Math
.min(start
.x
, end
.x
);
1463 final int width
= Math
.max(2, Math
.abs(end
.x
- start
.x
));
1464 g
.fillRect(x
, y
, width
, height
);
1467 private static void paintAfterFileEndBackground(IterationState iterationState
,
1472 final Color defaultBackground
) {
1473 Color backColor
= iterationState
.getPastFileEndBackground();
1474 if (backColor
!= null && !backColor
.equals(defaultBackground
)) {
1475 g
.setColor(backColor
);
1476 g
.fillRect(position
.x
, position
.y
, clip
.x
+ clip
.width
- position
.x
, lineHeight
);
1480 private int drawBackground(Graphics g
,
1485 Color defaultBackground
,
1487 int w
= getTextSegmentWidth(text
, position
.x
, fontType
, clip
);
1489 if (backColor
!= null && !backColor
.equals(defaultBackground
) && clip
.intersects(position
.x
, position
.y
, w
, getLineHeight())) {
1490 if (backColor
.equals(myLastBackgroundColor
) && myLastBackgroundPosition
.y
== position
.y
&&
1491 myLastBackgroundPosition
.x
+ myLastBackgroundWidth
== position
.x
) {
1492 myLastBackgroundWidth
+= w
;
1495 flushBackground(g
, clip
);
1496 myLastBackgroundColor
= backColor
;
1497 myLastBackgroundPosition
= new Point(position
);
1498 myLastBackgroundWidth
= w
;
1502 return position
.x
+ w
;
1505 private void flushBackground(Graphics g
, final Rectangle clip
) {
1506 if (myLastBackgroundColor
!= null) {
1507 final Point position
= myLastBackgroundPosition
;
1508 final int w
= myLastBackgroundWidth
;
1509 final int height
= getLineHeight();
1510 if (clip
.intersects(position
.x
, position
.y
, w
, height
)) {
1511 g
.setColor(myLastBackgroundColor
);
1512 g
.fillRect(position
.x
, position
.y
, w
, height
);
1514 myLastBackgroundColor
= null;
1518 private LineIterator
createLineIterator() {
1519 return myDocument
.createLineIterator();
1522 private void paintText(Graphics g
, Rectangle clip
) {
1523 myCurrentFontType
= null;
1525 final int plainSpaceWidth
= EditorUtil
.getSpaceWidth(Font
.PLAIN
, this);
1526 final int boldSpaceWidth
= EditorUtil
.getSpaceWidth(Font
.BOLD
, this);
1527 final int italicSpaceWidth
= EditorUtil
.getSpaceWidth(Font
.ITALIC
, this);
1528 final int boldItalicSpaceWidth
= EditorUtil
.getSpaceWidth(Font
.BOLD
| Font
.ITALIC
, this);
1529 mySpacesHaveSameWidth
=
1530 plainSpaceWidth
== boldSpaceWidth
&& plainSpaceWidth
== italicSpaceWidth
&& plainSpaceWidth
== boldItalicSpaceWidth
;
1532 int lineHeight
= getLineHeight();
1534 int visibleLineNumber
= clip
.y
/ lineHeight
;
1536 int startLineNumber
= xyToLogicalPosition(new Point(0, clip
.y
)).line
;
1538 Point position
= new Point(0, visibleLineNumber
* lineHeight
);
1539 if (startLineNumber
== 0 && myPrefixText
!= null) {
1540 position
.x
= drawString(g
, myPrefixText
, 0, myPrefixText
.length
, position
, clip
, myPrefixAttributes
.getEffectColor(),
1541 myPrefixAttributes
.getEffectType(), myPrefixAttributes
.getFontType(), myPrefixAttributes
.getForegroundColor());
1543 if (startLineNumber
>= myDocument
.getLineCount() || startLineNumber
< 0) {
1544 if (position
.x
> 0) flushCachedChars(g
);
1548 int start
= myDocument
.getLineStartOffset(startLineNumber
);
1550 IterationState iterationState
= new IterationState(this, start
, paintSelection());
1552 LineIterator lIterator
= createLineIterator();
1553 lIterator
.start(start
);
1554 if (lIterator
.atEnd()) {
1558 TextAttributes attributes
= iterationState
.getMergedAttributes();
1559 Color currentColor
= attributes
.getForegroundColor();
1560 if (currentColor
== null) {
1561 currentColor
= getForegroundColor();
1563 Color effectColor
= attributes
.getEffectColor();
1564 EffectType effectType
= attributes
.getEffectType();
1565 int fontType
= attributes
.getFontType();
1566 g
.setColor(currentColor
);
1568 final char[] chars
= myDocument
.getRawChars();
1569 while (!iterationState
.atEnd() && !lIterator
.atEnd()) {
1570 int hEnd
= iterationState
.getEndOffset();
1571 int lEnd
= lIterator
.getEnd();
1573 FoldRegion collapsedFolderAt
= myFoldingModel
.getCollapsedRegionAtOffset(start
);
1574 if (collapsedFolderAt
== null) {
1575 drawString(g
, chars
, start
, lEnd
- lIterator
.getSeparatorLength(), position
, clip
, effectColor
, effectType
, fontType
,
1578 if (position
.y
> clip
.y
+ clip
.height
) break;
1579 position
.y
+= lineHeight
;
1583 // myBorderEffect.eolReached(g, this);
1584 lIterator
.advance();
1587 FoldRegion collapsedFolderAt
= iterationState
.getCurrentFold();
1588 if (collapsedFolderAt
!= null) {
1589 int foldingXStart
= position
.x
;
1591 drawString(g
, collapsedFolderAt
.getPlaceholderText(), position
, clip
, effectColor
, effectType
, fontType
, currentColor
);
1592 BorderEffect
.paintFoldedEffect(g
, foldingXStart
, position
.y
, position
.x
, getLineHeight(), effectColor
, effectType
);
1596 if (hEnd
> lEnd
- lIterator
.getSeparatorLength()) {
1597 position
.x
= drawString(g
, chars
, start
, lEnd
- lIterator
.getSeparatorLength(), position
, clip
, effectColor
, effectType
,
1598 fontType
, currentColor
);
1601 position
.x
= drawString(g
, chars
, start
, hEnd
, position
, clip
, effectColor
, effectType
, fontType
, currentColor
);
1605 iterationState
.advance();
1606 attributes
= iterationState
.getMergedAttributes();
1608 currentColor
= attributes
.getForegroundColor();
1609 if (currentColor
== null) {
1610 currentColor
= getForegroundColor();
1613 effectColor
= attributes
.getEffectColor();
1614 effectType
= attributes
.getEffectType();
1615 fontType
= attributes
.getFontType();
1617 start
= iterationState
.getStartOffset();
1621 FoldRegion collapsedFolderAt
= iterationState
.getCurrentFold();
1622 if (collapsedFolderAt
!= null) {
1623 int foldingXStart
= position
.x
;
1625 drawString(g
, collapsedFolderAt
.getPlaceholderText(), position
, clip
, effectColor
, effectType
, fontType
, currentColor
);
1626 BorderEffect
.paintFoldedEffect(g
, foldingXStart
, position
.y
, foldingXEnd
, getLineHeight(), effectColor
, effectType
);
1627 // myBorderEffect.collapsedFolderReached(g, this);
1630 flushCachedChars(g
);
1633 private boolean paintSelection() {
1634 return !isOneLineMode() || IJSwingUtilities
.hasFocus(getContentComponent());
1637 private class CachedFontContent
{
1638 final char[][] data
= new char[CACHED_CHARS_BUFFER_SIZE
][];
1639 final int[] starts
= new int[CACHED_CHARS_BUFFER_SIZE
];
1640 final int[] ends
= new int[CACHED_CHARS_BUFFER_SIZE
];
1641 final int[] x
= new int[CACHED_CHARS_BUFFER_SIZE
];
1642 final int[] y
= new int[CACHED_CHARS_BUFFER_SIZE
];
1643 final Color
[] color
= new Color
[CACHED_CHARS_BUFFER_SIZE
];
1646 final FontInfo myFontType
;
1648 private char[] myLastData
;
1650 private CachedFontContent(FontInfo fontInfo
) {
1651 myFontType
= fontInfo
;
1654 private void flushContent(Graphics g
) {
1656 if (myCurrentFontType
!= myFontType
) {
1657 myCurrentFontType
= myFontType
;
1658 g
.setFont(myFontType
.getFont());
1660 Color currentColor
= null;
1661 for (int i
= 0; i
< myCount
; i
++) {
1662 if (!Comparing
.equal(color
[i
], currentColor
)) {
1663 currentColor
= color
[i
];
1664 g
.setColor(currentColor
!= null ? currentColor
: Color
.black
);
1667 drawChars(g
, data
[i
], starts
[i
], ends
[i
], x
[i
], y
[i
]);
1677 private void addContent(Graphics g
, char[] _data
, int _start
, int _end
, int _x
, int _y
, Color _color
) {
1678 final int count
= myCount
;
1680 final int lastCount
= count
- 1;
1681 final Color lastColor
= color
[lastCount
];
1682 if (_data
== myLastData
&& _start
== ends
[lastCount
] && (_color
== null || lastColor
== null || _color
== lastColor
)) {
1683 ends
[lastCount
] = _end
;
1684 if (lastColor
== null) color
[lastCount
] = _color
;
1690 data
[count
] = _data
;
1693 starts
[count
] = _start
;
1695 color
[count
] = _color
;
1698 if (count
>= CACHED_CHARS_BUFFER_SIZE
- 1) {
1704 private void flushCachedChars(Graphics g
) {
1705 for (CachedFontContent cache
: myFontCache
) {
1706 cache
.flushContent(g
);
1711 private void paintCaretCursor(Graphics g
) {
1712 myCaretCursor
.paint(g
);
1715 private void paintLineMarkersSeparators(Graphics g
, Rectangle clip
, MarkupModel markupModel
) {
1716 if (markupModel
== null) return;
1717 RangeHighlighter
[] lineMarkers
= markupModel
.getAllHighlighters();
1718 for (RangeHighlighter lineMarker
: lineMarkers
) {
1719 paintLineMarkerSeparator(lineMarker
, clip
, g
);
1723 private void paintLineMarkerSeparator(RangeHighlighter marker
, Rectangle clip
, Graphics g
) {
1724 if (!marker
.isValid()) {
1727 Color separatorColor
= marker
.getLineSeparatorColor();
1728 if (separatorColor
!= null) {
1729 int lineNumber
= marker
.getLineSeparatorPlacement() == SeparatorPlacement
.TOP ? marker
.getDocument()
1730 .getLineNumber(marker
.getStartOffset()) : marker
.getDocument().getLineNumber(marker
.getEndOffset());
1731 if (lineNumber
< 0 || lineNumber
>= myDocument
.getLineCount()) {
1735 int y
= visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber
, 0)).line
);
1736 if (marker
.getLineSeparatorPlacement() != SeparatorPlacement
.TOP
) {
1737 y
+= getLineHeight();
1740 if (y
< clip
.y
|| y
> clip
.y
+ clip
.height
) return;
1742 int endShift
= clip
.x
+ clip
.width
;
1743 g
.setColor(separatorColor
);
1745 if (mySettings
.isRightMarginShown() && myScheme
.getColor(EditorColors
.RIGHT_MARGIN_COLOR
) != null) {
1746 endShift
= Math
.min(endShift
, mySettings
.getRightMargin(myProject
) * EditorUtil
.getSpaceWidth(Font
.PLAIN
, this));
1749 UIUtil
.drawLine(g
, 0, y
- 1, endShift
, y
- 1);
1753 private int drawString(Graphics g
,
1760 EffectType effectType
,
1763 if (start
>= end
) return position
.x
;
1765 boolean isInClip
= getLineHeight() + position
.y
>= clip
.y
&& position
.y
<= clip
.y
+ clip
.height
;
1767 if (!isInClip
) return position
.x
;
1769 int y
= getLineHeight() - getDescent() + position
.y
;
1771 return drawTabbedString(g
, text
, start
, end
, x
, y
, effectColor
, effectType
, fontType
, fontColor
, clip
);
1774 private int drawString(Graphics g
,
1779 EffectType effectType
,
1782 boolean isInClip
= getLineHeight() + position
.y
>= clip
.y
&& position
.y
<= clip
.y
+ clip
.height
;
1784 if (!isInClip
) return position
.x
;
1786 int y
= getLineHeight() - getDescent() + position
.y
;
1789 return drawTabbedString(g
, text
.toCharArray(), 0, text
.length(), x
, y
, effectColor
, effectType
, fontType
, fontColor
, clip
);
1792 private int drawTabbedString(Graphics g
,
1799 EffectType effectType
,
1802 final Rectangle clip
) {
1805 for (int i
= start
; i
< end
; i
++) {
1806 if (text
[i
] != '\t') continue;
1808 x
= drawTablessString(text
, start
, i
, g
, x
, y
, fontType
, fontColor
, clip
);
1810 int x1
= EditorUtil
.nextTabStop(x
, this);
1811 drawTabPlacer(g
, y
, x
, x1
);
1816 x
= drawTablessString(text
, start
, end
, g
, x
, y
, fontType
, fontColor
, clip
);
1818 if (effectColor
!= null) {
1819 final Color savedColor
= g
.getColor();
1821 // myBorderEffect.flushIfCantProlong(g, this, effectType, effectColor);
1823 if (xStart
< clip
.x
&& xEnd
< clip
.x
|| xStart
> clip
.x
+ clip
.width
&& xEnd
> clip
.x
+ clip
.width
) {
1827 if (xEnd
> clip
.x
+ clip
.width
) {
1828 xEnd
= clip
.x
+ clip
.width
;
1830 if (xStart
< clip
.x
) {
1834 if (effectType
== EffectType
.LINE_UNDERSCORE
) {
1835 g
.setColor(effectColor
);
1836 UIUtil
.drawLine(g
, xStart
, y
+ 1, xEnd
, y
+ 1);
1837 g
.setColor(savedColor
);
1839 else if (effectType
== EffectType
.BOLD_LINE_UNDERSCORE
) {
1840 g
.setColor(effectColor
);
1841 UIUtil
.drawLine(g
, xStart
, y
, xEnd
, y
);
1842 UIUtil
.drawLine(g
, xStart
, y
+ 1, xEnd
, y
+ 1);
1843 g
.setColor(savedColor
);
1845 else if (effectType
== EffectType
.STRIKEOUT
) {
1846 g
.setColor(effectColor
);
1847 int y1
= y
- getCharHeight() / 2;
1848 UIUtil
.drawLine(g
, xStart
, y1
, xEnd
, y1
);
1849 g
.setColor(savedColor
);
1851 else if (effectType
== EffectType
.WAVE_UNDERSCORE
) {
1852 g
.setColor(effectColor
);
1853 drawWave(g
, xStart
, xEnd
, y
+ 1);
1854 g
.setColor(savedColor
);
1861 private int drawTablessString(final char[] text
,
1868 final Color fontColor
,
1869 final Rectangle clip
) {
1872 FontInfo font
= EditorUtil
.fontForChar(text
[start
], fontType
, this);
1873 for (int j
= start
; j
< end
; j
++) {
1874 final char c
= text
[j
];
1875 FontInfo newFont
= EditorUtil
.fontForChar(c
, fontType
, this);
1876 if (font
!= newFont
|| endX
> clip
.x
+ clip
.width
) {
1877 if (!(x
< clip
.x
&& endX
< clip
.x
|| x
> clip
.x
+ clip
.width
&& endX
> clip
.x
+ clip
.width
)) {
1878 drawCharsCached(g
, text
, start
, j
, x
, y
, fontType
, fontColor
);
1884 if (x
< clip
.x
&& endX
< clip
.x
) {
1889 else if (x
> clip
.x
+ clip
.width
) {
1892 endX
+= font
.charWidth(c
, myEditorComponent
);
1895 if (!(x
< clip
.x
&& endX
< clip
.x
|| x
> clip
.x
+ clip
.width
&& endX
> clip
.x
+ clip
.width
)) {
1896 drawCharsCached(g
, text
, start
, end
, x
, y
, fontType
, fontColor
);
1903 private void drawTabPlacer(Graphics g
, int y
, int start
, int stop
) {
1904 if (mySettings
.isWhitespacesShown()) {
1905 stop
-= g
.getFontMetrics().charWidth(' ') / 2;
1906 Color oldColor
= g
.getColor();
1907 g
.setColor(myScheme
.getColor(EditorColors
.WHITESPACES_COLOR
));
1908 final int charHeight
= getCharHeight();
1909 final int halfCharHeight
= charHeight
/ 2;
1910 int mid
= y
- halfCharHeight
;
1911 int top
= y
- charHeight
;
1912 UIUtil
.drawLine(g
, start
, mid
, stop
, mid
);
1913 UIUtil
.drawLine(g
, stop
, y
, stop
, top
);
1914 g
.fillPolygon(new int[]{stop
- halfCharHeight
, stop
- halfCharHeight
, stop
}, new int[]{y
, y
- charHeight
, y
- halfCharHeight
}, 3);
1915 g
.setColor(oldColor
);
1919 private void drawCharsCached(Graphics g
, char[] data
, int start
, int end
, int x
, int y
, int fontType
, Color color
) {
1920 if (mySpacesHaveSameWidth
&& myLastCache
!= null && spacesOnly(data
, start
, end
)) {
1921 myLastCache
.addContent(g
, data
, start
, end
, x
, y
, null);
1924 FontInfo fnt
= EditorUtil
.fontForChar(data
[start
], fontType
, this);
1925 CachedFontContent cache
= null;
1926 for (CachedFontContent fontCache
: myFontCache
) {
1927 if (fontCache
.myFontType
== fnt
) {
1932 if (cache
== null) {
1933 cache
= new CachedFontContent(fnt
);
1934 myFontCache
.add(cache
);
1937 myLastCache
= cache
;
1938 cache
.addContent(g
, data
, start
, end
, x
, y
, color
);
1942 private static boolean spacesOnly(char[] chars
, int start
, int end
) {
1943 for (int i
= start
; i
< end
; i
++) {
1944 if (chars
[i
] != ' ') return false;
1949 private void drawChars(Graphics g
, char[] data
, int start
, int end
, int x
, int y
) {
1950 g
.drawChars(data
, start
, end
- start
, x
, y
);
1952 if (mySettings
.isWhitespacesShown()) {
1953 Color oldColor
= g
.getColor();
1954 g
.setColor(myScheme
.getColor(EditorColors
.WHITESPACES_COLOR
));
1955 final FontMetrics metrics
= g
.getFontMetrics();
1956 int halfSpaceWidth
= metrics
.charWidth(' ') / 2;
1957 for (int i
= start
; i
< end
; i
++) {
1958 if (data
[i
] == ' ') {
1959 g
.fillRect(x
+ halfSpaceWidth
, y
, 1, 1);
1961 x
+= metrics
.charWidth(data
[i
]);
1963 g
.setColor(oldColor
);
1967 private static final int WAVE_HEIGHT
= 2;
1968 private static final int WAVE_SEGMENT_LENGTH
= 4;
1970 private static void drawWave(Graphics g
, int xStart
, int xEnd
, int y
) {
1971 int startSegment
= xStart
/ WAVE_SEGMENT_LENGTH
;
1972 int endSegment
= xEnd
/ WAVE_SEGMENT_LENGTH
;
1973 for (int i
= startSegment
; i
< endSegment
; i
++) {
1974 drawWaveSegment(g
, WAVE_SEGMENT_LENGTH
* i
, y
);
1977 int x
= WAVE_SEGMENT_LENGTH
* endSegment
;
1978 UIUtil
.drawLine(g
, x
, y
+ WAVE_HEIGHT
, x
+ WAVE_SEGMENT_LENGTH
/ 2, y
);
1981 private static void drawWaveSegment(Graphics g
, int x
, int y
) {
1982 UIUtil
.drawLine(g
, x
, y
+ WAVE_HEIGHT
, x
+ WAVE_SEGMENT_LENGTH
/ 2, y
);
1983 UIUtil
.drawLine(g
, x
+ WAVE_SEGMENT_LENGTH
/ 2, y
, x
+ WAVE_SEGMENT_LENGTH
, y
+ WAVE_HEIGHT
);
1986 private int getTextSegmentWidth(CharSequence text
, int xStart
, int fontType
, Rectangle clip
) {
1989 final int textLength
= text
.length();
1990 for (int i
= 0; i
< textLength
&& xStart
< clip
.x
+ clip
.width
; i
++) {
1991 if (text
.charAt(i
) == '\t') {
1992 x
= EditorUtil
.nextTabStop(x
, this);
1995 x
+= EditorUtil
.charWidth(text
.charAt(i
), fontType
, this);
1997 if (x
> clip
.x
+ clip
.width
) {
2004 public int getLineHeight() {
2005 if (myLineHeight
!= -1) return myLineHeight
;
2009 FontMetrics fontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
2010 myLineHeight
= (int)(fontMetrics
.getHeight() * (isOneLineMode() ?
1 : myScheme
.getLineSpacing()));
2011 if (myLineHeight
== 0) {
2012 myLineHeight
= fontMetrics
.getHeight();
2013 if (myLineHeight
== 0) {
2018 return myLineHeight
;
2022 if (myDescent
!= -1) {
2025 FontMetrics fontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
2026 myDescent
= fontMetrics
.getDescent();
2030 FontMetrics
getFontMetrics(int fontType
) {
2031 if (myPlainFontMetrics
== null) {
2032 assertIsDispatchThread();
2033 myPlainFontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
2034 myBoldFontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.BOLD
));
2035 myItalicFontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.ITALIC
));
2036 myBoldItalicFontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.BOLD_ITALIC
));
2039 if (fontType
== Font
.PLAIN
) return myPlainFontMetrics
;
2040 if (fontType
== Font
.BOLD
) return myBoldFontMetrics
;
2041 if (fontType
== Font
.ITALIC
) return myItalicFontMetrics
;
2042 if (fontType
== Font
.BOLD
+ Font
.ITALIC
) return myBoldItalicFontMetrics
;
2044 LOG
.assertTrue(false, "Unknown font type: " + fontType
);
2046 return myPlainFontMetrics
;
2049 private int getCharHeight() {
2050 if (myCharHeight
== -1) {
2051 assertIsDispatchThread();
2052 FontMetrics fontMetrics
= myEditorComponent
.getFontMetrics(myScheme
.getFont(EditorFontType
.PLAIN
));
2053 myCharHeight
= fontMetrics
.charWidth('a');
2055 return myCharHeight
;
2058 public Dimension
getPreferredSize() {
2059 if (ourIsUnitTestMode
&& getUserData(DO_DOCUMENT_UPDATE_TEST
) == null) {
2060 return new Dimension(1, 1);
2063 final Dimension draft
= getSizeWithoutCaret();
2064 final int additionalSpace
= mySettings
.getAdditionalColumnsCount() * EditorUtil
.getSpaceWidth(Font
.PLAIN
, this);
2066 if (!myDocument
.isInBulkUpdate()) {
2067 int caretX
= visualPositionToXY(getCaretModel().getVisualPosition()).x
;
2068 draft
.width
= Math
.max(caretX
, draft
.width
) + additionalSpace
;
2071 draft
.width
+= additionalSpace
;
2076 private Dimension
getSizeWithoutCaret() {
2077 Dimension size
= mySizeContainer
.getContentSize();
2078 if (isOneLineMode()) return new Dimension(size
.width
, getLineHeight());
2079 if (mySettings
.isAdditionalPageAtBottom()) {
2080 int lineHeight
= getLineHeight();
2081 return new Dimension(size
.width
, size
.height
+ Math
.max(getScrollingModel().getVisibleArea().height
- 2 * lineHeight
, lineHeight
));
2084 return getContentSize();
2087 public Dimension
getContentSize() {
2088 Dimension size
= mySizeContainer
.getContentSize();
2089 return new Dimension(size
.width
, size
.height
+ mySettings
.getAdditionalLinesCount() * getLineHeight());
2092 public JScrollPane
getScrollPane() {
2093 return myScrollPane
;
2096 public int logicalPositionToOffset(@NotNull LogicalPosition pos
) {
2098 if (myDocument
.getLineCount() == 0) return 0;
2100 if (pos
.line
< 0) throw new IndexOutOfBoundsException("Wrong line: " + pos
.line
);
2101 if (pos
.column
< 0) throw new IndexOutOfBoundsException("Wrong column:" + pos
.column
);
2103 if (pos
.line
>= myDocument
.getLineCount()) {
2104 return myDocument
.getTextLength();
2107 int start
= myDocument
.getLineStartOffset(pos
.line
);
2108 int end
= myDocument
.getLineEndOffset(pos
.line
);
2110 CharSequence text
= myDocument
.getCharsNoThreadCheck();
2112 if (pos
.column
== 0) return start
;
2113 return EditorUtil
.calcOffset(this, text
, start
, end
, pos
.column
, EditorUtil
.getTabSize(this));
2116 public void setLastColumnNumber(int val
) {
2117 assertIsDispatchThread();
2118 myLastColumnNumber
= val
;
2121 public int getLastColumnNumber() {
2123 return myLastColumnNumber
;
2126 int getVisibleLineCount() {
2127 int line
= getDocument().getLineCount();
2128 line
-= myFoldingModel
.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
2133 public VisualPosition
logicalToVisualPosition(@NotNull LogicalPosition logicalPos
) {
2135 if (!myFoldingModel
.isFoldingEnabled()) return new VisualPosition(logicalPos
.line
, logicalPos
.column
);
2137 int offset
= logicalPositionToOffset(logicalPos
);
2139 FoldRegion outermostCollapsed
= myFoldingModel
.getCollapsedRegionAtOffset(offset
);
2140 if (outermostCollapsed
!= null && offset
> outermostCollapsed
.getStartOffset()) {
2141 if (offset
< getDocument().getTextLength()) {
2142 offset
= outermostCollapsed
.getStartOffset();
2143 LogicalPosition foldStart
= offsetToLogicalPosition(offset
);
2144 return logicalToVisualPosition(foldStart
);
2147 offset
= outermostCollapsed
.getEndOffset() + 3; // WTF?
2151 int line
= logicalPos
.line
;
2152 int column
= logicalPos
.column
;
2154 line
-= myFoldingModel
.getFoldedLinesCountBefore(offset
);
2156 FoldRegion
[] toplevel
= myFoldingModel
.fetchTopLevel();
2157 for (int idx
= myFoldingModel
.getLastTopLevelIndexBefore(offset
); idx
>= 0; idx
--) {
2158 FoldRegion region
= toplevel
[idx
];
2159 if (region
.isValid()) {
2160 if (region
.getDocument().getLineNumber(region
.getEndOffset()) == logicalPos
.line
&& region
.getEndOffset() <= offset
) {
2161 LogicalPosition foldStart
= offsetToLogicalPosition(region
.getStartOffset());
2162 LogicalPosition foldEnd
= offsetToLogicalPosition(region
.getEndOffset());
2163 column
+= foldStart
.column
+ region
.getPlaceholderText().length() - foldEnd
.column
;
2164 offset
= region
.getStartOffset();
2165 logicalPos
= foldStart
;
2173 LOG
.assertTrue(line
>= 0);
2175 return new VisualPosition(line
, Math
.max(0, column
));
2179 private FoldRegion
getLastCollapsedBeforePosition(VisualPosition visual
) {
2180 FoldRegion
[] topLevelCollapsed
= myFoldingModel
.fetchTopLevel();
2182 if (topLevelCollapsed
== null) return null;
2185 int end
= topLevelCollapsed
.length
- 1;
2188 while (start
<= end
) {
2189 i
= (start
+ end
) / 2;
2190 FoldRegion region
= topLevelCollapsed
[i
];
2191 LogicalPosition logFoldEnd
= offsetToLogicalPosition(region
.getEndOffset() - 1);
2192 VisualPosition visFoldEnd
= logicalToVisualPosition(logFoldEnd
);
2193 if (visFoldEnd
.line
< visual
.line
) {
2197 if (visFoldEnd
.line
> visual
.line
) {
2201 if (visFoldEnd
.column
< visual
.column
) {
2205 if (visFoldEnd
.column
> visual
.column
) {
2217 while (i
>= 0 && i
< topLevelCollapsed
.length
) {
2218 if (topLevelCollapsed
[i
].isValid()) break;
2222 if (i
>= 0 && i
< topLevelCollapsed
.length
) {
2223 FoldRegion region
= topLevelCollapsed
[i
];
2224 LogicalPosition logFoldEnd
= offsetToLogicalPosition(region
.getEndOffset() - 1);
2225 VisualPosition visFoldEnd
= logicalToVisualPosition(logFoldEnd
);
2226 if (visFoldEnd
.line
> visual
.line
|| visFoldEnd
.line
== visual
.line
&& visFoldEnd
.column
> visual
.column
) {
2229 return topLevelCollapsed
[i
];
2242 public LogicalPosition
visualToLogicalPosition(@NotNull VisualPosition visiblePos
) {
2244 if (!myFoldingModel
.isFoldingEnabled()) return new LogicalPosition(visiblePos
.line
, visiblePos
.column
);
2246 int line
= visiblePos
.line
;
2247 int column
= visiblePos
.column
;
2249 FoldRegion lastCollapsedBefore
= getLastCollapsedBeforePosition(visiblePos
);
2251 if (lastCollapsedBefore
!= null) {
2252 LogicalPosition logFoldEnd
= offsetToLogicalPosition(lastCollapsedBefore
.getEndOffset());
2253 VisualPosition visFoldEnd
= logicalToVisualPosition(logFoldEnd
);
2255 line
= logFoldEnd
.line
+ (visiblePos
.line
- visFoldEnd
.line
);
2256 if (visFoldEnd
.line
== visiblePos
.line
) {
2257 if (visiblePos
.column
>= visFoldEnd
.column
) {
2258 column
= logFoldEnd
.column
+ (visiblePos
.column
- visFoldEnd
.column
);
2261 return offsetToLogicalPosition(lastCollapsedBefore
.getStartOffset());
2266 if (column
< 0) column
= 0;
2268 return new LogicalPosition(line
, column
);
2271 private int calcLogicalLineNumber(int offset
) {
2272 int textLength
= myDocument
.getTextLength();
2273 if (textLength
== 0) return 0;
2275 if (offset
> textLength
|| offset
< 0) {
2276 throw new IndexOutOfBoundsException("Wrong offset: " + offset
+ " textLength: " + textLength
);
2279 int lineIndex
= myDocument
.getLineNumber(offset
);
2281 LOG
.assertTrue(lineIndex
>= 0 && lineIndex
< myDocument
.getLineCount());
2286 private int calcColumnNumber(int offset
, int lineIndex
) {
2287 if (myDocument
.getTextLength() == 0) return 0;
2289 CharSequence text
= myDocument
.getCharsSequence();
2290 int start
= myDocument
.getLineStartOffset(lineIndex
);
2291 if (start
== offset
) return 0;
2292 return EditorUtil
.calcColumnNumber(this, text
, start
, offset
, EditorUtil
.getTabSize(this));
2295 private void moveCaretToScreenPos(int x
, int y
) {
2300 LogicalPosition pos
= xyToLogicalPosition(new Point(x
, y
));
2302 int columnNumber
= pos
.column
;
2303 int lineNumber
= pos
.line
;
2305 if (lineNumber
< 0) {
2310 final int totalLines
= myDocument
.getLineCount();
2311 if (totalLines
<= 0) {
2312 getCaretModel().moveToOffset(0);
2316 if (lineNumber
>= totalLines
) {
2317 moveCaretToScreenPos(x
, logicalLineToY(totalLines
- 1));
2321 if (!mySettings
.isVirtualSpace()) {
2322 int lineEndOffset
= myDocument
.getLineEndOffset(lineNumber
);
2323 int lineEndColumnNumber
= calcColumnNumber(lineEndOffset
, lineNumber
);
2324 if (columnNumber
> lineEndColumnNumber
) {
2325 columnNumber
= lineEndColumnNumber
;
2329 if (!mySettings
.isCaretInsideTabs()) {
2330 int offset
= logicalPositionToOffset(new LogicalPosition(lineNumber
, columnNumber
));
2331 CharSequence text
= myDocument
.getCharsSequence();
2332 if (offset
>= 0 && offset
< myDocument
.getTextLength()) {
2333 if (text
.charAt(offset
) == '\t') {
2334 columnNumber
= calcColumnNumber(offset
, lineNumber
);
2338 LogicalPosition pos1
= new LogicalPosition(lineNumber
, columnNumber
);
2339 getCaretModel().moveToLogicalPosition(pos1
);
2342 private boolean checkIgnore(MouseEvent e
, boolean isFinalCheck
) {
2343 if (!myIgnoreMouseEventsConsecutiveToInitial
) {
2344 myInitialMouseEvent
= null;
2348 if (e
.getComponent() != myInitialMouseEvent
.getComponent() || !e
.getPoint().equals(myInitialMouseEvent
.getPoint())) {
2349 myIgnoreMouseEventsConsecutiveToInitial
= false;
2350 myInitialMouseEvent
= null;
2355 myIgnoreMouseEventsConsecutiveToInitial
= false;
2356 myInitialMouseEvent
= null;
2364 private void processMouseReleased(MouseEvent e
) {
2365 if (checkIgnore(e
, true)) return;
2367 if (e
.getSource() == myGutterComponent
) {
2368 myGutterComponent
.mouseReleased(e
);
2371 if (getMouseEventArea(e
) != EditorMouseEventArea
.EDITING_AREA
|| e
.getY() < 0 || e
.getX() < 0) {
2375 // if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
2376 final FoldRegion region
= ((FoldingModelEx
)getFoldingModel()).getFoldingPlaceholderAt(e
.getPoint());
2377 if (e
.getX() >= 0 && e
.getY() >= 0 && region
!= null && region
== myMouseSelectedRegion
) {
2378 getFoldingModel().runBatchFoldingOperation(new Runnable() {
2380 myFoldingModel
.flushCaretShift();
2381 region
.setExpanded(true);
2386 if (myMousePressedEvent
!= null && myMousePressedEvent
.getClickCount() == 1 && myMousePressedInsideSelection
) {
2387 getSelectionModel().removeSelection();
2391 public DataContext
getDataContext() {
2392 return getProjectAwareDataContext(DataManager
.getInstance().getDataContext(getContentComponent()));
2395 private DataContext
getProjectAwareDataContext(final DataContext original
) {
2396 if (PlatformDataKeys
.PROJECT
.getData(original
) == myProject
) return original
;
2398 return new DataContext() {
2399 public Object
getData(String dataId
) {
2400 if (PlatformDataKeys
.PROJECT
.is(dataId
)) {
2403 return original
.getData(dataId
);
2409 public EditorMouseEventArea
getMouseEventArea(@NotNull MouseEvent e
) {
2410 if (myGutterComponent
!= e
.getSource()) return EditorMouseEventArea
.EDITING_AREA
;
2412 int x
= myGutterComponent
.convertX(e
.getX());
2414 if (x
>= myGutterComponent
.getLineNumberAreaOffset() &&
2415 x
< myGutterComponent
.getLineNumberAreaOffset() + myGutterComponent
.getLineNumberAreaWidth()) {
2416 return EditorMouseEventArea
.LINE_NUMBERS_AREA
;
2419 if (x
>= myGutterComponent
.getAnnotationsAreaOffset() &&
2420 x
<= myGutterComponent
.getAnnotationsAreaOffset() + myGutterComponent
.getAnnotationsAreaWidth()) {
2421 return EditorMouseEventArea
.ANNOTATIONS_AREA
;
2424 if (x
>= myGutterComponent
.getLineMarkerAreaOffset() &&
2425 x
< myGutterComponent
.getLineMarkerAreaOffset() + myGutterComponent
.getLineMarkerAreaWidth()) {
2426 return EditorMouseEventArea
.LINE_MARKERS_AREA
;
2429 if (x
>= myGutterComponent
.getFoldingAreaOffset() &&
2430 x
< myGutterComponent
.getFoldingAreaOffset() + myGutterComponent
.getFoldingAreaWidth()) {
2431 return EditorMouseEventArea
.FOLDING_OUTLINE_AREA
;
2437 private void requestFocus() {
2438 myEditorComponent
.requestFocus();
2441 private void validateMousePointer(MouseEvent e
) {
2442 if (e
.getSource() == myGutterComponent
) {
2443 FoldRegion foldingAtCursor
= myGutterComponent
.findFoldingAnchorAt(e
.getX(), e
.getY());
2444 myGutterComponent
.setActiveFoldRegion(foldingAtCursor
);
2445 if (foldingAtCursor
!= null) {
2446 myGutterComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
2449 myGutterComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.DEFAULT_CURSOR
));
2453 myGutterComponent
.setActiveFoldRegion(null);
2454 if (getSelectionModel().hasSelection() && (e
.getModifiersEx() & (InputEvent
.BUTTON1_DOWN_MASK
| InputEvent
.BUTTON2_DOWN_MASK
)) == 0) {
2455 int offset
= logicalPositionToOffset(xyToLogicalPosition(e
.getPoint()));
2456 if (getSelectionModel().getSelectionStart() <= offset
&& offset
< getSelectionModel().getSelectionEnd()) {
2457 myEditorComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.DEFAULT_CURSOR
));
2461 myEditorComponent
.setCursor(Cursor
.getPredefinedCursor(Cursor
.TEXT_CURSOR
));
2465 private void runMouseDraggedCommand(final MouseEvent e
) {
2466 if (myCommandProcessor
== null || myMousePressedEvent
!= null && myMousePressedEvent
.isConsumed()) {
2469 myCommandProcessor
.executeCommand(myProject
, new Runnable() {
2471 processMouseDragged(e
);
2473 }, "", MOUSE_DRAGGED_GROUP
, UndoConfirmationPolicy
.DEFAULT
, getDocument());
2476 private void processMouseDragged(MouseEvent e
) {
2477 if (SwingUtilities
.isRightMouseButton(e
)) {
2480 Rectangle rect
= getScrollingModel().getVisibleArea();
2484 if (e
.getSource() == myGutterComponent
) {
2489 if (x
< rect
.x
&& rect
.x
> 0) {
2493 if (x
> rect
.x
+ rect
.width
) {
2494 dx
= x
- rect
.x
- rect
.width
;
2500 if (y
< rect
.y
&& rect
.y
> 0) {
2504 if (y
> rect
.y
+ rect
.height
) {
2505 dy
= y
- rect
.y
- rect
.height
;
2508 if (dx
== 0 && dy
== 0) {
2509 myScrollingTimer
.stop();
2511 SelectionModel selectionModel
= getSelectionModel();
2512 int oldSelectionStart
= selectionModel
.getLeadSelectionOffset();
2513 int oldCaretOffset
= getCaretModel().getOffset();
2514 LogicalPosition oldLogicalCaret
= getCaretModel().getLogicalPosition();
2515 moveCaretToScreenPos(x
, y
);
2516 getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
2518 int newCaretOffset
= getCaretModel().getOffset();
2519 int caretShift
= newCaretOffset
- mySavedSelectionStart
;
2521 if (myMousePressedEvent
!= null && getMouseEventArea(myMousePressedEvent
) != EditorMouseEventArea
.EDITING_AREA
&&
2522 getMouseEventArea(myMousePressedEvent
) != EditorMouseEventArea
.LINE_NUMBERS_AREA
) {
2523 selectionModel
.setSelection(oldSelectionStart
, newCaretOffset
);
2526 if (isColumnMode() || e
.isAltDown()) {
2527 final LogicalPosition blockStart
= selectionModel
.hasBlockSelection() ? selectionModel
.getBlockStart() : oldLogicalCaret
;
2528 selectionModel
.setBlockSelection(blockStart
, getCaretModel().getLogicalPosition());
2531 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE
) {
2532 if (caretShift
< 0) {
2533 int newSelection
= newCaretOffset
;
2534 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2535 newSelection
= mySelectionModel
.getWordAtCaretStart();
2538 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2540 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
, 0)));
2543 if (newSelection
< 0) newSelection
= newCaretOffset
;
2544 selectionModel
.setSelection(mySavedSelectionEnd
, newSelection
);
2545 getCaretModel().moveToOffset(newSelection
);
2548 int newSelection
= newCaretOffset
;
2549 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2550 newSelection
= mySelectionModel
.getWordAtCaretEnd();
2553 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2555 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
+ 1, 0)));
2558 if (newSelection
< 0) newSelection
= newCaretOffset
;
2559 selectionModel
.setSelection(mySavedSelectionStart
, newSelection
);
2560 getCaretModel().moveToOffset(newSelection
);
2565 if (!myMousePressedInsideSelection
) {
2566 selectionModel
.setSelection(oldSelectionStart
, newCaretOffset
);
2569 if (caretShift
!= 0) {
2570 if (myMousePressedEvent
!= null) {
2571 if (mySettings
.isDndEnabled()) {
2572 boolean isCopy
= UIUtil
.isControlKeyDown(e
) || isViewer() || !getDocument().isWritable();
2573 mySavedCaretOffsetForDNDUndoHack
= oldCaretOffset
;
2574 getContentComponent().getTransferHandler()
2575 .exportAsDrag(getContentComponent(), e
, isCopy ? TransferHandler
.COPY
: TransferHandler
.MOVE
);
2578 selectionModel
.removeSelection();
2580 myMousePressedEvent
= null;
2588 myScrollingTimer
.start(dx
, dy
);
2592 private static class RepaintCursorCommand
implements Runnable
{
2593 private long mySleepTime
= 500;
2594 private boolean myIsBlinkCaret
= true;
2595 private EditorImpl myEditor
= null;
2596 private final MyRepaintRunnable myRepaintRunnable
;
2597 private ScheduledFuture
<?
> mySchedulerHandle
;
2599 private RepaintCursorCommand() {
2600 myRepaintRunnable
= new MyRepaintRunnable();
2603 private class MyRepaintRunnable
implements Runnable
{
2605 if (myEditor
!= null) {
2606 myEditor
.myCaretCursor
.repaint();
2611 public void start() {
2612 if (mySchedulerHandle
!= null) {
2613 mySchedulerHandle
.cancel(false);
2615 mySchedulerHandle
= JobScheduler
.getScheduler().scheduleAtFixedRate(this, mySleepTime
, mySleepTime
, TimeUnit
.MILLISECONDS
);
2618 private void setBlinkPeriod(int blinkPeriod
) {
2619 mySleepTime
= blinkPeriod
> 10 ? blinkPeriod
: 10;
2623 private void setBlinkCaret(boolean value
) {
2624 myIsBlinkCaret
= value
;
2628 if (myEditor
!= null) {
2629 CaretCursor activeCursor
= myEditor
.myCaretCursor
;
2631 long time
= System
.currentTimeMillis();
2632 time
-= activeCursor
.myStartTime
;
2634 if (time
> mySleepTime
) {
2635 boolean toRepaint
= true;
2636 if (myIsBlinkCaret
) {
2637 activeCursor
.myIsShown
= !activeCursor
.myIsShown
;
2640 toRepaint
= !activeCursor
.myIsShown
;
2641 activeCursor
.myIsShown
= true;
2645 SwingUtilities
.invokeLater(myRepaintRunnable
);
2652 void updateCaretCursor() {
2653 if (!ourIsUnitTestMode
&& !IJSwingUtilities
.hasFocus(getContentComponent())) {
2654 stopOptimizedScrolling();
2657 if (myCursorUpdater
== null) {
2658 myCursorUpdater
= new Runnable() {
2660 if (myCursorUpdater
== null) return;
2661 myCursorUpdater
= null;
2662 VisualPosition caretPosition
= getCaretModel().getVisualPosition();
2663 Point pos1
= visualPositionToXY(caretPosition
);
2664 Point pos2
= visualPositionToXY(new VisualPosition(caretPosition
.line
, caretPosition
.column
+ 1));
2665 myCaretCursor
.setPosition(pos1
, pos2
.x
- pos1
.x
);
2671 public boolean setCaretVisible(boolean b
) {
2672 myCaretCursor
.setVisible(b
);
2673 boolean old
= myCaretCursor
.isActive();
2675 myCaretCursor
.activate(true);
2678 myCaretCursor
.passivate();
2683 public void addFocusListener(FocusChangeListener listener
) {
2684 myFocusListeners
.add(listener
);
2687 public Project
getProject() {
2691 public boolean isOneLineMode() {
2692 return myIsOneLineMode
;
2695 public boolean isEmbeddedIntoDialogWrapper() {
2696 return myEmbeddedIntoDialogWrapper
;
2699 public void setEmbeddedIntoDialogWrapper(boolean b
) {
2700 assertIsDispatchThread();
2702 myEmbeddedIntoDialogWrapper
= b
;
2703 myScrollPane
.setFocusable(!b
);
2704 myEditorComponent
.setFocusCycleRoot(!b
);
2705 myEditorComponent
.setFocusable(b
);
2708 public void setOneLineMode(boolean isOneLineMode
) {
2709 myIsOneLineMode
= isOneLineMode
;
2710 getScrollPane().setInputMap(JComponent
.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
, null);
2714 public void stopOptimizedScrolling() {
2715 myEditorComponent
.setOpaque(false);
2718 private void startOptimizedScrolling() {
2719 myEditorComponent
.setOpaque(true);
2722 private class CaretCursor
{
2723 private Point myLocation
;
2724 private int myWidth
;
2725 private boolean myIsVisible
;
2727 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
2728 private boolean myIsShown
= false;
2729 private long myStartTime
= 0;
2731 private CaretCursor() {
2732 myLocation
= new Point(0, 0);
2736 public boolean isVisible() {
2740 public void setVisible(boolean visible
) {
2741 myIsVisible
= visible
;
2744 private void activate(boolean enforceVisible
) {
2745 if (enforceVisible
) {
2749 if (!myIsVisible
) return;
2750 final boolean blink
= mySettings
.isBlinkCaret();
2751 final int blinkPeriod
= mySettings
.getCaretBlinkPeriod();
2752 synchronized (ourCaretBlinkingCommand
) {
2753 ourCaretBlinkingCommand
.myEditor
= EditorImpl
.this;
2754 ourCaretBlinkingCommand
.setBlinkCaret(blink
);
2755 ourCaretBlinkingCommand
.setBlinkPeriod(blinkPeriod
);
2760 public boolean isActive() {
2761 synchronized (ourCaretBlinkingCommand
) {
2766 private void passivate() {
2767 synchronized (ourCaretBlinkingCommand
) {
2772 private void setPosition(Point location
, int width
) {
2773 myStartTime
= System
.currentTimeMillis();
2774 myLocation
= location
;
2775 myIsShown
= myIsVisible
;
2776 myWidth
= Math
.max(width
, 2);
2780 private void repaint() {
2781 myEditorComponent
.repaintEditorComponent(myLocation
.x
, myLocation
.y
, myWidth
, getLineHeight());
2784 private void paint(Graphics g
) {
2785 if (!myIsShown
|| !IJSwingUtilities
.hasFocus(getContentComponent()) || isRendererMode()) return;
2787 int x
= myLocation
.x
;
2788 int lineHeight
= getLineHeight();
2789 int y
= myLocation
.y
;
2791 Rectangle viewRect
= getScrollingModel().getVisibleArea();
2792 if (x
- viewRect
.x
< 0) {
2797 g
.setColor(myScheme
.getColor(EditorColors
.CARET_COLOR
));
2799 if (myIsInsertMode
!= mySettings
.isBlockCursor()) {
2800 for (int i
= 0; i
< mySettings
.getLineCursorWidth(); i
++) {
2801 UIUtil
.drawLine(g
, x
+ i
, y
, x
+ i
, y
+ lineHeight
- 1);
2805 Color background
= myScheme
.getColor(EditorColors
.CARET_ROW_COLOR
);
2806 if (background
== null) background
= getBackroundColor();
2807 g
.setXORMode(background
);
2809 g
.fillRect(x
, y
, myWidth
, lineHeight
- 1);
2816 private class ScrollingTimer
{
2818 private static final int TIMER_PERIOD
= 100;
2819 private static final int CYCLE_SIZE
= 20;
2820 private int myXCycles
;
2821 private int myYCycles
;
2824 private int xPassedCycles
= 0;
2825 private int yPassedCycles
= 0;
2827 private void start(int dx
, int dy
) {
2831 myXCycles
= CYCLE_SIZE
/ dx
+ 1;
2832 myDx
= 1 + dx
/ CYCLE_SIZE
;
2836 myXCycles
= -CYCLE_SIZE
/ dx
+ 1;
2837 myDx
= -1 + dx
/ CYCLE_SIZE
;
2842 myYCycles
= CYCLE_SIZE
/ dy
+ 1;
2843 myDy
= 1 + dy
/ CYCLE_SIZE
;
2847 myYCycles
= -CYCLE_SIZE
/ dy
+ 1;
2848 myDy
= -1 + dy
/ CYCLE_SIZE
;
2852 if (myTimer
!= null) {
2857 myTimer
= new Timer(TIMER_PERIOD
, new ActionListener() {
2858 public void actionPerformed(ActionEvent e
) {
2859 myCommandProcessor
.executeCommand(myProject
, new DocumentRunnable(myDocument
, myProject
) {
2861 int oldSelectionStart
= mySelectionModel
.getLeadSelectionOffset();
2862 LogicalPosition caretPosition
= getCaretModel().getLogicalPosition();
2863 int columnNumber
= caretPosition
.column
;
2865 if (xPassedCycles
>= myXCycles
) {
2867 columnNumber
+= myDx
;
2870 int lineNumber
= caretPosition
.line
;
2872 if (yPassedCycles
>= myYCycles
) {
2877 LogicalPosition pos
= new LogicalPosition(lineNumber
, columnNumber
);
2878 getCaretModel().moveToLogicalPosition(pos
);
2879 getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
2881 int newCaretOffset
= getCaretModel().getOffset();
2882 int caretShift
= newCaretOffset
- mySavedSelectionStart
;
2884 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE
) {
2885 if (caretShift
< 0) {
2886 int newSelection
= newCaretOffset
;
2887 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2888 newSelection
= mySelectionModel
.getWordAtCaretStart();
2891 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2893 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
, 0)));
2896 if (newSelection
< 0) newSelection
= newCaretOffset
;
2897 mySelectionModel
.setSelection(validateOffset(mySavedSelectionEnd
), newSelection
);
2898 getCaretModel().moveToOffset(newSelection
);
2901 int newSelection
= newCaretOffset
;
2902 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED
) {
2903 newSelection
= mySelectionModel
.getWordAtCaretEnd();
2906 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED
) {
2907 newSelection
= logicalPositionToOffset(
2908 visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line
+ 1, 0)));
2911 if (newSelection
< 0) newSelection
= newCaretOffset
;
2912 mySelectionModel
.setSelection(validateOffset(mySavedSelectionStart
), newSelection
);
2913 getCaretModel().moveToOffset(newSelection
);
2918 if (mySelectionModel
.hasBlockSelection()) {
2919 mySelectionModel
.setBlockSelection(mySelectionModel
.getBlockStart(), getCaretModel().getLogicalPosition());
2922 mySelectionModel
.setSelection(oldSelectionStart
, getCaretModel().getOffset());
2925 }, EditorBundle
.message("move.cursor.command.name"), DocCommandGroupId
.noneGroupId(getDocument()), UndoConfirmationPolicy
.DEFAULT
, getDocument());
2931 private void stop() {
2932 if (myTimer
!= null) {
2938 private int validateOffset(int offset
) {
2939 if (offset
< 0) return 0;
2940 if (offset
> myDocument
.getTextLength()) return myDocument
.getTextLength();
2945 class MyScrollBar
extends JScrollBar
{
2946 @NonNls private static final String DECR_BUTTON_FIELD
= "decrButton";
2947 @NonNls private static final String INCR_BUTTON_FIELD
= "incrButton";
2948 @NonNls private static final String APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS
= "apple.laf.AquaScrollBarUI";
2950 private MyScrollBar(int orientation
) {
2952 setFocusable(false);
2953 putClientProperty("JScrollBar.fastWheelScrolling", Boolean
.TRUE
); // fast scrolling for JDK 6
2957 * This is helper method. It returns height of the top (descrease) scrollbar
2958 * button. Please note, that it's possible to return real height only if scrollbar
2959 * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
2962 int getDecScrollButtonHeight() {
2963 ScrollBarUI barUI
= getUI();
2964 Insets insets
= getInsets();
2965 if (barUI
instanceof BasicScrollBarUI
) {
2967 Field decrButtonField
= BasicScrollBarUI
.class.getDeclaredField(DECR_BUTTON_FIELD
);
2968 decrButtonField
.setAccessible(true);
2969 JButton decrButtonValue
= (JButton
)decrButtonField
.get(barUI
);
2970 LOG
.assertTrue(decrButtonValue
!= null);
2971 return insets
.top
+ decrButtonValue
.getHeight();
2973 catch (Exception exc
) {
2974 throw new IllegalStateException(exc
.getMessage());
2978 return insets
.top
+ 15;
2983 * This is helper method. It returns height of the bottom (increase) scrollbar
2984 * button. Please note, that it's possible to return real height only if scrollbar
2985 * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
2988 int getIncScrollButtonHeight() {
2989 ScrollBarUI barUI
= getUI();
2990 Insets insets
= getInsets();
2991 if (barUI
instanceof BasicScrollBarUI
) {
2993 Field incrButtonField
= BasicScrollBarUI
.class.getDeclaredField(INCR_BUTTON_FIELD
);
2994 incrButtonField
.setAccessible(true);
2995 JButton incrButtonValue
= (JButton
)incrButtonField
.get(barUI
);
2996 LOG
.assertTrue(incrButtonValue
!= null);
2997 return insets
.bottom
+ incrButtonValue
.getHeight();
2999 catch (Exception exc
) {
3000 throw new IllegalStateException(exc
.getMessage());
3003 else if (APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS
.equals(barUI
.getClass().getName())) {
3004 return insets
.bottom
+ 30;
3007 return insets
.bottom
+ 15;
3011 public int getUnitIncrement(int direction
) {
3012 JViewport vp
= myScrollPane
.getViewport();
3013 Rectangle vr
= vp
.getViewRect();
3014 return myEditorComponent
.getScrollableUnitIncrement(vr
, SwingConstants
.VERTICAL
, direction
);
3017 public int getBlockIncrement(int direction
) {
3018 JViewport vp
= myScrollPane
.getViewport();
3019 Rectangle vr
= vp
.getViewRect();
3020 return myEditorComponent
.getScrollableBlockIncrement(vr
, SwingConstants
.VERTICAL
, direction
);
3024 private MyEditable
getViewer() {
3025 if (myEditable
== null) {
3026 myEditable
= new MyEditable();
3031 public CopyProvider
getCopyProvider() {
3035 public CutProvider
getCutProvider() {
3039 public PasteProvider
getPasteProvider() {
3044 public DeleteProvider
getDeleteProvider() {
3048 private class MyEditable
implements CutProvider
, CopyProvider
, PasteProvider
, DeleteProvider
{
3049 public void performCopy(DataContext dataContext
) {
3050 executeAction(IdeActions
.ACTION_EDITOR_COPY
, dataContext
);
3053 public boolean isCopyEnabled(DataContext dataContext
) {
3057 public boolean isCopyVisible(DataContext dataContext
) {
3058 return getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection();
3061 public void performCut(DataContext dataContext
) {
3062 executeAction(IdeActions
.ACTION_EDITOR_CUT
, dataContext
);
3065 public boolean isCutEnabled(DataContext dataContext
) {
3066 return !isViewer() && getDocument().isWritable();
3069 public boolean isCutVisible(DataContext dataContext
) {
3070 return getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection();
3073 public void performPaste(DataContext dataContext
) {
3074 executeAction(IdeActions
.ACTION_EDITOR_PASTE
, dataContext
);
3077 public boolean isPastePossible(DataContext dataContext
) {
3078 // Copy of isPasteEnabled. See interface method javadoc.
3079 return !isViewer() && getDocument().isWritable();
3082 public boolean isPasteEnabled(DataContext dataContext
) {
3083 return !isViewer() && getDocument().isWritable();
3086 public void deleteElement(DataContext dataContext
) {
3087 executeAction(IdeActions
.ACTION_EDITOR_DELETE
, dataContext
);
3090 public boolean canDeleteElement(DataContext dataContext
) {
3091 return !isViewer() && getDocument().isWritable();
3094 private void executeAction(String actionId
, DataContext dataContext
) {
3095 EditorAction action
= (EditorAction
)ActionManager
.getInstance().getAction(actionId
);
3096 if (action
!= null) {
3097 action
.actionPerformed(EditorImpl
.this, dataContext
);
3102 public void setColorsScheme(@NotNull EditorColorsScheme scheme
) {
3103 assertIsDispatchThread();
3109 public EditorColorsScheme
getColorsScheme() {
3114 void assertIsDispatchThread() {
3115 ApplicationManagerEx
.getApplicationEx().assertIsDispatchThread(myEditorComponent
);
3117 private static void assertReadAccess() {
3118 ApplicationManagerEx
.getApplicationEx().assertReadAccessAllowed();
3121 public void setVerticalScrollbarOrientation(int type
) {
3122 assertIsDispatchThread();
3123 int currentHorOffset
= myScrollingModel
.getHorizontalScrollOffset();
3124 myScrollbarOrientation
= type
;
3125 if (type
== VERTICAL_SCROLLBAR_LEFT
) {
3126 myScrollPane
.setLayout(new LeftHandScrollbarLayout());
3129 myScrollPane
.setLayout(new ScrollPaneLayout());
3131 myScrollingModel
.scrollHorizontally(currentHorOffset
);
3134 public void setVerticalScrollbarVisible(boolean b
) {
3136 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_ALWAYS
);
3139 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_NEVER
);
3143 public void setHorizontalScrollbarVisible(boolean b
) {
3145 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
3148 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_NEVER
);
3152 int getVerticalScrollbarOrientation() {
3153 return myScrollbarOrientation
;
3156 MyScrollBar
getVerticalScrollBar() {
3157 return myVerticalScrollBar
;
3164 private int getMouseSelectionState() {
3165 return myMouseSelectionState
;
3168 private void setMouseSelectionState(int mouseSelectionState
) {
3169 myMouseSelectionState
= mouseSelectionState
;
3170 myMouseSelectionChangeTimestamp
= System
.currentTimeMillis();
3174 void replaceInputMethodText(InputMethodEvent e
) {
3175 getInputMethodRequests();
3176 myInputMethodRequestsHandler
.replaceInputMethodText(e
);
3179 void inputMethodCaretPositionChanged(InputMethodEvent e
) {
3180 getInputMethodRequests();
3181 myInputMethodRequestsHandler
.setInputMethodCaretPosition(e
);
3184 InputMethodRequests
getInputMethodRequests() {
3185 if (myInputMethodRequestsHandler
== null) {
3186 myInputMethodRequestsHandler
= new MyInputMethodHandler();
3187 myInputMethodRequestsSwingWrapper
= new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler
);
3189 return myInputMethodRequestsSwingWrapper
;
3192 public boolean processKeyTyped(KeyEvent e
) {
3193 if (e
.getID() != KeyEvent
.KEY_TYPED
) return false;
3194 char c
= e
.getKeyChar();
3195 if (UIUtil
.isReallyTypedEvent(e
)) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
3204 void beforeModalityStateChanged() {
3205 myScrollingModel
.beforeModalityStateChanged();
3208 public EditorDropHandler
getDropHandler() {
3209 return myDropHandler
;
3212 public void setDropHandler(EditorDropHandler dropHandler
) {
3213 myDropHandler
= dropHandler
;
3216 private static class MyInputMethodHandleSwingThreadWrapper
implements InputMethodRequests
{
3217 private final InputMethodRequests myDelegate
;
3219 private MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate
) {
3220 myDelegate
= delegate
;
3223 public Rectangle
getTextLocation(final TextHitInfo offset
) {
3224 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getTextLocation(offset
);
3226 final Rectangle
[] r
= new Rectangle
[1];
3228 GuiUtils
.invokeAndWait(new Runnable() {
3230 r
[0] = myDelegate
.getTextLocation(offset
);
3234 catch (InterruptedException e
) {
3237 catch (InvocationTargetException e
) {
3243 public TextHitInfo
getLocationOffset(final int x
, final int y
) {
3244 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getLocationOffset(x
, y
);
3246 final TextHitInfo
[] r
= new TextHitInfo
[1];
3248 GuiUtils
.invokeAndWait(new Runnable() {
3250 r
[0] = myDelegate
.getLocationOffset(x
, y
);
3254 catch (InterruptedException e
) {
3257 catch (InvocationTargetException e
) {
3263 public int getInsertPositionOffset() {
3264 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getInsertPositionOffset();
3266 final int[] r
= new int[1];
3268 GuiUtils
.invokeAndWait(new Runnable() {
3270 r
[0] = myDelegate
.getInsertPositionOffset();
3274 catch (InterruptedException e
) {
3277 catch (InvocationTargetException e
) {
3283 public AttributedCharacterIterator
getCommittedText(final int beginIndex
,
3285 final AttributedCharacterIterator
.Attribute
[] attributes
) {
3286 if (ApplicationManager
.getApplication().isDispatchThread()) {
3287 return myDelegate
.getCommittedText(beginIndex
, endIndex
, attributes
);
3289 final AttributedCharacterIterator
[] r
= new AttributedCharacterIterator
[1];
3291 GuiUtils
.invokeAndWait(new Runnable() {
3293 r
[0] = myDelegate
.getCommittedText(beginIndex
, endIndex
, attributes
);
3297 catch (InterruptedException e
) {
3300 catch (InvocationTargetException e
) {
3306 public int getCommittedTextLength() {
3307 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getCommittedTextLength();
3308 final int[] r
= new int[1];
3310 GuiUtils
.invokeAndWait(new Runnable() {
3312 r
[0] = myDelegate
.getCommittedTextLength();
3316 catch (InterruptedException e
) {
3319 catch (InvocationTargetException e
) {
3325 public AttributedCharacterIterator
cancelLatestCommittedText(AttributedCharacterIterator
.Attribute
[] attributes
) {
3329 public AttributedCharacterIterator
getSelectedText(final AttributedCharacterIterator
.Attribute
[] attributes
) {
3330 if (ApplicationManager
.getApplication().isDispatchThread()) return myDelegate
.getSelectedText(attributes
);
3332 final AttributedCharacterIterator
[] r
= new AttributedCharacterIterator
[1];
3334 GuiUtils
.invokeAndWait(new Runnable() {
3336 r
[0] = myDelegate
.getSelectedText(attributes
);
3340 catch (InterruptedException e
) {
3343 catch (InvocationTargetException e
) {
3350 private class MyInputMethodHandler
implements InputMethodRequests
{
3351 private String composedText
;
3352 private int composedTextStart
;
3353 private int composedTextEnd
;
3355 public Rectangle
getTextLocation(TextHitInfo offset
) {
3356 Point caret
= logicalPositionToXY(getCaretModel().getLogicalPosition());
3357 Rectangle r
= new Rectangle(caret
, new Dimension(1, getLineHeight()));
3358 Point p
= getContentComponent().getLocationOnScreen();
3359 r
.translate(p
.x
, p
.y
);
3364 public TextHitInfo
getLocationOffset(int x
, int y
) {
3365 if (composedText
!= null) {
3366 Point p
= getContentComponent().getLocationOnScreen();
3369 int pos
= logicalPositionToOffset(xyToLogicalPosition(p
));
3370 if (pos
>= composedTextStart
&& pos
<= composedTextEnd
) {
3371 return TextHitInfo
.leading(pos
- composedTextStart
);
3377 public int getInsertPositionOffset() {
3378 int composedStartIndex
= 0;
3379 int composedEndIndex
= 0;
3380 if (composedText
!= null) {
3381 composedStartIndex
= composedTextStart
;
3382 composedEndIndex
= composedTextEnd
;
3385 int caretIndex
= getCaretModel().getOffset();
3387 if (caretIndex
< composedStartIndex
) {
3391 if (caretIndex
< composedEndIndex
) {
3392 return composedStartIndex
;
3395 return caretIndex
- (composedEndIndex
- composedStartIndex
);
3400 private String
getText(int startIdx
, int endIdx
) {
3401 CharSequence chars
= getDocument().getCharsSequence();
3402 return chars
.subSequence(startIdx
, endIdx
).toString();
3405 public AttributedCharacterIterator
getCommittedText(int beginIndex
, int endIndex
, AttributedCharacterIterator
.Attribute
[] attributes
) {
3406 int composedStartIndex
= 0;
3407 int composedEndIndex
= 0;
3408 if (composedText
!= null) {
3409 composedStartIndex
= composedTextStart
;
3410 composedEndIndex
= composedTextEnd
;
3414 if (beginIndex
< composedStartIndex
) {
3415 if (endIndex
<= composedStartIndex
) {
3416 committed
= getText(beginIndex
, endIndex
- beginIndex
);
3419 int firstPartLength
= composedStartIndex
- beginIndex
;
3420 committed
= getText(beginIndex
, firstPartLength
) + getText(composedEndIndex
, endIndex
- beginIndex
- firstPartLength
);
3424 committed
= getText(beginIndex
+ (composedEndIndex
- composedStartIndex
), endIndex
- beginIndex
);
3427 return new AttributedString(committed
).getIterator();
3430 public int getCommittedTextLength() {
3431 int length
= getDocument().getTextLength();
3432 if (composedText
!= null) {
3433 length
-= composedText
.length();
3438 public AttributedCharacterIterator
cancelLatestCommittedText(AttributedCharacterIterator
.Attribute
[] attributes
) {
3442 public AttributedCharacterIterator
getSelectedText(AttributedCharacterIterator
.Attribute
[] attributes
) {
3443 String text
= getSelectionModel().getSelectedText();
3444 return text
== null ?
null : new AttributedString(text
).getIterator();
3447 private void createComposedString(int composedIndex
, AttributedCharacterIterator text
) {
3448 StringBuffer strBuf
= new StringBuffer();
3450 // create attributed string with no attributes
3451 for (char c
= text
.setIndex(composedIndex
); c
!= CharacterIterator
.DONE
; c
= text
.next()) {
3455 composedText
= new String(strBuf
);
3458 private void setInputMethodCaretPosition(InputMethodEvent e
) {
3459 if (composedText
!= null) {
3460 int dot
= composedTextStart
;
3462 TextHitInfo caretPos
= e
.getCaret();
3463 if (caretPos
!= null) {
3464 dot
+= caretPos
.getInsertionIndex();
3467 getCaretModel().moveToOffset(dot
);
3468 getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
3472 private void runUndoTransparent(final Runnable runnable
) {
3473 CommandProcessor
.getInstance().runUndoTransparentAction(new Runnable() {
3475 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
3477 ApplicationManager
.getApplication().runWriteAction(runnable
);
3479 }, "", getDocument(), UndoConfirmationPolicy
.DEFAULT
, getDocument());
3484 private void replaceInputMethodText(InputMethodEvent e
) {
3485 int commitCount
= e
.getCommittedCharacterCount();
3486 AttributedCharacterIterator text
= e
.getText();
3488 // old composed text deletion
3489 final Document doc
= getDocument();
3491 if (composedText
!= null) {
3492 if (!isViewer() && doc
.isWritable()) {
3493 runUndoTransparent(new Runnable() {
3495 doc
.deleteString(Math
.max(0, composedTextStart
), Math
.min(composedTextEnd
, doc
.getTextLength()));
3499 composedText
= null;
3505 // committed text insertion
3506 if (commitCount
> 0) {
3507 //noinspection ForLoopThatDoesntUseLoopVariable
3508 for (char c
= text
.current(); commitCount
> 0; c
= text
.next(), commitCount
--) {
3509 if (c
>= 0x20 && c
!= 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
3515 // new composed text insertion
3516 if (!isViewer() && doc
.isWritable()) {
3517 int composedTextIndex
= text
.getIndex();
3518 if (composedTextIndex
< text
.getEndIndex()) {
3519 createComposedString(composedTextIndex
, text
);
3521 runUndoTransparent(new Runnable() {
3523 EditorModificationUtil
.insertStringAtCaret(EditorImpl
.this, composedText
, false, false);
3527 composedTextStart
= getCaretModel().getOffset();
3528 composedTextEnd
= getCaretModel().getOffset() + composedText
.length();
3535 private class MyMouseAdapter
extends MouseAdapter
{
3536 public void mousePressed(MouseEvent e
) {
3538 runMousePressedCommand(e
);
3541 public void mouseReleased(MouseEvent e
) {
3542 runMouseReleasedCommand(e
);
3543 if (!e
.isConsumed() && myMousePressedEvent
!= null && !myMousePressedEvent
.isConsumed() &&
3544 Math
.abs(e
.getX() - myMousePressedEvent
.getX()) < EditorUtil
.getSpaceWidth(Font
.PLAIN
, EditorImpl
.this) &&
3545 Math
.abs(e
.getY() - myMousePressedEvent
.getY()) < getLineHeight()) {
3546 runMouseClickedCommand(e
);
3548 myMousePressedEvent
= null;
3551 public void mouseEntered(MouseEvent e
) {
3552 runMouseEnteredCommand(e
);
3555 public void mouseExited(MouseEvent e
) {
3556 runMouseExitedCommand(e
);
3557 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3558 if (event
.getArea() == EditorMouseEventArea
.LINE_MARKERS_AREA
) {
3559 myGutterComponent
.mouseExited(e
);
3562 TooltipController
.getInstance().cancelTooltip(FOLDING_TOOLTIP_GROUP
);
3564 private void runMousePressedCommand(final MouseEvent e
) {
3565 myMousePressedEvent
= e
;
3566 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3568 for (EditorMouseListener mouseListener
: myMouseListeners
) {
3569 mouseListener
.mousePressed(event
);
3572 // On some systems (for example on Linux) popup trigger is MOUSE_PRESSED event.
3573 // But this trigger is always consumed by popup handler. In that case we have to
3575 if (event
.isConsumed() && !(event
.getMouseEvent().isPopupTrigger() || event
.getArea() == EditorMouseEventArea
.EDITING_AREA
)) {
3579 if (myCommandProcessor
!= null) {
3580 Runnable runnable
= new Runnable() {
3582 processMousePressed(e
);
3585 myCommandProcessor
.executeCommand(myProject
, runnable
, "", DocCommandGroupId
.noneGroupId(getDocument()), UndoConfirmationPolicy
.DEFAULT
, getDocument());
3588 processMousePressed(e
);
3592 private void runMouseClickedCommand(final MouseEvent e
) {
3593 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3594 for (EditorMouseListener listener
: myMouseListeners
) {
3595 listener
.mouseClicked(event
);
3596 if (event
.isConsumed()) {
3603 private void runMouseReleasedCommand(final MouseEvent e
) {
3604 myScrollingTimer
.stop();
3605 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3606 for (EditorMouseListener listener
: myMouseListeners
) {
3607 listener
.mouseReleased(event
);
3608 if (event
.isConsumed()) {
3614 if (myCommandProcessor
!= null) {
3615 Runnable runnable
= new Runnable() {
3617 processMouseReleased(e
);
3620 myCommandProcessor
.executeCommand(myProject
, runnable
, "", DocCommandGroupId
.noneGroupId(getDocument()), UndoConfirmationPolicy
.DEFAULT
, getDocument());
3623 processMouseReleased(e
);
3627 private void runMouseEnteredCommand(MouseEvent e
) {
3628 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3629 for (EditorMouseListener listener
: myMouseListeners
) {
3630 listener
.mouseEntered(event
);
3631 if (event
.isConsumed()) {
3638 private void runMouseExitedCommand(MouseEvent e
) {
3639 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3640 for (EditorMouseListener listener
: myMouseListeners
) {
3641 listener
.mouseExited(event
);
3642 if (event
.isConsumed()) {
3649 private void processMousePressed(MouseEvent e
) {
3650 myInitialMouseEvent
= e
;
3652 if (myMouseSelectionState
!= MOUSE_SELECTION_STATE_NONE
&& System
.currentTimeMillis() - myMouseSelectionChangeTimestamp
> 1000) {
3653 setMouseSelectionState(MOUSE_SELECTION_STATE_NONE
);
3662 final EditorMouseEventArea eventArea
= getMouseEventArea(e
);
3663 if (eventArea
== EditorMouseEventArea
.FOLDING_OUTLINE_AREA
) {
3664 final FoldRegion range
= myGutterComponent
.findFoldingAnchorAt(x
, y
);
3665 if (range
!= null) {
3666 final boolean expansion
= !range
.isExpanded();
3668 int scrollShift
= y
- getScrollingModel().getVerticalScrollOffset();
3669 Runnable processor
= new Runnable() {
3671 myFoldingModel
.flushCaretShift();
3672 range
.setExpanded(expansion
);
3675 getFoldingModel().runBatchFoldingOperation(processor
);
3676 y
= myGutterComponent
.getHeadCenterY(range
);
3677 getScrollingModel().scrollVertically(y
- scrollShift
);
3682 if (e
.getSource() == myGutterComponent
) {
3683 if (eventArea
== EditorMouseEventArea
.LINE_MARKERS_AREA
|| eventArea
== EditorMouseEventArea
.ANNOTATIONS_AREA
|| eventArea
== EditorMouseEventArea
.LINE_NUMBERS_AREA
) {
3684 myGutterComponent
.mousePressed(e
);
3685 if (e
.isConsumed()) return;
3690 int oldSelectionStart
= mySelectionModel
.getLeadSelectionOffset();
3691 moveCaretToScreenPos(x
, y
);
3693 if (e
.isPopupTrigger()) return;
3697 int caretOffset
= getCaretModel().getOffset();
3699 myMouseSelectedRegion
= myFoldingModel
.getFoldingPlaceholderAt(new Point(x
, y
));
3700 myMousePressedInsideSelection
= mySelectionModel
.hasSelection() && caretOffset
>= mySelectionModel
.getSelectionStart() &&
3701 caretOffset
<= mySelectionModel
.getSelectionEnd();
3703 if (!myMousePressedInsideSelection
&& mySelectionModel
.hasBlockSelection()) {
3704 int[] starts
= mySelectionModel
.getBlockSelectionStarts();
3705 int[] ends
= mySelectionModel
.getBlockSelectionEnds();
3706 for (int i
= 0; i
< starts
.length
; i
++) {
3707 if (caretOffset
>= starts
[i
] && caretOffset
< ends
[i
]) {
3708 myMousePressedInsideSelection
= true;
3714 if (getMouseEventArea(e
) == EditorMouseEventArea
.LINE_NUMBERS_AREA
&& e
.getClickCount() == 1) {
3715 mySelectionModel
.selectLineAtCaret();
3716 setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED
);
3717 mySavedSelectionStart
= mySelectionModel
.getSelectionStart();
3718 mySavedSelectionEnd
= mySelectionModel
.getSelectionEnd();
3722 if (e
.isShiftDown() && !e
.isControlDown() && !e
.isAltDown()) {
3723 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE
) {
3724 if (caretOffset
< mySavedSelectionStart
) {
3725 mySelectionModel
.setSelection(mySavedSelectionEnd
, caretOffset
);
3728 mySelectionModel
.setSelection(mySavedSelectionStart
, caretOffset
);
3732 mySelectionModel
.setSelection(oldSelectionStart
, caretOffset
);
3736 if (!myMousePressedInsideSelection
&& (getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection())) {
3737 setMouseSelectionState(MOUSE_SELECTION_STATE_NONE
);
3738 mySelectionModel
.setSelection(caretOffset
, caretOffset
);
3741 if (!e
.isPopupTrigger()) {
3742 switch (e
.getClickCount()) {
3744 mySelectionModel
.selectWordAtCaret(mySettings
.isMouseClickSelectionHonorsCamelWords());
3745 setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED
);
3746 mySavedSelectionStart
= mySelectionModel
.getSelectionStart();
3747 mySavedSelectionEnd
= mySelectionModel
.getSelectionEnd();
3748 getCaretModel().moveToOffset(mySavedSelectionEnd
);
3752 mySelectionModel
.selectLineAtCaret();
3753 setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED
);
3754 mySavedSelectionStart
= mySelectionModel
.getSelectionStart();
3755 mySavedSelectionEnd
= mySelectionModel
.getSelectionEnd();
3764 private static final TooltipGroup FOLDING_TOOLTIP_GROUP
= new TooltipGroup("FOLDING_TOOLTIP_GROUP", 10);
3766 private class MyMouseMotionListener
implements MouseMotionListener
{
3767 public void mouseDragged(MouseEvent e
) {
3768 validateMousePointer(e
);
3769 runMouseDraggedCommand(e
);
3770 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3771 if (event
.getArea() == EditorMouseEventArea
.LINE_MARKERS_AREA
) {
3772 myGutterComponent
.mouseDragged(e
);
3775 for (EditorMouseMotionListener listener
: myMouseMotionListeners
) {
3776 listener
.mouseDragged(event
);
3780 public void mouseMoved(MouseEvent e
) {
3781 validateMousePointer(e
);
3782 EditorMouseEvent event
= new EditorMouseEvent(EditorImpl
.this, e
, getMouseEventArea(e
));
3783 if (e
.getSource() == myGutterComponent
) {
3784 myGutterComponent
.mouseMoved(e
);
3787 if (event
.getArea() == EditorMouseEventArea
.EDITING_AREA
) {
3788 FoldRegion fold
= myFoldingModel
.getFoldingPlaceholderAt(e
.getPoint());
3789 TooltipController controller
= TooltipController
.getInstance();
3791 DocumentFragment range
= createDocumentFragment(fold
);
3793 SwingUtilities
.convertPoint((Component
)e
.getSource(), e
.getPoint(), getComponent().getRootPane().getLayeredPane());
3794 controller
.showTooltip(EditorImpl
.this, p
, new DocumentFragmentTooltipRenderer(range
), false, FOLDING_TOOLTIP_GROUP
);
3797 controller
.cancelTooltip(FOLDING_TOOLTIP_GROUP
);
3801 for (EditorMouseMotionListener listener
: myMouseMotionListeners
) {
3802 listener
.mouseMoved(event
);
3806 private DocumentFragment
createDocumentFragment(FoldRegion fold
) {
3807 final FoldingGroup group
= fold
.getGroup();
3808 final int foldStart
= fold
.getStartOffset();
3809 if (group
!= null) {
3810 final int endOffset
= myFoldingModel
.getEndOffset(group
);
3811 if (offsetToVisualPosition(endOffset
).line
== offsetToVisualPosition(foldStart
).line
) {
3812 return new DocumentFragment(myDocument
, foldStart
, endOffset
);
3816 final int oldEnd
= fold
.getEndOffset();
3817 return new DocumentFragment(myDocument
, foldStart
, oldEnd
);
3821 private class MyColorSchemeDelegate
implements EditorColorsScheme
{
3822 private final HashMap
<TextAttributesKey
, TextAttributes
> myOwnAttributes
= new HashMap
<TextAttributesKey
, TextAttributes
>();
3823 private final HashMap
<ColorKey
, Color
> myOwnColors
= new HashMap
<ColorKey
, Color
>();
3824 private Map
<EditorFontType
, Font
> myFontsMap
= null;
3825 private int myFontSize
= -1;
3826 private String myFaceName
= null;
3827 private EditorColorsScheme myGlobalScheme
;
3829 private MyColorSchemeDelegate() {
3830 updateGlobalScheme();
3833 private EditorColorsScheme
getGlobal() {
3834 return myGlobalScheme
;
3837 public String
getName() {
3838 return getGlobal().getName();
3842 protected void initFonts() {
3843 String editorFontName
= getEditorFontName();
3844 int editorFontSize
= getEditorFontSize();
3846 myFontsMap
= new EnumMap
<EditorFontType
, Font
>(EditorFontType
.class);
3848 Font plainFont
= new Font(editorFontName
, Font
.PLAIN
, editorFontSize
);
3849 Font boldFont
= new Font(editorFontName
, Font
.BOLD
, editorFontSize
);
3850 Font italicFont
= new Font(editorFontName
, Font
.ITALIC
, editorFontSize
);
3851 Font boldItalicFont
= new Font(editorFontName
, Font
.BOLD
+ Font
.ITALIC
, editorFontSize
);
3853 myFontsMap
.put(EditorFontType
.PLAIN
, plainFont
);
3854 myFontsMap
.put(EditorFontType
.BOLD
, boldFont
);
3855 myFontsMap
.put(EditorFontType
.ITALIC
, italicFont
);
3856 myFontsMap
.put(EditorFontType
.BOLD_ITALIC
, boldItalicFont
);
3861 public void setName(String name
) {
3862 getGlobal().setName(name
);
3865 public TextAttributes
getAttributes(TextAttributesKey key
) {
3866 if (myOwnAttributes
.containsKey(key
)) return myOwnAttributes
.get(key
);
3867 return getGlobal().getAttributes(key
);
3870 public void setAttributes(TextAttributesKey key
, TextAttributes attributes
) {
3871 myOwnAttributes
.put(key
, attributes
);
3874 public Color
getDefaultBackground() {
3875 return getGlobal().getDefaultBackground();
3878 public Color
getDefaultForeground() {
3879 return getGlobal().getDefaultForeground();
3882 public Color
getColor(ColorKey key
) {
3883 if (myOwnColors
.containsKey(key
)) return myOwnColors
.get(key
);
3884 return getGlobal().getColor(key
);
3887 public void setColor(ColorKey key
, Color color
) {
3888 myOwnColors
.put(key
, color
);
3890 // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
3891 // settings in this case.
3892 myCaretModel
.reinitSettings();
3893 mySelectionModel
.reinitSettings();
3896 public int getEditorFontSize() {
3897 if (myFontSize
== -1) {
3898 return getGlobal().getEditorFontSize();
3903 public void setEditorFontSize(int fontSize
) {
3904 if (fontSize
< 8) fontSize
= 8;
3905 if (fontSize
> 20) fontSize
= 20;
3906 myFontSize
= fontSize
;
3910 public String
getEditorFontName() {
3911 if (myFaceName
== null) {
3912 return getGlobal().getEditorFontName();
3917 public void setEditorFontName(String fontName
) {
3918 myFaceName
= fontName
;
3922 public Font
getFont(EditorFontType key
) {
3923 if (myFontsMap
!= null) {
3924 Font font
= myFontsMap
.get(key
);
3925 if (font
!= null) return font
;
3927 return getGlobal().getFont(key
);
3930 public void setFont(EditorFontType key
, Font font
) {
3931 if (myFontsMap
== null) {
3934 myFontsMap
.put(key
, font
);
3938 public float getLineSpacing() {
3939 return getGlobal().getLineSpacing();
3942 public void setLineSpacing(float lineSpacing
) {
3943 getGlobal().setLineSpacing(lineSpacing
);
3946 public Object
clone() {
3950 public void readExternal(Element element
) throws InvalidDataException
{
3953 public void writeExternal(Element element
) throws WriteExternalException
{
3956 public void updateGlobalScheme() {
3957 myGlobalScheme
= EditorColorsManager
.getInstance().getGlobalScheme();
3961 private static class MyTransferHandler
extends TransferHandler
{
3962 private RangeMarker myDraggedRange
= null;
3964 private static Editor
getEditor(JComponent comp
) {
3965 EditorComponentImpl editorComponent
= (EditorComponentImpl
)comp
;
3966 return editorComponent
.getEditor();
3969 public boolean importData(final JComponent comp
, final Transferable t
) {
3970 final EditorImpl editor
= (EditorImpl
)getEditor(comp
);
3972 final EditorDropHandler dropHandler
= editor
.getDropHandler();
3973 if (dropHandler
!= null && dropHandler
.canHandleDrop(t
.getTransferDataFlavors())) {
3974 dropHandler
.handleDrop(t
, editor
.getProject());
3978 final int caretOffset
= editor
.getCaretModel().getOffset();
3979 if (myDraggedRange
!= null && myDraggedRange
.getStartOffset() <= caretOffset
&& caretOffset
< myDraggedRange
.getEndOffset()) {
3983 if (myDraggedRange
!= null) {
3984 editor
.getCaretModel().moveToOffset(editor
.mySavedCaretOffsetForDNDUndoHack
);
3987 CommandProcessor
.getInstance().executeCommand(editor
.myProject
, new Runnable() {
3989 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
3992 editor
.getSelectionModel().removeSelection();
3994 if (myDraggedRange
!= null) {
3995 editor
.getCaretModel().moveToOffset(caretOffset
);
3996 offset
= caretOffset
;
3999 offset
= editor
.getCaretModel().getOffset();
4001 if (editor
.getDocument().getRangeGuard(offset
, offset
) != null) return;
4003 EditorActionHandler pasteHandler
= EditorActionManager
.getInstance().getActionHandler(IdeActions
.ACTION_EDITOR_PASTE
);
4004 Clipboard clipboard
= Toolkit
.getDefaultToolkit().getSystemClipboard();
4006 Transferable backup
= null;
4008 backup
= clipboard
.getContents(this);
4009 clipboard
.setContents(t
, EmptyClipboardOwner
.INSTANCE
);
4011 catch (Exception e
) {
4012 LOG
.info("Error communicating with system clipboard", e
);
4015 editor
.putUserData(LAST_PASTED_REGION
, null);
4016 pasteHandler
.execute(editor
, editor
.getDataContext());
4018 if (backup
!= null) {
4019 clipboard
.setContents(backup
, EmptyClipboardOwner
.INSTANCE
);
4022 catch (IllegalStateException e
) {
4026 TextRange range
= editor
.getUserData(LAST_PASTED_REGION
);
4027 if (range
!= null) {
4028 editor
.getCaretModel().moveToOffset(range
.getStartOffset());
4029 editor
.getSelectionModel().setSelection(range
.getStartOffset(), range
.getEndOffset());
4032 catch (Exception exception
) {
4033 LOG
.error(exception
);
4038 }, EditorBundle
.message("paste.command.name"), DND_COMMAND_KEY
, UndoConfirmationPolicy
.DEFAULT
, editor
.getDocument());
4043 public boolean canImport(JComponent comp
, DataFlavor
[] transferFlavors
) {
4044 Editor editor
= getEditor(comp
);
4045 final EditorDropHandler dropHandler
= ((EditorImpl
)editor
).getDropHandler();
4046 if (dropHandler
!= null && dropHandler
.canHandleDrop(transferFlavors
)) {
4049 if (editor
.isViewer()) return false;
4051 int offset
= editor
.getCaretModel().getOffset();
4052 if (editor
.getDocument().getRangeGuard(offset
, offset
) != null) return false;
4054 for (DataFlavor transferFlavor
: transferFlavors
) {
4055 if (transferFlavor
.equals(DataFlavor
.stringFlavor
)) return true;
4061 protected Transferable
createTransferable(JComponent c
) {
4062 Editor editor
= getEditor(c
);
4063 String s
= editor
.getSelectionModel().getSelectedText();
4064 if (s
== null) return null;
4065 int selectionStart
= editor
.getSelectionModel().getSelectionStart();
4066 int selectionEnd
= editor
.getSelectionModel().getSelectionEnd();
4067 myDraggedRange
= editor
.getDocument().createRangeMarker(selectionStart
, selectionEnd
);
4069 return new StringSelection(s
);
4072 public int getSourceActions(JComponent c
) {
4073 return COPY_OR_MOVE
;
4076 protected void exportDone(final JComponent source
, Transferable data
, int action
) {
4077 if (data
== null) return;
4079 final Component last
= DnDManager
.getInstance().getLastDropHandler();
4081 if (last
!= null && !(last
instanceof EditorComponentImpl
)) return;
4083 final Editor editor
= getEditor(source
);
4084 if (action
== MOVE
&& !editor
.isViewer()) {
4085 if (!FileDocumentManager
.getInstance().requestWriting(editor
.getDocument(), editor
.getProject())) {
4088 CommandProcessor
.getInstance().executeCommand(((EditorImpl
)editor
).myProject
, new Runnable() {
4090 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
4092 Document doc
= editor
.getDocument();
4093 doc
.startGuardedBlockChecking();
4095 doc
.deleteString(myDraggedRange
.getStartOffset(), myDraggedRange
.getEndOffset());
4097 catch (ReadOnlyFragmentModificationException e
) {
4098 EditorActionManager
.getInstance().getReadonlyFragmentModificationHandler(doc
).handle(e
);
4101 doc
.stopGuardedBlockChecking();
4106 }, EditorBundle
.message("move.selection.command.name"), DND_COMMAND_KEY
, UndoConfirmationPolicy
.DEFAULT
, editor
.getDocument());
4109 myDraggedRange
= null;
4113 class EditorDocumentAdapter
implements PrioritizedDocumentListener
{
4114 public void beforeDocumentChange(DocumentEvent e
) {
4115 beforeChangedUpdate(e
);
4118 public void documentChanged(DocumentEvent e
) {
4122 public int getPriority() {
4127 private class EditorSizeContainer
{
4128 private TIntArrayList myLineWidths
;
4129 private volatile boolean myIsDirty
;
4130 private int myOldEndLine
;
4131 private Dimension mySize
;
4132 private int myMaxWidth
= -1;
4134 public synchronized void reset() {
4135 int visLinesCount
= getVisibleLineCount();
4136 myLineWidths
= new TIntArrayList(visLinesCount
+ 300);
4137 int[] values
= new int[visLinesCount
];
4138 Arrays
.fill(values
, -1);
4139 myLineWidths
.add(values
);
4143 public synchronized void beforeChange(DocumentEvent e
) {
4144 if (myDocument
.isInBulkUpdate()) {
4145 myMaxWidth
= mySize
!= null ? mySize
.width
: -1;
4148 myOldEndLine
= getVisualPositionLine(e
.getOffset() + e
.getOldLength());
4151 private int getVisualPositionLine(int offset
) {
4152 // Do round up of offset to the nearest line start (valid since we need only line)
4153 // This is needed for preventing access to lexer editor highlighter regions [that are reset] during bulk mode operation
4154 final int startLineOffset
= myDocument
.getLineStartOffset(calcLogicalLineNumber(offset
));
4155 return offsetToVisualPosition(startLineOffset
).line
;
4158 public synchronized void changedUpdate(DocumentEvent e
) {
4159 int startLine
= e
.getOldLength() == 0 ? myOldEndLine
: getVisualPositionLine(e
.getOffset());
4160 int newEndLine
= e
.getNewLength() == 0 ? startLine
: getVisualPositionLine(e
.getOffset() + e
.getNewLength());
4161 int oldEndLine
= myOldEndLine
;
4163 final int lineWidthSize
= myLineWidths
.size();
4164 if (lineWidthSize
== 0) {
4168 final int min
= Math
.min(oldEndLine
, newEndLine
);
4169 final boolean toAddNewLines
= min
>= lineWidthSize
;
4171 if (toAddNewLines
) {
4172 final int[] delta
= new int[min
- lineWidthSize
+ 1];
4173 myLineWidths
.insert(lineWidthSize
, delta
);
4176 for (int i
= startLine
; i
<= min
; i
++) myLineWidths
.set(i
, -1);
4177 if (newEndLine
> oldEndLine
) {
4178 int[] delta
= new int[newEndLine
- oldEndLine
];
4179 Arrays
.fill(delta
, -1);
4180 myLineWidths
.insert(oldEndLine
+ 1, delta
);
4182 else if (oldEndLine
> newEndLine
&& !toAddNewLines
&& newEndLine
+ 1 < lineWidthSize
) {
4183 myLineWidths
.remove(newEndLine
+ 1, Math
.min(oldEndLine
, lineWidthSize
) - newEndLine
);
4189 private void validateSizes() {
4190 if (!myIsDirty
) return;
4192 synchronized (this) {
4193 if (!myIsDirty
) return;
4194 int lineCount
= myLineWidths
.size();
4196 if (myMaxWidth
!= -1 && myDocument
.isInBulkUpdate()) {
4197 mySize
= new Dimension(myMaxWidth
, getLineHeight() * lineCount
);
4202 final CharSequence text
= myDocument
.getCharsNoThreadCheck();
4203 int end
= myDocument
.getTextLength();
4205 final int fontSize
= myScheme
.getEditorFontSize();
4206 final String fontName
= myScheme
.getEditorFontName();
4208 for (int line
= 0; line
< lineCount
; line
++) {
4209 if (myLineWidths
.getQuick(line
) != -1) continue;
4211 int offset
= logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line
, 0)));
4213 if (offset
>= myDocument
.getTextLength()) {
4214 myLineWidths
.set(line
, 0);
4218 IterationState state
= new IterationState(EditorImpl
.this, offset
, false);
4219 int fontType
= state
.getMergedAttributes().getFontType();
4221 while (offset
< end
&& line
< lineCount
) {
4222 char c
= text
.charAt(offset
);
4223 if (offset
>= state
.getEndOffset()) {
4225 fontType
= state
.getMergedAttributes().getFontType();
4228 FoldRegion collapsed
= state
.getCurrentFold();
4229 if (collapsed
!= null) {
4230 String placeholder
= collapsed
.getPlaceholderText();
4231 for (int i
= 0; i
< placeholder
.length(); i
++) {
4232 x
+= EditorUtil
.charWidth(placeholder
.charAt(i
), fontType
, EditorImpl
.this);
4234 offset
= collapsed
.getEndOffset();
4238 x
= EditorUtil
.nextTabStop(x
, EditorImpl
.this);
4243 myLineWidths
.set(line
, x
);
4244 if (line
+ 1 >= lineCount
|| myLineWidths
.getQuick(line
+ 1) != -1) break;
4247 //noinspection AssignmentToForLoopParameter
4251 x
+= ComplementaryFontsRegistry
.getFontAbleToDisplay(c
, fontSize
, fontType
, fontName
).charWidth(c
, myEditorComponent
);
4259 if (lineCount
> 0) {
4260 myLineWidths
.set(lineCount
- 1,
4261 x
); // Last line can be non-zero length and won't be caught by in-loop procedure since latter only react on \n's
4265 for (int i
= 0; i
< lineCount
; i
++) {
4266 maxWidth
= Math
.max(maxWidth
, myLineWidths
.getQuick(i
));
4269 mySize
= new Dimension(maxWidth
, getLineHeight() * lineCount
);
4275 public Dimension
getContentSize() {
4282 public EditorGutter
getGutter() {
4283 return getGutterComponentEx();
4286 public int calcColumnNumber(CharSequence text
, int start
, int offset
, int tabSize
) {
4287 IterationState state
= new IterationState(this, start
, false);
4288 int fontType
= state
.getMergedAttributes().getFontType();
4291 int spaceSize
= EditorUtil
.getSpaceWidth(fontType
, this);
4292 for (int i
= start
; i
< offset
; i
++) {
4293 if (i
>= state
.getEndOffset()) {
4295 fontType
= state
.getMergedAttributes().getFontType();
4298 char c
= text
.charAt(i
);
4301 x
= EditorUtil
.nextTabStop(x
, this);
4302 column
+= (x
- prevX
) / spaceSize
;
4303 //column += Math.max(1, (x - prevX) / spaceSize);
4306 x
+= EditorUtil
.charWidth(c
, fontType
, this);
4314 public void putInfo(Map
<String
, String
> info
) {
4315 final VisualPosition visual
= getCaretModel().getVisualPosition();
4316 info
.put("caret", visual
.getLine() + ":" + visual
.getColumn());
4319 private class MyScrollPane
extends JScrollPane2
{
4320 protected void processMouseWheelEvent(MouseWheelEvent e
) {
4321 if (mySettings
.isWheelFontChangeEnabled()) {
4322 boolean changeFontSize
= SystemInfo
.isMac
4323 ?
!e
.isControlDown() && e
.isMetaDown() && !e
.isAltDown() && !e
.isShiftDown()
4324 : e
.isControlDown() && !e
.isMetaDown() && !e
.isAltDown() && !e
.isShiftDown();
4325 if (changeFontSize
) {
4326 setFontSize(myScheme
.getEditorFontSize() + e
.getWheelRotation());
4331 super.processMouseWheelEvent(e
);
4335 private class MyHeaderPanel
extends JPanel
{
4336 private int myOldHeight
= 0;
4338 private MyHeaderPanel() {
4339 super(new BorderLayout());
4342 public void revalidate() {
4343 myOldHeight
= getHeight();
4347 protected void validateTree() {
4348 int height
= myOldHeight
;
4349 super.validateTree();
4350 height
-= getHeight();
4353 myVerticalScrollBar
.setValue(myVerticalScrollBar
.getValue() - height
);
4355 myOldHeight
= getHeight();