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