caret fix
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / EditorImpl.java
blobf0b00ade31cd4e0cd4238804e3f95fef902529cc
1 /*
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;
74 import javax.swing.*;
75 import javax.swing.Timer;
76 import javax.swing.plaf.ScrollBarUI;
77 import javax.swing.plaf.basic.BasicScrollBarUI;
78 import java.awt.*;
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;
97 import java.util.*;
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;
113 static {
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;
225 static {
226 ourCaretBlinkingCommand = new RepaintCursorCommand();
227 ourCaretBlinkingCommand.start();
231 public EditorImpl(Document document, boolean viewer, Project project) {
232 myProject = project;
233 myDocument = (DocumentImpl)document;
234 myScheme = new MyColorSchemeDelegate();
235 myIsViewer = viewer;
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()));
261 else {
262 repaint(0, getDocument().getTextLength());
264 ((EditorMarkupModelImpl)getMarkupModel()).repaint();
265 ((EditorMarkupModelImpl)getMarkupModel()).markDirtied();
266 GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
267 if (renderer != null) {
268 updateGutterSize();
270 updateCaretCursor();
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() {
294 super.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);
302 initComponent();
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() {
318 public void run() {
319 repaint(0, myDocument.getTextLength());
321 }, 50, ModalityState.stateForComponent(myEditorComponent));
326 updateCaretCursor();
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() {
331 public void run() {
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;
361 reinitSettings();
364 public VirtualFile getVirtualFile() {
365 return myVirtualFile;
368 @NotNull
369 public SelectionModel getSelectionModel() {
370 return mySelectionModel;
373 @NotNull
374 public MarkupModel getMarkupModel() {
375 return myMarkupModel;
378 @NotNull
379 public FoldingModel getFoldingModel() {
380 return myFoldingModel;
383 @NotNull
384 public CaretModel getCaretModel() {
385 return myCaretModel;
388 @NotNull
389 public ScrollingModel getScrollingModel() {
390 return myScrollingModel;
393 @NotNull
394 public EditorSettings getSettings() {
395 assertReadAccess();
396 return mySettings;
399 public void reinitSettings() {
400 assertIsDispatchThread();
401 myCharHeight = -1;
402 myLineHeight = -1;
403 myDescent = -1;
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();
425 updateCaretCursor();
427 if (myInitialMouseEvent != null) {
428 myIgnoreMouseEventsConsecutiveToInitial = true;
432 public void release() {
433 if (isReleased) {
434 LOG.error("Double release. First released at: =====\n" + myReleasedAt+"\n======");
437 myReleasedAt = StringUtil.getThrowableText(new Throwable());
439 isReleased = true;
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();
453 myLineHeight = -1;
454 myCharHeight = -1;
455 myDescent = -1;
456 myPlainFontMetrics = null;
457 myScrollingModel.dispose();
458 myGutterComponent.dispose();
459 clearCaretThread();
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() {
502 @Override
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);
510 else {
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);
524 else {
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()) {
534 return;
536 if (processKeyTyped(event)) {
537 event.consume();
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();
553 int caretLine = getCaretModel().getLogicalPosition().line;
554 repaintLines(caretLine, caretLine);
555 fireFocusGained();
556 if (myGutterNeedsUpdate) {
557 updateGutterSize();
561 public void focusLost(FocusEvent e) {
562 clearCaretThread();
563 int caretLine = getCaretModel().getLogicalPosition().line;
564 repaintLines(caretLine, caretLine);
565 fireFocusLost();
569 // myBorderEffect.reset();
570 try {
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) {
587 LOG.error(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();
613 } else {
614 app.runWriteAction(new Runnable() {
615 public void run() {
616 for (int i = 0; i < text.length(); i++) {
617 if (!processKeyTyped(text.charAt(i))) {
618 result.setRejected();
619 return;
623 result.setDone();
628 return result;
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()) {
636 return false;
638 ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
639 DataContext dataContext = getDataContext();
640 actionManager.fireBeforeEditorTyping(c, dataContext);
641 EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
643 return true;
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) {
680 reinitSettings();
684 public EditorHighlighter getHighlighter() {
685 assertIsDispatchThread();
686 return myHighlighter;
689 @NotNull
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();
735 @NotNull
736 public VisualPosition xyToVisualPosition(@NotNull Point p) {
737 int line = yPositionToVisibleLineNumber(p.y);
738 int px = p.x;
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);
750 int column = 0;
751 int prevX = 0;
752 CharSequence text = myDocument.getCharsNoThreadCheck();
753 char c = ' ';
754 IterationState state = new IterationState(this, offset, false);
756 int fontType = state.getMergedAttributes().getFontType();
757 int spaceSize = EditorUtil.getSpaceWidth(fontType, this);
759 int x = 0;
760 outer:
761 while (true) {
762 if (offset >= textLength) break;
764 if (offset >= state.getEndOffset()) {
765 state.advance();
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) {
773 c = aPlaceholder;
774 x += EditorUtil.charWidth(c, fontType, this);
775 if (x >= px) break outer;
776 column++;
778 offset = region.getEndOffset();
780 else {
781 prevX = x;
782 c = text.charAt(offset);
783 if (c == '\n') {
784 break;
786 if (c == '\t') {
787 x = EditorUtil.nextTabStop(x, this);
789 else {
790 x += EditorUtil.charWidth(c, fontType, this);
793 if (x >= px) break;
795 if (c == '\t') {
796 column += (x - prevX) / spaceSize;
798 else {
799 column++;
802 offset++;
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++;
813 else {
814 if ((x - px) * 2 < x - prevX) {
815 column += (x - prevX) / spaceSize;
819 else {
820 if (x >= px) {
821 if ((x - px) * 2 < charWidth) column++;
823 else {
824 column += (px - x) / EditorUtil.getSpaceWidth(fontType, this);
828 return new VisualPosition(line, column);
831 @NotNull
832 public VisualPosition offsetToVisualPosition(int offset) {
833 return logicalToVisualPosition(offsetToLogicalPosition(offset));
836 @NotNull
837 public LogicalPosition offsetToLogicalPosition(int offset) {
838 int line = calcLogicalLineNumber(offset);
839 int column = calcColumnNumber(offset, line);
840 return new LogicalPosition(line, column);
843 @NotNull
844 public LogicalPosition xyToLogicalPosition(@NotNull Point p) {
845 final Point pp;
846 if (p.x >= 0 && p.y >= 0) {
847 pp = p;
849 else {
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);
861 @NotNull
862 public Point logicalPositionToXY(@NotNull LogicalPosition pos) {
863 VisualPosition visible = logicalToVisualPosition(pos);
864 int y = visibleLineNumberToYPosition(visible.line);
866 int lineStartOffset;
868 if (pos.line == 0) {
869 lineStartOffset = 0;
871 else {
872 if (pos.line >= myDocument.getLineCount()) {
873 lineStartOffset = myDocument.getTextLength();
875 else {
876 lineStartOffset = myDocument.getLineStartOffset(pos.line);
880 int x = getTabbedTextWidth(lineStartOffset, visible);
881 return new Point(x, y);
884 @NotNull
885 public Point visualPositionToXY(@NotNull VisualPosition visible) {
886 int y = visibleLineNumberToYPosition(visible.line);
887 int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
889 int lineStartOffset;
891 if (logLine == 0) {
892 lineStartOffset = 0;
894 else {
895 if (logLine >= myDocument.getLineCount()) {
896 lineStartOffset = myDocument.getTextLength();
898 else {
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) {
908 int x = 0;
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);
922 int column = 0;
923 outer:
924 while (column < pos.column) {
925 if (offset >= textLength) break;
927 if (offset >= state.getEndOffset()) {
928 state.advance();
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);
938 column++;
939 if (column >= pos.column) break outer;
941 offset = region.getEndOffset();
943 else {
944 char c = text.charAt(offset);
945 if (c == '\n') {
946 break;
948 if (c == '\t') {
949 int prevX = x;
950 x = EditorUtil.nextTabStop(x, this);
951 column += (x - prevX) / spaceSize;
953 else {
954 x += EditorUtil.charWidth(c, fontType, this);
955 column++;
957 offset++;
961 if (column != pos.column) {
962 x += EditorUtil.getSpaceWidth(fontType, this) * (pos.column - column);
965 return x;
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()) {
975 return;
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);
1030 validateSize();
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) {
1040 updateGutterSize();
1043 if (countLineFeeds(e.getOldFragment()) != countLineFeeds(e.getNewFragment())) {
1044 // Lines removed. Need to repaint till the end of the screen
1045 repaintToScreenBotton(startLine);
1046 painted = true;
1050 updateCaretCursor();
1051 if (!painted) {
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() {
1069 public void run() {
1070 if (!isDisposed()) {
1071 if (isShowing()) {
1072 myGutterComponent.updateSize();
1073 myGutterNeedsUpdate = false;
1075 else {
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();
1105 validateSize();
1106 myEditorComponent.repaintEditorComponent();
1109 @NotNull
1110 public Document getDocument() {
1111 return myDocument;
1114 @NotNull
1115 public JComponent getComponent() {
1116 return myPanel;
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() {
1138 return isReleased;
1141 void paint(Graphics g) {
1142 startOptimizedScrolling();
1144 if (myCursorUpdater != null) {
1145 myCursorUpdater.run();
1146 myCursorUpdater = null;
1149 Rectangle clip = getClipBounds(g);
1151 if (clip == null) {
1152 return;
1155 Rectangle viewRect = getScrollingModel().getVisibleArea();
1156 if (viewRect == null) {
1157 return;
1160 if (isReleased) {
1161 g.setColor(new Color(128, 255, 128));
1162 g.fillRect(clip.x, clip.y, clip.width, clip.height);
1164 return;
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);
1173 paintText(g, clip);
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;
1197 @Nullable
1198 public JComponent getHeaderComponent() {
1199 if (hasHeaderComponent()) {
1200 return (JComponent)myHeaderPanel.getComponent(0);
1202 return null;
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()) {
1231 return color;
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);
1243 Point p2 =
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);
1250 g.setStroke(saved);
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) {
1261 return;
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()) {
1292 return;
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) {
1326 int width = 0;
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) {
1335 width = lineWidth;
1339 return 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);
1361 return;
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()) {
1371 return;
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();
1386 if (hEnd >= lEnd) {
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);
1398 else {
1399 paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground);
1400 break;
1403 position.x = 0;
1404 if (position.y > clip.y + clip.height) break;
1405 position.y += lineHeight;
1406 start = lEnd;
1409 lIterator.advance();
1411 else {
1412 FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
1413 if (collapsedFolderAt != null) {
1414 position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText(), position, fontType, defaultBackground, clip);
1416 else {
1417 if (hEnd > lEnd - lIterator.getSeparatorLength()) {
1418 position.x = drawBackground(g, backColor, text.subSequence(start, lEnd - lIterator.getSeparatorLength()), position, fontType,
1419 defaultBackground, clip);
1421 else {
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));
1452 final int y;
1453 final int height;
1454 if (start.y <= end.y) {
1455 y = start.y;
1456 height = end.y - y + getLineHeight();
1458 else {
1459 y = end.y;
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,
1468 Graphics g,
1469 Point position,
1470 Rectangle clip,
1471 int lineHeight,
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,
1481 Color backColor,
1482 CharSequence text,
1483 Point position,
1484 int fontType,
1485 Color defaultBackground,
1486 Rectangle clip) {
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;
1494 else {
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;
1524 myLastCache = 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);
1545 return;
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()) {
1555 return;
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();
1572 if (hEnd >= lEnd) {
1573 FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
1574 if (collapsedFolderAt == null) {
1575 drawString(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType, fontType,
1576 currentColor);
1577 position.x = 0;
1578 if (position.y > clip.y + clip.height) break;
1579 position.y += lineHeight;
1580 start = lEnd;
1583 // myBorderEffect.eolReached(g, this);
1584 lIterator.advance();
1586 else {
1587 FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
1588 if (collapsedFolderAt != null) {
1589 int foldingXStart = position.x;
1590 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);
1595 else {
1596 if (hEnd > lEnd - lIterator.getSeparatorLength()) {
1597 position.x = drawString(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
1598 fontType, currentColor);
1600 else {
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;
1624 int foldingXEnd =
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];
1645 int myCount = 0;
1646 final FontInfo myFontType;
1648 private char[] myLastData;
1650 private CachedFontContent(FontInfo fontInfo) {
1651 myFontType = fontInfo;
1654 private void flushContent(Graphics g) {
1655 if (myCount != 0) {
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]);
1668 color[i] = null;
1669 data[i] = null;
1672 myCount = 0;
1673 myLastData = null;
1677 private void addContent(Graphics g, char[] _data, int _start, int _end, int _x, int _y, Color _color) {
1678 final int count = myCount;
1679 if (count > 0) {
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;
1685 return;
1689 myLastData = _data;
1690 data[count] = _data;
1691 x[count] = _x;
1692 y[count] = _y;
1693 starts[count] = _start;
1694 ends[count] = _end;
1695 color[count] = _color;
1697 myCount++;
1698 if (count >= CACHED_CHARS_BUFFER_SIZE - 1) {
1699 flushContent(g);
1704 private void flushCachedChars(Graphics g) {
1705 for (CachedFontContent cache : myFontCache) {
1706 cache.flushContent(g);
1708 myLastCache = null;
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()) {
1725 return;
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()) {
1732 return;
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,
1754 final char[] text,
1755 int start,
1756 int end,
1757 Point position,
1758 Rectangle clip,
1759 Color effectColor,
1760 EffectType effectType,
1761 int fontType,
1762 Color fontColor) {
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;
1770 int x = position.x;
1771 return drawTabbedString(g, text, start, end, x, y, effectColor, effectType, fontType, fontColor, clip);
1774 private int drawString(Graphics g,
1775 String text,
1776 Point position,
1777 Rectangle clip,
1778 Color effectColor,
1779 EffectType effectType,
1780 int fontType,
1781 Color fontColor) {
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;
1787 int x = position.x;
1789 return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType, fontColor, clip);
1792 private int drawTabbedString(Graphics g,
1793 char[] text,
1794 int start,
1795 int end,
1796 int x,
1797 int y,
1798 Color effectColor,
1799 EffectType effectType,
1800 int fontType,
1801 Color fontColor,
1802 final Rectangle clip) {
1803 int xStart = x;
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);
1812 x = x1;
1813 start = i + 1;
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);
1822 int xEnd = x;
1823 if (xStart < clip.x && xEnd < clip.x || xStart > clip.x + clip.width && xEnd > clip.x + clip.width) {
1824 return x;
1827 if (xEnd > clip.x + clip.width) {
1828 xEnd = clip.x + clip.width;
1830 if (xStart < clip.x) {
1831 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);
1858 return x;
1861 private int drawTablessString(final char[] text,
1862 int start,
1863 final int end,
1864 final Graphics g,
1865 int x,
1866 final int y,
1867 final int fontType,
1868 final Color fontColor,
1869 final Rectangle clip) {
1870 int endX = x;
1871 if (start < end) {
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);
1880 start = j;
1881 x = endX;
1882 font = newFont;
1884 if (x < clip.x && endX < clip.x) {
1885 start = j;
1886 x = endX;
1887 font = newFont;
1889 else if (x > clip.x + clip.width) {
1890 return endX;
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);
1900 return endX;
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);
1923 else {
1924 FontInfo fnt = EditorUtil.fontForChar(data[start], fontType, this);
1925 CachedFontContent cache = null;
1926 for (CachedFontContent fontCache : myFontCache) {
1927 if (fontCache.myFontType == fnt) {
1928 cache = fontCache;
1929 break;
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;
1946 return true;
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) {
1987 int x = xStart;
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);
1994 else {
1995 x += EditorUtil.charWidth(text.charAt(i), fontType, this);
1997 if (x > clip.x + clip.width) {
1998 break;
2001 return x - xStart;
2004 public int getLineHeight() {
2005 if (myLineHeight != -1) return myLineHeight;
2007 assertReadAccess();
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) {
2014 myLineHeight = 12;
2018 return myLineHeight;
2021 int getDescent() {
2022 if (myDescent != -1) {
2023 return myDescent;
2025 FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
2026 myDescent = fontMetrics.getDescent();
2027 return myDescent;
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;
2070 else {
2071 draft.width += additionalSpace;
2073 return draft;
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) {
2097 assertReadAccess();
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() {
2122 assertReadAccess();
2123 return myLastColumnNumber;
2126 int getVisibleLineCount() {
2127 int line = getDocument().getLineCount();
2128 line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
2129 return line;
2132 @NotNull
2133 public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos) {
2134 assertReadAccess();
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);
2146 else {
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;
2167 else {
2168 break;
2173 LOG.assertTrue(line >= 0);
2175 return new VisualPosition(line, Math.max(0, column));
2178 @Nullable
2179 private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
2180 FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
2182 if (topLevelCollapsed == null) return null;
2184 int start = 0;
2185 int end = topLevelCollapsed.length - 1;
2186 int i = 0;
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) {
2194 start = i + 1;
2196 else {
2197 if (visFoldEnd.line > visual.line) {
2198 end = i - 1;
2200 else {
2201 if (visFoldEnd.column < visual.column) {
2202 start = i + 1;
2204 else {
2205 if (visFoldEnd.column > visual.column) {
2206 end = i - 1;
2208 else {
2209 i--;
2210 break;
2217 while (i >= 0 && i < topLevelCollapsed.length) {
2218 if (topLevelCollapsed[i].isValid()) break;
2219 i--;
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) {
2227 i--;
2228 if (i >= 0) {
2229 return topLevelCollapsed[i];
2231 else {
2232 return null;
2235 return region;
2238 return null;
2241 @NotNull
2242 public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos) {
2243 assertReadAccess();
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);
2260 else {
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());
2283 return lineIndex;
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) {
2296 if (x < 0) {
2297 x = 0;
2300 LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
2302 int columnNumber = pos.column;
2303 int lineNumber = pos.line;
2305 if (lineNumber < 0) {
2306 lineNumber = 0;
2307 columnNumber = 0;
2310 final int totalLines = myDocument.getLineCount();
2311 if (totalLines <= 0) {
2312 getCaretModel().moveToOffset(0);
2313 return;
2316 if (lineNumber >= totalLines) {
2317 moveCaretToScreenPos(x, logicalLineToY(totalLines - 1));
2318 return;
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;
2345 return false;
2348 if (e.getComponent() != myInitialMouseEvent.getComponent() || !e.getPoint().equals(myInitialMouseEvent.getPoint())) {
2349 myIgnoreMouseEventsConsecutiveToInitial = false;
2350 myInitialMouseEvent = null;
2351 return false;
2354 if (isFinalCheck) {
2355 myIgnoreMouseEventsConsecutiveToInitial = false;
2356 myInitialMouseEvent = null;
2359 e.consume();
2361 return true;
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) {
2372 return;
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() {
2379 public void run() {
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)) {
2401 return myProject;
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;
2434 return null;
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));
2448 else {
2449 myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
2452 else {
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));
2458 return;
2461 myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
2465 private void runMouseDraggedCommand(final MouseEvent e) {
2466 if (myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
2467 return;
2469 myCommandProcessor.executeCommand(myProject, new Runnable() {
2470 public void run() {
2471 processMouseDragged(e);
2473 }, "", MOUSE_DRAGGED_GROUP, UndoConfirmationPolicy.DEFAULT, getDocument());
2476 private void processMouseDragged(MouseEvent e) {
2477 if (SwingUtilities.isRightMouseButton(e)) {
2478 return;
2480 Rectangle rect = getScrollingModel().getVisibleArea();
2482 int x = e.getX();
2484 if (e.getSource() == myGutterComponent) {
2485 x = 0;
2488 int dx = 0;
2489 if (x < rect.x && rect.x > 0) {
2490 dx = x - rect.x;
2492 else {
2493 if (x > rect.x + rect.width) {
2494 dx = x - rect.x - rect.width;
2498 int dy = 0;
2499 int y = e.getY();
2500 if (y < rect.y && rect.y > 0) {
2501 dy = y - rect.y;
2503 else {
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);
2525 else {
2526 if (isColumnMode() || e.isAltDown()) {
2527 final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
2528 selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
2530 else {
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();
2537 else {
2538 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
2539 newSelection =
2540 logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0)));
2543 if (newSelection < 0) newSelection = newCaretOffset;
2544 selectionModel.setSelection(mySavedSelectionEnd, newSelection);
2545 getCaretModel().moveToOffset(newSelection);
2547 else {
2548 int newSelection = newCaretOffset;
2549 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
2550 newSelection = mySelectionModel.getWordAtCaretEnd();
2552 else {
2553 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
2554 newSelection =
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);
2562 return;
2565 if (!myMousePressedInsideSelection) {
2566 selectionModel.setSelection(oldSelectionStart, newCaretOffset);
2568 else {
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);
2577 else {
2578 selectionModel.removeSelection();
2580 myMousePressedEvent = null;
2587 else {
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 {
2604 public void run() {
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;
2620 start();
2623 private void setBlinkCaret(boolean value) {
2624 myIsBlinkCaret = value;
2627 public void run() {
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;
2639 else {
2640 toRepaint = !activeCursor.myIsShown;
2641 activeCursor.myIsShown = true;
2644 if (toRepaint) {
2645 SwingUtilities.invokeLater(myRepaintRunnable);
2652 void updateCaretCursor() {
2653 if (!ourIsUnitTestMode && !IJSwingUtilities.hasFocus(getContentComponent())) {
2654 stopOptimizedScrolling();
2657 if (myCursorUpdater == null) {
2658 myCursorUpdater = new Runnable() {
2659 public void run() {
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 boolean old = myCaretCursor.isActive();
2673 if (b) {
2674 myCaretCursor.activate();
2676 else {
2677 myCaretCursor.passivate();
2679 return old;
2682 public boolean setCaretEnabled(boolean enabled) {
2683 boolean old = myCaretCursor.isEnabled();
2684 myCaretCursor.setEnabled(enabled);
2685 return old;
2688 public void addFocusListener(FocusChangeListener listener) {
2689 myFocusListeners.add(listener);
2692 public Project getProject() {
2693 return myProject;
2696 public boolean isOneLineMode() {
2697 return myIsOneLineMode;
2700 public boolean isEmbeddedIntoDialogWrapper() {
2701 return myEmbeddedIntoDialogWrapper;
2704 public void setEmbeddedIntoDialogWrapper(boolean b) {
2705 assertIsDispatchThread();
2707 myEmbeddedIntoDialogWrapper = b;
2708 myScrollPane.setFocusable(!b);
2709 myEditorComponent.setFocusCycleRoot(!b);
2710 myEditorComponent.setFocusable(b);
2713 public void setOneLineMode(boolean isOneLineMode) {
2714 myIsOneLineMode = isOneLineMode;
2715 getScrollPane().setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
2716 reinitSettings();
2719 public void stopOptimizedScrolling() {
2720 myEditorComponent.setOpaque(false);
2723 private void startOptimizedScrolling() {
2724 myEditorComponent.setOpaque(true);
2727 private class CaretCursor {
2728 private Point myLocation;
2729 private int myWidth;
2730 private boolean myEnabled;
2732 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
2733 private boolean myIsShown = false;
2734 private long myStartTime = 0;
2736 private CaretCursor() {
2737 myLocation = new Point(0, 0);
2738 setEnabled(true);
2741 public boolean isEnabled() {
2742 return myEnabled;
2745 public void setEnabled(boolean enabled) {
2746 myEnabled = enabled;
2749 private void activate() {
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);
2756 myIsShown = true;
2760 public boolean isActive() {
2761 synchronized (ourCaretBlinkingCommand) {
2762 return myIsShown;
2766 private void passivate() {
2767 synchronized (ourCaretBlinkingCommand) {
2768 myIsShown = false;
2772 private void setPosition(Point location, int width) {
2773 myStartTime = System.currentTimeMillis();
2774 myLocation = location;
2775 myWidth = Math.max(width, 2);
2776 myIsShown = true;
2777 repaint();
2780 private void repaint() {
2781 myEditorComponent.repaintEditorComponent(myLocation.x, myLocation.y, myWidth, getLineHeight());
2784 private void paint(Graphics g) {
2785 if (!isEnabled() || !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) {
2793 return;
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);
2804 else {
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);
2811 g.setPaintMode();
2816 private class ScrollingTimer {
2817 Timer myTimer;
2818 private static final int TIMER_PERIOD = 100;
2819 private static final int CYCLE_SIZE = 20;
2820 private int myXCycles;
2821 private int myYCycles;
2822 private int myDx;
2823 private int myDy;
2824 private int xPassedCycles = 0;
2825 private int yPassedCycles = 0;
2827 private void start(int dx, int dy) {
2828 myDx = 0;
2829 myDy = 0;
2830 if (dx > 0) {
2831 myXCycles = CYCLE_SIZE / dx + 1;
2832 myDx = 1 + dx / CYCLE_SIZE;
2834 else {
2835 if (dx < 0) {
2836 myXCycles = -CYCLE_SIZE / dx + 1;
2837 myDx = -1 + dx / CYCLE_SIZE;
2841 if (dy > 0) {
2842 myYCycles = CYCLE_SIZE / dy + 1;
2843 myDy = 1 + dy / CYCLE_SIZE;
2845 else {
2846 if (dy < 0) {
2847 myYCycles = -CYCLE_SIZE / dy + 1;
2848 myDy = -1 + dy / CYCLE_SIZE;
2852 if (myTimer != null) {
2853 return;
2857 myTimer = new Timer(TIMER_PERIOD, new ActionListener() {
2858 public void actionPerformed(ActionEvent e) {
2859 myCommandProcessor.executeCommand(myProject, new DocumentRunnable(myDocument, myProject) {
2860 public void run() {
2861 int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
2862 LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
2863 int columnNumber = caretPosition.column;
2864 xPassedCycles++;
2865 if (xPassedCycles >= myXCycles) {
2866 xPassedCycles = 0;
2867 columnNumber += myDx;
2870 int lineNumber = caretPosition.line;
2871 yPassedCycles++;
2872 if (yPassedCycles >= myYCycles) {
2873 yPassedCycles = 0;
2874 lineNumber += myDy;
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();
2890 else {
2891 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
2892 newSelection =
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);
2900 else {
2901 int newSelection = newCaretOffset;
2902 if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
2903 newSelection = mySelectionModel.getWordAtCaretEnd();
2905 else {
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);
2915 return;
2918 if (mySelectionModel.hasBlockSelection()) {
2919 mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
2921 else {
2922 mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
2925 }, EditorBundle.message("move.cursor.command.name"), DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument());
2928 myTimer.start();
2931 private void stop() {
2932 if (myTimer != null) {
2933 myTimer.stop();
2934 myTimer = null;
2938 private int validateOffset(int offset) {
2939 if (offset < 0) return 0;
2940 if (offset > myDocument.getTextLength()) return myDocument.getTextLength();
2941 return offset;
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) {
2951 super(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 :) )
2960 * value.
2962 int getDecScrollButtonHeight() {
2963 ScrollBarUI barUI = getUI();
2964 Insets insets = getInsets();
2965 if (barUI instanceof BasicScrollBarUI) {
2966 try {
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());
2977 else {
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 :) )
2986 * value.
2988 int getIncScrollButtonHeight() {
2989 ScrollBarUI barUI = getUI();
2990 Insets insets = getInsets();
2991 if (barUI instanceof BasicScrollBarUI) {
2992 try {
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;
3006 else {
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();
3028 return myEditable;
3031 public CopyProvider getCopyProvider() {
3032 return getViewer();
3035 public CutProvider getCutProvider() {
3036 return getViewer();
3039 public PasteProvider getPasteProvider() {
3041 return getViewer();
3044 public DeleteProvider getDeleteProvider() {
3045 return getViewer();
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) {
3054 return true;
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();
3104 myScheme = scheme;
3105 reinitSettings();
3108 @NotNull
3109 public EditorColorsScheme getColorsScheme() {
3110 assertReadAccess();
3111 return myScheme;
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());
3128 else {
3129 myScrollPane.setLayout(new ScrollPaneLayout());
3131 myScrollingModel.scrollHorizontally(currentHorOffset);
3134 public void setVerticalScrollbarVisible(boolean b) {
3135 if (b) {
3136 myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
3138 else {
3139 myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
3143 public void setHorizontalScrollbarVisible(boolean b) {
3144 if (b) {
3145 myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
3147 else {
3148 myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
3152 int getVerticalScrollbarOrientation() {
3153 return myScrollbarOrientation;
3156 MyScrollBar getVerticalScrollBar() {
3157 return myVerticalScrollBar;
3160 JPanel getPanel() {
3161 return myPanel;
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
3196 processKeyTyped(c);
3197 return true;
3199 else {
3200 return false;
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];
3227 try {
3228 GuiUtils.invokeAndWait(new Runnable() {
3229 public void run() {
3230 r[0] = myDelegate.getTextLocation(offset);
3234 catch (InterruptedException e) {
3235 LOG.error(e);
3237 catch (InvocationTargetException e) {
3238 LOG.error(e);
3240 return r[0];
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];
3247 try {
3248 GuiUtils.invokeAndWait(new Runnable() {
3249 public void run() {
3250 r[0] = myDelegate.getLocationOffset(x, y);
3254 catch (InterruptedException e) {
3255 LOG.error(e);
3257 catch (InvocationTargetException e) {
3258 LOG.error(e);
3260 return r[0];
3263 public int getInsertPositionOffset() {
3264 if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getInsertPositionOffset();
3266 final int[] r = new int[1];
3267 try {
3268 GuiUtils.invokeAndWait(new Runnable() {
3269 public void run() {
3270 r[0] = myDelegate.getInsertPositionOffset();
3274 catch (InterruptedException e) {
3275 LOG.error(e);
3277 catch (InvocationTargetException e) {
3278 LOG.error(e);
3280 return r[0];
3283 public AttributedCharacterIterator getCommittedText(final int beginIndex,
3284 final int endIndex,
3285 final AttributedCharacterIterator.Attribute[] attributes) {
3286 if (ApplicationManager.getApplication().isDispatchThread()) {
3287 return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
3289 final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
3290 try {
3291 GuiUtils.invokeAndWait(new Runnable() {
3292 public void run() {
3293 r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
3297 catch (InterruptedException e) {
3298 LOG.error(e);
3300 catch (InvocationTargetException e) {
3301 LOG.error(e);
3303 return r[0];
3306 public int getCommittedTextLength() {
3307 if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getCommittedTextLength();
3308 final int[] r = new int[1];
3309 try {
3310 GuiUtils.invokeAndWait(new Runnable() {
3311 public void run() {
3312 r[0] = myDelegate.getCommittedTextLength();
3316 catch (InterruptedException e) {
3317 LOG.error(e);
3319 catch (InvocationTargetException e) {
3320 LOG.error(e);
3322 return r[0];
3325 public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
3326 return null;
3329 public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
3330 if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getSelectedText(attributes);
3332 final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
3333 try {
3334 GuiUtils.invokeAndWait(new Runnable() {
3335 public void run() {
3336 r[0] = myDelegate.getSelectedText(attributes);
3340 catch (InterruptedException e) {
3341 LOG.error(e);
3343 catch (InvocationTargetException e) {
3344 LOG.error(e);
3346 return r[0];
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);
3361 return r;
3364 public TextHitInfo getLocationOffset(int x, int y) {
3365 if (composedText != null) {
3366 Point p = getContentComponent().getLocationOnScreen();
3367 p.x = x - p.x;
3368 p.y = y - p.y;
3369 int pos = logicalPositionToOffset(xyToLogicalPosition(p));
3370 if (pos >= composedTextStart && pos <= composedTextEnd) {
3371 return TextHitInfo.leading(pos - composedTextStart);
3374 return null;
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) {
3388 return caretIndex;
3390 else {
3391 if (caretIndex < composedEndIndex) {
3392 return composedStartIndex;
3394 else {
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;
3413 String committed;
3414 if (beginIndex < composedStartIndex) {
3415 if (endIndex <= composedStartIndex) {
3416 committed = getText(beginIndex, endIndex - beginIndex);
3418 else {
3419 int firstPartLength = composedStartIndex - beginIndex;
3420 committed = getText(beginIndex, firstPartLength) + getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
3423 else {
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();
3435 return length;
3438 public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
3439 return null;
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()) {
3452 strBuf.append(c);
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() {
3474 public void run() {
3475 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
3476 public void run() {
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() {
3494 public void run() {
3495 doc.deleteString(Math.max(0, composedTextStart), Math.min(composedTextEnd, doc.getTextLength()));
3499 composedText = null;
3502 if (text != null) {
3503 text.first();
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
3510 processKeyTyped(c);
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() {
3522 public void run() {
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) {
3537 requestFocus();
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
3574 // also move caret.
3575 if (event.isConsumed() && !(event.getMouseEvent().isPopupTrigger() || event.getArea() == EditorMouseEventArea.EDITING_AREA)) {
3576 return;
3579 if (myCommandProcessor != null) {
3580 Runnable runnable = new Runnable() {
3581 public void run() {
3582 processMousePressed(e);
3585 myCommandProcessor.executeCommand(myProject, runnable, "", DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument());
3587 else {
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()) {
3597 e.consume();
3598 return;
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()) {
3609 e.consume();
3610 return;
3614 if (myCommandProcessor != null) {
3615 Runnable runnable = new Runnable() {
3616 public void run() {
3617 processMouseReleased(e);
3620 myCommandProcessor.executeCommand(myProject, runnable, "", DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument());
3622 else {
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()) {
3632 e.consume();
3633 return;
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()) {
3643 e.consume();
3644 return;
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);
3656 int x = e.getX();
3657 int y = e.getY();
3659 if (x < 0) x = 0;
3660 if (y < 0) y = 0;
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() {
3670 public void run() {
3671 myFoldingModel.flushCaretShift();
3672 range.setExpanded(expansion);
3675 getFoldingModel().runBatchFoldingOperation(processor);
3676 y = myGutterComponent.getHeadCenterY(range);
3677 getScrollingModel().scrollVertically(y - scrollShift);
3678 return;
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;
3687 x = 0;
3690 int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
3691 moveCaretToScreenPos(x, y);
3693 if (e.isPopupTrigger()) return;
3695 requestFocus();
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;
3709 break;
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();
3719 return;
3722 if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
3723 if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
3724 if (caretOffset < mySavedSelectionStart) {
3725 mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
3727 else {
3728 mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
3731 else {
3732 mySelectionModel.setSelection(oldSelectionStart, caretOffset);
3735 else {
3736 if (!myMousePressedInsideSelection && (getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection())) {
3737 setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
3738 mySelectionModel.setSelection(caretOffset, caretOffset);
3740 else {
3741 if (!e.isPopupTrigger()) {
3742 switch (e.getClickCount()) {
3743 case 2:
3744 mySelectionModel.selectWordAtCaret(mySettings.isMouseClickSelectionHonorsCamelWords());
3745 setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
3746 mySavedSelectionStart = mySelectionModel.getSelectionStart();
3747 mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
3748 getCaretModel().moveToOffset(mySavedSelectionEnd);
3749 break;
3751 case 3:
3752 mySelectionModel.selectLineAtCaret();
3753 setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
3754 mySavedSelectionStart = mySelectionModel.getSelectionStart();
3755 mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
3756 break;
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();
3790 if (fold != null) {
3791 DocumentFragment range = createDocumentFragment(fold);
3792 final Point p =
3793 SwingUtilities.convertPoint((Component)e.getSource(), e.getPoint(), getComponent().getRootPane().getLayeredPane());
3794 controller.showTooltip(EditorImpl.this, p, new DocumentFragmentTooltipRenderer(range), false, FOLDING_TOOLTIP_GROUP);
3796 else {
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);
3858 reinitSettings();
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();
3900 return myFontSize;
3903 public void setEditorFontSize(int fontSize) {
3904 if (fontSize < 8) fontSize = 8;
3905 if (fontSize > 20) fontSize = 20;
3906 myFontSize = fontSize;
3907 initFonts();
3910 public String getEditorFontName() {
3911 if (myFaceName == null) {
3912 return getGlobal().getEditorFontName();
3914 return myFaceName;
3917 public void setEditorFontName(String fontName) {
3918 myFaceName = fontName;
3919 initFonts();
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) {
3932 initFonts();
3934 myFontsMap.put(key, font);
3935 reinitSettings();
3938 public float getLineSpacing() {
3939 return getGlobal().getLineSpacing();
3942 public void setLineSpacing(float lineSpacing) {
3943 getGlobal().setLineSpacing(lineSpacing);
3946 public Object clone() {
3947 return null;
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());
3975 return true;
3978 final int caretOffset = editor.getCaretModel().getOffset();
3979 if (myDraggedRange != null && myDraggedRange.getStartOffset() <= caretOffset && caretOffset < myDraggedRange.getEndOffset()) {
3980 return false;
3983 if (myDraggedRange != null) {
3984 editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
3987 CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
3988 public void run() {
3989 ApplicationManager.getApplication().runWriteAction(new Runnable() {
3990 public void run() {
3991 try {
3992 editor.getSelectionModel().removeSelection();
3993 final int offset;
3994 if (myDraggedRange != null) {
3995 editor.getCaretModel().moveToOffset(caretOffset);
3996 offset = caretOffset;
3998 else {
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;
4007 try {
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());
4017 try {
4018 if (backup != null) {
4019 clipboard.setContents(backup, EmptyClipboardOwner.INSTANCE);
4022 catch (IllegalStateException e) {
4023 LOG.info(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());
4040 return true;
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)) {
4047 return true;
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;
4058 return false;
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())) {
4086 return;
4088 CommandProcessor.getInstance().executeCommand(((EditorImpl)editor).myProject, new Runnable() {
4089 public void run() {
4090 ApplicationManager.getApplication().runWriteAction(new Runnable() {
4091 public void run() {
4092 Document doc = editor.getDocument();
4093 doc.startGuardedBlockChecking();
4094 try {
4095 doc.deleteString(myDraggedRange.getStartOffset(), myDraggedRange.getEndOffset());
4097 catch (ReadOnlyFragmentModificationException e) {
4098 EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
4100 finally {
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) {
4119 changedUpdate(e);
4122 public int getPriority() {
4123 return 5;
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);
4140 myIsDirty = true;
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) {
4165 reset();
4167 else {
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);
4185 myIsDirty = true;
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);
4198 myIsDirty = false;
4199 return;
4202 final CharSequence text = myDocument.getCharsNoThreadCheck();
4203 int end = myDocument.getTextLength();
4204 int x = 0;
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;
4210 x = 0;
4211 int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
4213 if (offset >= myDocument.getTextLength()) {
4214 myLineWidths.set(line, 0);
4215 break;
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()) {
4224 state.advance();
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();
4236 else {
4237 if (c == '\t') {
4238 x = EditorUtil.nextTabStop(x, EditorImpl.this);
4239 offset++;
4241 else {
4242 if (c == '\n') {
4243 myLineWidths.set(line, x);
4244 if (line + 1 >= lineCount || myLineWidths.getQuick(line + 1) != -1) break;
4245 offset++;
4246 x = 0;
4247 //noinspection AssignmentToForLoopParameter
4248 line++;
4250 else {
4251 x += ComplementaryFontsRegistry.getFontAbleToDisplay(c, fontSize, fontType, fontName).charWidth(c, myEditorComponent);
4252 offset++;
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
4264 int maxWidth = 0;
4265 for (int i = 0; i < lineCount; i++) {
4266 maxWidth = Math.max(maxWidth, myLineWidths.getQuick(i));
4269 mySize = new Dimension(maxWidth, getLineHeight() * lineCount);
4271 myIsDirty = false;
4275 public Dimension getContentSize() {
4276 validateSizes();
4277 return mySize;
4281 @NotNull
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();
4289 int column = 0;
4290 int x = 0;
4291 int spaceSize = EditorUtil.getSpaceWidth(fontType, this);
4292 for (int i = start; i < offset; i++) {
4293 if (i >= state.getEndOffset()) {
4294 state.advance();
4295 fontType = state.getMergedAttributes().getFontType();
4298 char c = text.charAt(i);
4299 if (c == '\t') {
4300 int prevX = x;
4301 x = EditorUtil.nextTabStop(x, this);
4302 column += (x - prevX) / spaceSize;
4303 //column += Math.max(1, (x - prevX) / spaceSize);
4305 else {
4306 x += EditorUtil.charWidth(c, fontType, this);
4307 column++;
4311 return column;
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());
4327 return;
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();
4344 super.revalidate();
4347 protected void validateTree() {
4348 int height = myOldHeight;
4349 super.validateTree();
4350 height -= getHeight();
4352 if (height != 0) {
4353 myVerticalScrollBar.setValue(myVerticalScrollBar.getValue() - height);
4355 myOldHeight = getHeight();