Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / EditorImpl.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.editor.impl;
17
18 import com.intellij.Patches;
19 import com.intellij.application.options.OptionsConstants;
20 import com.intellij.codeInsight.hint.DocumentFragmentTooltipRenderer;
21 import com.intellij.codeInsight.hint.EditorFragmentComponent;
22 import com.intellij.codeInsight.hint.TooltipController;
23 import com.intellij.codeInsight.hint.TooltipGroup;
24 import com.intellij.concurrency.JobScheduler;
25 import com.intellij.diagnostic.Dumpable;
26 import com.intellij.diagnostic.LogMessageEx;
27 import com.intellij.ide.*;
28 import com.intellij.ide.dnd.DnDManager;
29 import com.intellij.ide.ui.UISettings;
30 import com.intellij.openapi.Disposable;
31 import com.intellij.openapi.actionSystem.*;
32 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
33 import com.intellij.openapi.actionSystem.impl.MouseGestureManager;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.application.ModalityState;
36 import com.intellij.openapi.command.CommandProcessor;
37 import com.intellij.openapi.command.UndoConfirmationPolicy;
38 import com.intellij.openapi.diagnostic.Logger;
39 import com.intellij.openapi.editor.*;
40 import com.intellij.openapi.editor.actionSystem.*;
41 import com.intellij.openapi.editor.actions.EditorActionUtil;
42 import com.intellij.openapi.editor.colors.*;
43 import com.intellij.openapi.editor.colors.impl.DelegateColorScheme;
44 import com.intellij.openapi.editor.event.*;
45 import com.intellij.openapi.editor.ex.*;
46 import com.intellij.openapi.editor.ex.util.EditorUtil;
47 import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter;
48 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
49 import com.intellij.openapi.editor.highlighter.HighlighterClient;
50 import com.intellij.openapi.editor.impl.event.MarkupModelListener;
51 import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces;
52 import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType;
53 import com.intellij.openapi.editor.markup.*;
54 import com.intellij.openapi.fileEditor.FileDocumentManager;
55 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
56 import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
57 import com.intellij.openapi.keymap.KeymapUtil;
58 import com.intellij.openapi.progress.ProgressManager;
59 import com.intellij.openapi.project.Project;
60 import com.intellij.openapi.ui.Queryable;
61 import com.intellij.openapi.util.*;
62 import com.intellij.openapi.util.registry.Registry;
63 import com.intellij.openapi.util.text.StringUtil;
64 import com.intellij.openapi.vfs.VirtualFile;
65 import com.intellij.openapi.wm.IdeFocusManager;
66 import com.intellij.openapi.wm.IdeGlassPane;
67 import com.intellij.openapi.wm.ToolWindowAnchor;
68 import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
69 import com.intellij.psi.PsiDocumentManager;
70 import com.intellij.psi.PsiFile;
71 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
72 import com.intellij.ui.*;
73 import com.intellij.ui.components.JBLayeredPane;
74 import com.intellij.ui.components.JBScrollBar;
75 import com.intellij.ui.components.JBScrollPane;
76 import com.intellij.util.*;
77 import com.intellij.util.containers.ContainerUtil;
78 import com.intellij.util.containers.ContainerUtilRt;
79 import com.intellij.util.messages.MessageBusConnection;
80 import com.intellij.util.text.CharArrayCharSequence;
81 import com.intellij.util.text.CharArrayUtil;
82 import com.intellij.util.ui.*;
83 import com.intellij.util.ui.update.Activatable;
84 import com.intellij.util.ui.update.UiNotifyConnector;
85 import gnu.trove.TIntArrayList;
86 import gnu.trove.TIntFunction;
87 import gnu.trove.TIntHashSet;
88 import gnu.trove.TIntIntHashMap;
89 import org.intellij.lang.annotations.JdkConstants;
90 import org.intellij.lang.annotations.MagicConstant;
91 import org.jetbrains.annotations.NonNls;
92 import org.jetbrains.annotations.NotNull;
93 import org.jetbrains.annotations.Nullable;
94 import org.jetbrains.annotations.TestOnly;
95
96 import javax.swing.*;
97 import javax.swing.Timer;
98 import javax.swing.border.Border;
99 import javax.swing.plaf.ScrollBarUI;
100 import javax.swing.plaf.basic.BasicScrollBarUI;
101 import java.awt.*;
102 import java.awt.datatransfer.DataFlavor;
103 import java.awt.datatransfer.StringSelection;
104 import java.awt.datatransfer.Transferable;
105 import java.awt.dnd.DropTarget;
106 import java.awt.dnd.DropTargetAdapter;
107 import java.awt.dnd.DropTargetDragEvent;
108 import java.awt.dnd.DropTargetDropEvent;
109 import java.awt.event.*;
110 import java.awt.font.TextHitInfo;
111 import java.awt.im.InputMethodRequests;
112 import java.awt.image.BufferedImage;
113 import java.beans.PropertyChangeListener;
114 import java.beans.PropertyChangeSupport;
115 import java.lang.reflect.Field;
116 import java.lang.reflect.InvocationTargetException;
117 import java.text.AttributedCharacterIterator;
118 import java.text.AttributedString;
119 import java.text.CharacterIterator;
120 import java.util.*;
121 import java.util.List;
122 import java.util.concurrent.ScheduledFuture;
123 import java.util.concurrent.TimeUnit;
124
125 public final class EditorImpl extends UserDataHolderBase implements EditorEx, HighlighterClient, Queryable, Dumpable {
126   private static final boolean isOracleRetina = UIUtil.isRetina() && SystemInfo.isOracleJvm;
127   private static final int MIN_FONT_SIZE = 8;
128   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
129   private static final Key DND_COMMAND_KEY = Key.create("DndCommand");
130   @NonNls public static final Object IGNORE_MOUSE_TRACKING = "ignore_mouse_tracking";
131   public static final Key<JComponent> PERMANENT_HEADER = Key.create("PERMANENT_HEADER");
132   public static final Key<Boolean> DO_DOCUMENT_UPDATE_TEST = Key.create("DoDocumentUpdateTest");
133   public static final Key<Boolean> FORCED_SOFT_WRAPS = Key.create("forced.soft.wraps");
134   private static final boolean HONOR_CAMEL_HUMPS_ON_TRIPLE_CLICK =
135     Boolean.parseBoolean(System.getProperty("idea.honor.camel.humps.on.triple.click"));
136   private static final Key<BufferedImage> BUFFER = Key.create("buffer");
137   public static final Color CURSOR_FOREGROUND_LIGHT = Gray._255;
138   public static final Color CURSOR_FOREGROUND_DARK = Gray._0;
139   @NotNull private final DocumentEx myDocument;
140
141   private final JPanel myPanel;
142   @NotNull private final JScrollPane myScrollPane;
143   @NotNull private final EditorComponentImpl myEditorComponent;
144   @NotNull private final EditorGutterComponentImpl myGutterComponent;
145   private final TraceableDisposable myTraceableDisposable = new TraceableDisposable(new Throwable());
146   private int myLinePaintersWidth = 0;
147
148   static {
149     ComplementaryFontsRegistry.getFontAbleToDisplay(' ', 0, 0, UIManager.getFont("Label.font").getFamily()); // load costly font info
150   }
151
152   private final CommandProcessor myCommandProcessor;
153   @NotNull private final MyScrollBar myVerticalScrollBar;
154
155   private final List<EditorMouseListener> myMouseListeners = ContainerUtil.createLockFreeCopyOnWriteList();
156   @NotNull private final List<EditorMouseMotionListener> myMouseMotionListeners = ContainerUtil.createLockFreeCopyOnWriteList();
157
158   private int myCharHeight = -1;
159   private int myLineHeight = -1;
160   private int myDescent    = -1;
161
162   private boolean myIsInsertMode = true;
163
164   @NotNull private final CaretCursor myCaretCursor;
165   private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
166
167   @SuppressWarnings("RedundantStringConstructorCall")
168   private final Object MOUSE_DRAGGED_GROUP = new String("MouseDraggedGroup");
169
170   @NotNull private final SettingsImpl mySettings;
171
172   private boolean isReleased = false;
173
174   @Nullable private MouseEvent myMousePressedEvent = null;
175   @Nullable private MouseEvent myMouseMovedEvent   = null;
176
177   /**
178    * Holds information about area where mouse was pressed.
179    */
180   @Nullable private EditorMouseEventArea myMousePressArea;
181   private int mySavedSelectionStart = -1;
182   private int mySavedSelectionEnd   = -1;
183   private int myLastColumnNumber    = 0;
184
185   private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
186   private MyEditable myEditable;
187
188   @NotNull
189   private EditorColorsScheme myScheme;
190   private ArrowPainter myTabPainter;
191   private final boolean myIsViewer;
192   @NotNull private final SelectionModelImpl mySelectionModel;
193   @NotNull private final EditorMarkupModelImpl myMarkupModel;
194   @NotNull private final FoldingModelImpl myFoldingModel;
195   @NotNull private final ScrollingModelImpl myScrollingModel;
196   @NotNull private final CaretModelImpl myCaretModel;
197   @NotNull private final SoftWrapModelImpl mySoftWrapModel;
198
199   @NotNull private static final RepaintCursorCommand ourCaretBlinkingCommand = new RepaintCursorCommand();
200   private                       MessageBusConnection myConnection;
201
202   private           int        myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
203   @Nullable private FoldRegion myMouseSelectedRegion = null;
204
205   private static final int MOUSE_SELECTION_STATE_NONE          = 0;
206   private static final int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
207   private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
208
209   private EditorHighlighter myHighlighter;
210   private Disposable myHighlighterDisposable = Disposer.newDisposable();
211   private final TextDrawingCallback myTextDrawingCallback = new MyTextDrawingCallback();
212
213   @MagicConstant(intValues = {VERTICAL_SCROLLBAR_LEFT, VERTICAL_SCROLLBAR_RIGHT})
214   private int         myScrollBarOrientation;
215   private boolean     myMousePressedInsideSelection;
216   private FontMetrics myPlainFontMetrics;
217   private FontMetrics myBoldFontMetrics;
218   private FontMetrics myItalicFontMetrics;
219   private FontMetrics myBoldItalicFontMetrics;
220
221   private static final int CACHED_CHARS_BUFFER_SIZE = 300;
222
223   private final     ArrayList<CachedFontContent> myFontCache       = new ArrayList<CachedFontContent>();
224   @Nullable private FontInfo                     myCurrentFontType = null;
225
226   private final EditorSizeContainer mySizeContainer = new EditorSizeContainer();
227
228   private boolean myUpdateCursor;
229   private int myCaretUpdateVShift;
230
231   @Nullable
232   private final Project myProject;
233   private long myMouseSelectionChangeTimestamp;
234   private int mySavedCaretOffsetForDNDUndoHack;
235   private final List<FocusChangeListener> myFocusListeners = ContainerUtil.createLockFreeCopyOnWriteList();
236
237   private MyInputMethodHandler myInputMethodRequestsHandler;
238   private InputMethodRequests myInputMethodRequestsSwingWrapper;
239   private boolean myIsOneLineMode;
240   private boolean myIsRendererMode;
241   private VirtualFile myVirtualFile;
242   private boolean myIsColumnMode = false;
243   @Nullable private Color myForcedBackground = null;
244   @Nullable private Dimension myPreferredSize;
245   private int myVirtualPageHeight;
246   private Alarm myAppleRepaintAlarm;
247
248   private final Alarm myMouseSelectionStateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
249   private Runnable myMouseSelectionStateResetRunnable;
250
251   private boolean myEmbeddedIntoDialogWrapper;
252   @Nullable private CachedFontContent myLastCache;
253   private int myDragOnGutterSelectionStartLine = -1;
254   private RangeMarker myDraggedRange;
255
256   private boolean mySoftWrapsChanged;
257
258   // transient fields used during painting
259   private VisualPosition mySelectionStartPosition;
260   private VisualPosition mySelectionEndPosition;
261
262   private Color myLastBackgroundColor    = null;
263   private Point myLastBackgroundPosition = null;
264   private int myLastBackgroundWidth;
265   private static final boolean ourIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
266   @NotNull private final JPanel myHeaderPanel;
267
268   @Nullable private MouseEvent myInitialMouseEvent;
269   private boolean myIgnoreMouseEventsConsecutiveToInitial;
270
271   private EditorDropHandler myDropHandler;
272
273   private char[] myPrefixText;
274   private TextAttributes myPrefixAttributes;
275   private int myPrefixWidthInPixels;
276   @NotNull private final IndentsModel myIndentsModel;
277
278   @Nullable
279   private CharSequence myPlaceholderText;
280   private int myLastPaintedPlaceholderWidth;
281   private boolean myShowPlaceholderWhenFocused;
282
283   private boolean myStickySelection;
284   private int myStickySelectionStart;
285   private boolean myScrollToCaret = true;
286
287   private boolean myPurePaintingMode;
288   private boolean myPaintSelection;
289
290   private final EditorSizeAdjustmentStrategy mySizeAdjustmentStrategy = new EditorSizeAdjustmentStrategy();
291   private final Disposable myDisposable = Disposer.newDisposable();
292
293   private List<CaretState> myCaretStateBeforeLastPress;
294   private LogicalPosition myLastMousePressedLocation;
295   private VisualPosition myTargetMultiSelectionPosition;
296   private boolean myMultiSelectionInProgress;
297   private boolean myRectangularSelectionInProgress;
298   private boolean myLastPressCreatedCaret;
299   // Set when the selection (normal or block one) initiated by mouse drag becomes noticeable (at least one character is selected).
300   // Reset on mouse press event.
301   private boolean myCurrentDragIsSubstantial;
302
303   private CaretImpl myPrimaryCaret;
304
305   private final boolean myDisableRtl = Registry.is("editor.disable.rtl");
306
307   private final TIntFunction myLineNumberAreaWidthFunction = new TIntFunction() {
308     @Override
309     public int execute(int lineNumber) {
310       return getFontMetrics(Font.PLAIN).stringWidth(Integer.toString(lineNumber + 1)) + 5;
311     }
312   };
313
314   static {
315     ourCaretBlinkingCommand.start();
316   }
317
318   EditorImpl(@NotNull Document document, boolean viewer, @Nullable Project project) {
319     assertIsDispatchThread();
320     myProject = project;
321     myDocument = (DocumentEx)document;
322     if (myDocument instanceof DocumentImpl) {
323       ((DocumentImpl)myDocument).requestTabTracking();
324     }
325     myScheme = createBoundColorSchemeDelegate(null);
326     initTabPainter();
327     myIsViewer = viewer;
328     mySettings = new SettingsImpl(this, project);
329     boolean forceSoftWraps = !mySettings.isUseSoftWraps() && shouldSoftWrapsBeForced(myDocument);
330     if (forceSoftWraps) {
331       mySettings.setUseSoftWrapsQuiet();
332       putUserData(FORCED_SOFT_WRAPS, Boolean.TRUE);
333     }
334
335     mySelectionModel = new SelectionModelImpl(this);
336     myMarkupModel = new EditorMarkupModelImpl(this);
337     myFoldingModel = new FoldingModelImpl(this);
338     myCaretModel = new CaretModelImpl(this);
339     mySoftWrapModel = new SoftWrapModelImpl(this);
340     mySizeContainer.reset();
341
342     myCommandProcessor = CommandProcessor.getInstance();
343
344     if (project != null) {
345       myConnection = project.getMessageBus().connect();
346       myConnection.subscribe(DocumentBulkUpdateListener.TOPIC, new EditorDocumentBulkUpdateAdapter());
347     }
348
349     MarkupModelListener markupModelListener = new MarkupModelListener() {
350       private boolean areRenderersInvolved(@NotNull RangeHighlighterEx highlighter) {
351         return highlighter.getCustomRenderer() != null ||
352                highlighter.getGutterIconRenderer() != null ||
353                highlighter.getLineMarkerRenderer() != null ||
354                highlighter.getLineSeparatorRenderer() != null;
355       }
356       @Override
357       public void afterAdded(@NotNull RangeHighlighterEx highlighter) {
358         attributesChanged(highlighter, areRenderersInvolved(highlighter));
359       }
360
361       @Override
362       public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
363         attributesChanged(highlighter, areRenderersInvolved(highlighter));
364       }
365
366       @Override
367       public void attributesChanged(@NotNull RangeHighlighterEx highlighter, boolean renderersChanged) {
368         if (myDocument.isInBulkUpdate()) return; // bulkUpdateFinished() will repaint anything
369         int textLength = myDocument.getTextLength();
370
371         clearTextWidthCache();
372
373         int start = Math.min(Math.max(highlighter.getAffectedAreaStartOffset(), 0), textLength);
374         int end = Math.min(Math.max(highlighter.getAffectedAreaEndOffset(), 0), textLength);
375
376         int startLine = start == -1 ? 0 : myDocument.getLineNumber(start);
377         int endLine = end == -1 ? myDocument.getLineCount() : myDocument.getLineNumber(end);
378         repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount()));
379
380         // optimization: there is no need to repaint error stripe if the highlighter is invisible on it
381         if (renderersChanged || highlighter.getErrorStripeMarkColor() != null) {
382           ((EditorMarkupModelImpl)getMarkupModel()).repaint(start, end);
383         }
384
385         if (renderersChanged) {
386           updateGutterSize();
387         }
388         updateCaretCursor();
389       }
390     };
391
392     ((MarkupModelEx)DocumentMarkupModel.forDocument(myDocument, myProject, true)).addMarkupModelListener(myCaretModel, markupModelListener);
393     getMarkupModel().addMarkupModelListener(myCaretModel, markupModelListener);
394
395     myDocument.addDocumentListener(myFoldingModel, myCaretModel);
396     myDocument.addDocumentListener(myCaretModel, myCaretModel);
397     myDocument.addDocumentListener(mySelectionModel, myCaretModel);
398
399     myDocument.addDocumentListener(new EditorDocumentAdapter(), myCaretModel);
400     myDocument.addDocumentListener(mySoftWrapModel, myCaretModel);
401
402     myFoldingModel.addListener(mySoftWrapModel, myCaretModel);
403
404     myIndentsModel = new IndentsModelImpl(this);
405     myCaretModel.addCaretListener(new CaretListener() {
406       @Nullable private LightweightHint myCurrentHint = null;
407       @Nullable private IndentGuideDescriptor myCurrentCaretGuide = null;
408
409       @Override
410       public void caretPositionChanged(CaretEvent e) {
411         if (myStickySelection) {
412           int selectionStart = Math.min(myStickySelectionStart, getDocument().getTextLength() - 1);
413           mySelectionModel.setSelection(selectionStart, myCaretModel.getVisualPosition(), myCaretModel.getOffset());
414         }
415
416         final IndentGuideDescriptor newGuide = myIndentsModel.getCaretIndentGuide();
417         if (!Comparing.equal(myCurrentCaretGuide, newGuide)) {
418           repaintGuide(newGuide);
419           repaintGuide(myCurrentCaretGuide);
420           myCurrentCaretGuide = newGuide;
421
422           if (myCurrentHint != null) {
423             myCurrentHint.hide();
424             myCurrentHint = null;
425           }
426
427           if (newGuide != null) {
428             final Rectangle visibleArea = getScrollingModel().getVisibleArea();
429             final int line = newGuide.startLine;
430             if (logicalLineToY(line) < visibleArea.y) {
431               TextRange textRange = new TextRange(myDocument.getLineStartOffset(line), myDocument.getLineEndOffset(line));
432
433               myCurrentHint = EditorFragmentComponent.showEditorFragmentHint(EditorImpl.this, textRange, false, false);
434             }
435           }
436         }
437       }
438
439       @Override
440       public void caretAdded(CaretEvent e) {
441         if (myPrimaryCaret != null) {
442           myPrimaryCaret.updateVisualPosition(); // repainting old primary caret's row background
443         }
444         repaintCaretRegion(e);
445         myPrimaryCaret = myCaretModel.getPrimaryCaret();
446       }
447
448       @Override
449       public void caretRemoved(CaretEvent e) {
450         repaintCaretRegion(e);
451         myPrimaryCaret = myCaretModel.getPrimaryCaret(); // repainting new primary caret's row background
452         myPrimaryCaret.updateVisualPosition();
453       }
454     });
455
456     myCaretCursor = new CaretCursor();
457
458     myFoldingModel.flushCaretShift();
459     myScrollBarOrientation = VERTICAL_SCROLLBAR_RIGHT;
460
461     mySoftWrapModel.addSoftWrapChangeListener(new SoftWrapChangeListenerAdapter() {
462       @Override
463       public void recalculationEnds() {
464         if (myCaretModel.isUpToDate()) {
465           myCaretModel.updateVisualPosition();
466         }
467       }
468
469       @Override
470       public void softWrapsChanged() {
471         mySoftWrapsChanged = true;
472       }
473     });
474
475     mySoftWrapModel.addVisualSizeChangeListener(new VisualSizeChangeListener() {
476       @Override
477       public void onLineWidthsChange(int startLine, int oldEndLine, int newEndLine, @NotNull TIntIntHashMap lineWidths) {
478         mySizeContainer.update(startLine, newEndLine, oldEndLine);
479         for (int i = startLine; i <= newEndLine; i++) {
480           if (lineWidths.contains(i)) {
481             int width = lineWidths.get(i);
482             if (width >= 0) {
483               mySizeContainer.updateLineWidthIfNecessary(i, width);
484             }
485           }
486         }
487       }
488     });
489
490     EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
491     setHighlighter(highlighter);
492
493     myEditorComponent = new EditorComponentImpl(this);
494     myScrollPane = new MyScrollPane();
495     myVerticalScrollBar = (MyScrollBar)myScrollPane.getVerticalScrollBar();
496     myVerticalScrollBar.setOpaque(false);
497     myPanel = new JPanel();
498
499     UIUtil.putClientProperty(
500       myPanel, JBSwingUtilities.NOT_IN_HIERARCHY_COMPONENTS, new Iterable<JComponent>() {
501         @Override
502         public Iterator<JComponent> iterator() {
503           JComponent component = getPermanentHeaderComponent();
504           if (component != null && !component.isValid()) {
505             return Collections.singleton(component).iterator();
506           }
507           return ContainerUtil.emptyIterator();
508         }
509       });
510
511     myHeaderPanel = new MyHeaderPanel();
512     myGutterComponent = new EditorGutterComponentImpl(this);
513     initComponent();
514     myScrollingModel = new ScrollingModelImpl(this);
515     if (UISettings.getInstance().PRESENTATION_MODE) {
516       setFontSize(UISettings.getInstance().PRESENTATION_MODE_FONT_SIZE);
517     }
518
519     myGutterComponent.setLineNumberAreaWidth(myLineNumberAreaWidthFunction);
520     myGutterComponent.updateSize();
521     Dimension preferredSize = getPreferredSize();
522     myEditorComponent.setSize(preferredSize);
523
524     if (Patches.APPLE_BUG_ID_3716835) {
525       myScrollingModel.addVisibleAreaListener(new VisibleAreaListener() {
526         @Override
527         public void visibleAreaChanged(VisibleAreaEvent e) {
528           if (myAppleRepaintAlarm == null) {
529             myAppleRepaintAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
530           }
531           myAppleRepaintAlarm.cancelAllRequests();
532           myAppleRepaintAlarm.addRequest(new Runnable() {
533             @Override
534             public void run() {
535               repaint(0, myDocument.getTextLength());
536             }
537           }, 50, ModalityState.stateForComponent(myEditorComponent));
538         }
539       });
540     }
541
542     updateCaretCursor();
543
544     // This hacks context layout problem where editor appears scrolled to the right just after it is created.
545     if (!ourIsUnitTestMode) {
546       UiNotifyConnector.doWhenFirstShown(myEditorComponent, new Runnable() {
547         @Override
548         public void run() {
549           if (!isDisposed() && !myScrollingModel.isScrollingNow()) {
550             myScrollingModel.disableAnimation();
551             myScrollingModel.scrollHorizontally(0);
552             myScrollingModel.enableAnimation();
553           }
554         }
555       });
556     }
557   }
558
559   private static boolean shouldSoftWrapsBeForced(Document document) {
560     int lineWidthLimit = Registry.intValue("editor.soft.wrap.force.limit");
561     for (int i = 0; i < document.getLineCount(); i++) {
562       if (document.getLineEndOffset(i) - document.getLineStartOffset(i) > lineWidthLimit) {
563         return true;
564       }
565     }
566     return false;
567   }
568
569   @NotNull
570   static Color adjustThumbColor(@NotNull Color base, boolean dark) {
571     return dark ? ColorUtil.withAlpha(ColorUtil.shift(base, 1.35), 0.5) 
572                 : ColorUtil.withAlpha(ColorUtil.shift(base, 0.68), 0.4);
573   }
574
575   boolean isDarkEnough() {
576     return ColorUtil.isDark(getBackgroundColor());
577   }
578
579   private void repaintCaretRegion(CaretEvent e) {
580     CaretImpl caretImpl = (CaretImpl)e.getCaret();
581     if (caretImpl != null) {
582       caretImpl.updateVisualPosition();
583       if (caretImpl.hasSelection()) {
584         repaint(caretImpl.getSelectionStart(), caretImpl.getSelectionEnd());
585       }
586     }
587   }
588
589   @NotNull
590   @Override
591   public EditorColorsScheme createBoundColorSchemeDelegate(@Nullable final EditorColorsScheme customGlobalScheme) {
592     return new MyColorSchemeDelegate(customGlobalScheme);
593   }
594
595   private void repaintGuide(@Nullable IndentGuideDescriptor guide) {
596     if (guide != null) {
597       repaintLines(guide.startLine, guide.endLine);
598     }
599   }
600
601   @Override
602   public int getPrefixTextWidthInPixels() {
603     return myPrefixWidthInPixels;
604   }
605
606   @Override
607   public void setPrefixTextAndAttributes(@Nullable String prefixText, @Nullable TextAttributes attributes) {
608     myPrefixText = prefixText == null ? null : prefixText.toCharArray();
609     myPrefixAttributes = attributes;
610     myPrefixWidthInPixels = 0;
611     if (myPrefixText != null) {
612       for (char c : myPrefixText) {
613         LOG.assertTrue(myPrefixAttributes != null);
614         if (myPrefixAttributes != null) {
615           myPrefixWidthInPixels += EditorUtil.charWidth(c, myPrefixAttributes.getFontType(), this);
616         }
617       }
618     }
619     mySoftWrapModel.recalculate();
620   }
621
622   @Override
623   public boolean isPurePaintingMode() {
624     return myPurePaintingMode;
625   }
626
627   @Override
628   public void setPurePaintingMode(boolean enabled) {
629     myPurePaintingMode = enabled;
630   }
631
632   @Override
633   public void registerScrollBarRepaintCallback(@Nullable ButtonlessScrollBarUI.ScrollbarRepaintCallback callback) {
634     myVerticalScrollBar.registerRepaintCallback(callback);
635   }
636
637   @Override
638   public boolean isViewer() {
639     return myIsViewer || myIsRendererMode;
640   }
641
642   @Override
643   public boolean isRendererMode() {
644     return myIsRendererMode;
645   }
646
647   @Override
648   public void setRendererMode(boolean isRendererMode) {
649     myIsRendererMode = isRendererMode;
650   }
651
652   @Override
653   public void setFile(VirtualFile vFile) {
654     myVirtualFile = vFile;
655     reinitSettings();
656   }
657
658   @Override
659   public VirtualFile getVirtualFile() {
660     return myVirtualFile;
661   }
662
663   @Override
664   public void setSoftWrapAppliancePlace(@NotNull SoftWrapAppliancePlaces place) {
665     getSoftWrapModel().setPlace(place);
666     mySettings.setSoftWrapAppliancePlace(place);
667   }
668
669   @Override
670   @NotNull
671   public SelectionModelImpl getSelectionModel() {
672     return mySelectionModel;
673   }
674
675   @Override
676   @NotNull
677   public MarkupModelEx getMarkupModel() {
678     return myMarkupModel;
679   }
680
681   @Override
682   @NotNull
683   public FoldingModelImpl getFoldingModel() {
684     return myFoldingModel;
685   }
686
687   @Override
688   @NotNull
689   public CaretModelImpl getCaretModel() {
690     return myCaretModel;
691   }
692
693   @Override
694   @NotNull
695   public ScrollingModelEx getScrollingModel() {
696     return myScrollingModel;
697   }
698
699   @Override
700   @NotNull
701   public SoftWrapModelImpl getSoftWrapModel() {
702     return mySoftWrapModel;
703   }
704
705   @Override
706   @NotNull
707   public EditorSettings getSettings() {
708     assertReadAccess();
709     return mySettings;
710   }
711
712   public void resetSizes() {
713     mySizeContainer.reset();
714   }
715
716   @Override
717   public void reinitSettings() {
718     assertIsDispatchThread();
719     clearSettingsCache();
720
721     reinitDocumentIndentOptions();
722
723     for (EditorColorsScheme scheme = myScheme; scheme instanceof DelegateColorScheme; scheme = ((DelegateColorScheme)scheme).getDelegate()) {
724       if (scheme instanceof MyColorSchemeDelegate) {
725         ((MyColorSchemeDelegate)scheme).updateGlobalScheme();
726         break;
727       }
728     }
729
730     boolean softWrapsUsedBefore = mySoftWrapModel.isSoftWrappingEnabled();
731
732     mySettings.reinitSettings();
733     mySoftWrapModel.reinitSettings();
734     myCaretModel.reinitSettings();
735     mySelectionModel.reinitSettings();
736     ourCaretBlinkingCommand.setBlinkCaret(mySettings.isBlinkCaret());
737     ourCaretBlinkingCommand.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
738     mySizeContainer.reset();
739     myFoldingModel.rebuild();
740
741     if (softWrapsUsedBefore ^ mySoftWrapModel.isSoftWrappingEnabled()) {
742       mySizeContainer.reset();
743       validateSize();
744     }
745
746     myHighlighter.setColorScheme(myScheme);
747     myFoldingModel.refreshSettings();
748
749     myGutterComponent.reinitSettings();
750     myGutterComponent.revalidate();
751
752     myEditorComponent.repaint();
753
754     initTabPainter();
755     updateCaretCursor();
756
757     if (myInitialMouseEvent != null) {
758       myIgnoreMouseEventsConsecutiveToInitial = true;
759     }
760
761     if (myCaretModel.supportsMultipleCarets()) {
762       myCaretModel.updateVisualPosition();
763     }
764     else {
765       // There is a possible case that 'use soft wrap' setting value is changed and we need to repaint all affected lines then.
766       repaintToScreenBottom(getCaretModel().getLogicalPosition().line);
767       int y = getCaretModel().getVisualLineStart() * getLineHeight();
768       myGutterComponent.repaint(0, y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - y);
769     }
770     // make sure carets won't appear at invalid positions (e.g. on Tab width change)
771     for (Caret caret : getCaretModel().getAllCarets()) {
772       caret.moveToOffset(caret.getOffset());
773     }
774   }
775
776   private void clearSettingsCache() {
777     myCharHeight = -1;
778     myLineHeight = -1;
779     myDescent = -1;
780     myPlainFontMetrics = null;
781
782     clearTextWidthCache();
783   }
784
785   private void reinitDocumentIndentOptions() {
786     if (myProject != null && !myProject.isDisposed()) {
787       CodeStyleSettingsManager.updateDocumentIndentOptions(myProject, myDocument);
788     }
789   }
790
791   private void initTabPainter() {
792     myTabPainter = new ArrowPainter(
793       ColorProvider.byColorsScheme(myScheme, EditorColors.WHITESPACES_COLOR),
794       new Computable.PredefinedValueComputable<Integer>(EditorUtil.getSpaceWidth(Font.PLAIN, this)),
795       new Computable<Integer>() {
796         @Override
797         public Integer compute() {
798           return getCharHeight();
799         }
800       }
801     );
802   }
803
804   public void throwDisposalError(@NonNls @NotNull String msg) {
805     myTraceableDisposable.throwDisposalError(msg);
806   }
807
808   public void release() {
809     assertIsDispatchThread();
810     if (isReleased) {
811       throwDisposalError("Double release of editor:");
812     }
813     myTraceableDisposable.kill(null);
814
815     isReleased = true;
816     clearSettingsCache();
817
818     myFoldingModel.dispose();
819     mySoftWrapModel.release();
820     myMarkupModel.dispose();
821
822     myScrollingModel.dispose();
823     myGutterComponent.dispose();
824     myMousePressedEvent = null;
825     myMouseMovedEvent = null;
826     Disposer.dispose(myCaretModel);
827     Disposer.dispose(mySoftWrapModel);
828     clearCaretThread();
829
830     myFocusListeners.clear();
831     myMouseListeners.clear();
832     myMouseMotionListeners.clear();
833
834     if (myConnection != null) {
835       myConnection.disconnect();
836     }
837     if (myDocument instanceof DocumentImpl) {
838       ((DocumentImpl)myDocument).giveUpTabTracking();
839     }
840     Disposer.dispose(myDisposable);
841   }
842
843   private void clearCaretThread() {
844     synchronized (ourCaretBlinkingCommand) {
845       if (ourCaretBlinkingCommand.myEditor == this) {
846         ourCaretBlinkingCommand.myEditor = null;
847       }
848     }
849   }
850
851   private void initComponent() {
852     myPanel.setLayout(new BorderLayout());
853
854     myPanel.add(myHeaderPanel, BorderLayout.NORTH);
855
856     myGutterComponent.setOpaque(true);
857
858     myScrollPane.setViewportView(myEditorComponent);
859     //myScrollPane.setBorder(null);
860     myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
861     myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
862
863     myScrollPane.setRowHeaderView(myGutterComponent);
864
865     myEditorComponent.setTransferHandler(new MyTransferHandler());
866     myEditorComponent.setAutoscrolls(true);
867
868    /*  Default mode till 1.4.0
869     *   myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
870     */
871
872     if (mayShowToolbar()) {
873       JLayeredPane layeredPane = new JBLayeredPane() {
874         @Override
875         public void doLayout() {
876           final Component[] components = getComponents();
877           final Rectangle r = getBounds();
878           for (Component c : components) {
879             if (c instanceof JScrollPane) {
880               c.setBounds(0, 0, r.width, r.height);
881             }
882             else {
883               final Dimension d = c.getPreferredSize();
884               final MyScrollBar scrollBar = getVerticalScrollBar();
885               c.setBounds(r.width - d.width - scrollBar.getWidth() - 30, 20, d.width, d.height);
886             }
887           }
888         }
889       };
890
891       layeredPane.add(myScrollPane, JLayeredPane.DEFAULT_LAYER);
892       myPanel.add(layeredPane);
893
894       new ContextMenuImpl(layeredPane, myScrollPane, this);
895     }
896     else {
897       myPanel.add(myScrollPane);
898     }
899
900     myEditorComponent.addKeyListener(new KeyAdapter() {
901       @Override
902       public void keyTyped(@NotNull KeyEvent event) {
903         if (Patches.APPLE_BUG_ID_3337563)
904           return; // Everything is going through InputMethods under MacOS X in JDK releases earlier than 1.4.2_03-117.1
905         if (event.isConsumed()) {
906           return;
907         }
908         if (processKeyTyped(event)) {
909           event.consume();
910         }
911       }
912     });
913
914     MyMouseAdapter mouseAdapter = new MyMouseAdapter();
915     myEditorComponent.addMouseListener(mouseAdapter);
916     myGutterComponent.addMouseListener(mouseAdapter);
917
918     MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
919     myEditorComponent.addMouseMotionListener(mouseMotionListener);
920     myGutterComponent.addMouseMotionListener(mouseMotionListener);
921
922     myEditorComponent.addFocusListener(new FocusAdapter() {
923       @Override
924       public void focusGained(@NotNull FocusEvent e) {
925         myCaretCursor.activate();
926         for (Caret caret : myCaretModel.getAllCarets()) {
927           int caretLine = caret.getLogicalPosition().line;
928           repaintLines(caretLine, caretLine);
929         }
930         fireFocusGained();
931       }
932
933       @Override
934       public void focusLost(@NotNull FocusEvent e) {
935         clearCaretThread();
936         for (Caret caret : myCaretModel.getAllCarets()) {
937           int caretLine = caret.getLogicalPosition().line;
938           repaintLines(caretLine, caretLine);
939         }
940         fireFocusLost();
941       }
942     });
943
944     UiNotifyConnector connector = new UiNotifyConnector(myEditorComponent, new Activatable.Adapter() {
945       @Override
946       public void showNotify() {
947         myGutterComponent.updateSize();
948       }
949     });
950     Disposer.register(getDisposable(), connector);
951
952     try {
953       final DropTarget dropTarget = myEditorComponent.getDropTarget();
954       if (dropTarget != null) { // might be null in headless environment
955         dropTarget.addDropTargetListener(new DropTargetAdapter() {
956           @Override
957           public void drop(@NotNull DropTargetDropEvent e) {
958           }
959
960           @Override
961           public void dragOver(@NotNull DropTargetDragEvent e) {
962             Point location = e.getLocation();
963
964             getCaretModel().moveToLogicalPosition(getLogicalPositionForScreenPos(location.x, location.y, true));
965             getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
966           }
967         });
968       }
969     }
970     catch (TooManyListenersException e) {
971       LOG.error(e);
972     }
973
974     myPanel.addComponentListener(new ComponentAdapter() {
975       @Override
976       public void componentResized(@NotNull ComponentEvent e) {
977         myMarkupModel.recalcEditorDimensions();
978         myMarkupModel.repaint(-1, -1);
979       }
980     });
981   }
982
983   private boolean mayShowToolbar() {
984     return !isEmbeddedIntoDialogWrapper() && !isOneLineMode() && ContextMenuImpl.mayShowToolbar(myDocument);
985   }
986
987   @Override
988   public void setFontSize(final int fontSize) {
989     setFontSize(fontSize, null);
990   }
991
992   /**
993    * Changes editor font size, attempting to keep a given point unmoved. If point is not given, top left screen corner is assumed.
994    *
995    * @param fontSize new font size
996    * @param zoomCenter zoom point, relative to viewport
997    */
998   private void setFontSize(final int fontSize, @Nullable Point zoomCenter) {
999     int oldFontSize = myScheme.getEditorFontSize();
1000
1001     Rectangle visibleArea = myScrollingModel.getVisibleArea();
1002     Point zoomCenterRelative = zoomCenter == null ? new Point() : zoomCenter;
1003     Point zoomCenterAbsolute = new Point(visibleArea.x + zoomCenterRelative.x, visibleArea.y + zoomCenterRelative.y);
1004     LogicalPosition zoomCenterLogical = xyToLogicalPosition(zoomCenterAbsolute).withoutVisualPositionInfo();
1005     int oldLineHeight = getLineHeight();
1006     int intraLineOffset = zoomCenterAbsolute.y % oldLineHeight;
1007
1008     myScheme.setEditorFontSize(fontSize);
1009     myPropertyChangeSupport.firePropertyChange(PROP_FONT_SIZE, oldFontSize, fontSize);
1010     // Update vertical scroll bar bounds if necessary (we had a problem that use increased editor font size and it was not possible
1011     // to scroll to the bottom of the document).
1012     myScrollPane.getViewport().invalidate();
1013
1014     Point shiftedZoomCenterAbsolute = logicalPositionToXY(zoomCenterLogical);
1015     myScrollingModel.disableAnimation();
1016     try {
1017       myScrollingModel.scrollToOffsets(visibleArea.x == 0 ? 0 : shiftedZoomCenterAbsolute.x - zoomCenterRelative.x, // stick to left border if it's visible
1018                                        shiftedZoomCenterAbsolute.y - zoomCenterRelative.y + (intraLineOffset * getLineHeight() + oldLineHeight / 2) / oldLineHeight);
1019     } finally {
1020       myScrollingModel.enableAnimation();
1021     }
1022   }
1023
1024   public int getFontSize() {
1025     return myScheme.getEditorFontSize();
1026   }
1027
1028   @NotNull
1029   public ActionCallback type(@NotNull final String text) {
1030     final ActionCallback result = new ActionCallback();
1031
1032     ApplicationManager.getApplication().runWriteAction(new Runnable() {
1033       @Override
1034       public void run() {
1035         for (int i = 0; i < text.length(); i++) {
1036           if (!processKeyTyped(text.charAt(i))) {
1037             result.setRejected();
1038             return;
1039           }
1040         }
1041
1042         result.setDone();
1043       }
1044     });
1045
1046     return result;
1047   }
1048
1049   private boolean processKeyTyped(char c) {
1050     // [vova] This is patch for Mac OS X. Under Mac "input methods"
1051     // is handled before our EventQueue consume upcoming KeyEvents.
1052     IdeEventQueue queue = IdeEventQueue.getInstance();
1053     if (queue.shouldNotTypeInEditor() || ProgressManager.getInstance().hasModalProgressIndicator()) {
1054       return false;
1055     }
1056     FileDocumentManager manager = FileDocumentManager.getInstance();
1057     final VirtualFile file = manager.getFile(myDocument);
1058     if (file != null && !file.isValid()) {
1059       return false;
1060     }
1061
1062     ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
1063     DataContext dataContext = getDataContext();
1064     actionManager.fireBeforeEditorTyping(c, dataContext);
1065     MacUIUtil.hideCursor();
1066     EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
1067
1068     return true;
1069   }
1070
1071   private void fireFocusLost() {
1072     for (FocusChangeListener listener : myFocusListeners) {
1073       listener.focusLost(this);
1074     }
1075   }
1076
1077   private void fireFocusGained() {
1078     for (FocusChangeListener listener : myFocusListeners) {
1079       listener.focusGained(this);
1080     }
1081   }
1082
1083   @Override
1084   public void setHighlighter(@NotNull final EditorHighlighter highlighter) {
1085     assertIsDispatchThread();
1086     final Document document = getDocument();
1087     Disposer.dispose(myHighlighterDisposable);
1088
1089     document.addDocumentListener(highlighter);
1090     myHighlighter = highlighter;
1091     myHighlighterDisposable = new Disposable() {
1092       @Override
1093       public void dispose() {
1094         document.removeDocumentListener(highlighter);
1095       }
1096     };
1097     Disposer.register(myDisposable, myHighlighterDisposable);
1098     highlighter.setEditor(this);
1099     highlighter.setText(document.getImmutableCharSequence());
1100     EditorHighlighterCache.rememberEditorHighlighterForCachesOptimization(document, highlighter);
1101
1102     if (myPanel != null) {
1103       reinitSettings();
1104     }
1105   }
1106
1107   @NotNull
1108   @Override
1109   public EditorHighlighter getHighlighter() {
1110     assertReadAccess();
1111     return myHighlighter;
1112   }
1113
1114   @Override
1115   @NotNull
1116   public EditorComponentImpl getContentComponent() {
1117     return myEditorComponent;
1118   }
1119
1120   @NotNull
1121   @Override
1122   public EditorGutterComponentEx getGutterComponentEx() {
1123     return myGutterComponent;
1124   }
1125
1126   @Override
1127   public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
1128     myPropertyChangeSupport.addPropertyChangeListener(listener);
1129   }
1130
1131   @Override
1132   public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
1133     myPropertyChangeSupport.removePropertyChangeListener(listener);
1134   }
1135
1136   @Override
1137   public void setInsertMode(boolean mode) {
1138     assertIsDispatchThread();
1139     boolean oldValue = myIsInsertMode;
1140     myIsInsertMode = mode;
1141     myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, mode);
1142     if (myCaretModel.supportsMultipleCarets()) {
1143       myCaretCursor.repaint();
1144     }
1145     else {
1146       //Repaint the caret line by moving caret to the same place
1147       LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
1148       getCaretModel().moveToLogicalPosition(caretPosition);
1149     }
1150   }
1151
1152   @Override
1153   public boolean isInsertMode() {
1154     return myIsInsertMode;
1155   }
1156
1157   @Override
1158   public void setColumnMode(boolean mode) {
1159     assertIsDispatchThread();
1160     boolean oldValue = myIsColumnMode;
1161     myIsColumnMode = mode;
1162     myPropertyChangeSupport.firePropertyChange(PROP_COLUMN_MODE, oldValue, mode);
1163   }
1164
1165   @Override
1166   public boolean isColumnMode() {
1167     return myIsColumnMode;
1168   }
1169
1170   private int yPositionToVisibleLine(int y) {
1171     assert y >= 0 : y;
1172     return y / getLineHeight();
1173   }
1174
1175   @Override
1176   @NotNull
1177   public VisualPosition xyToVisualPosition(@NotNull Point p) {
1178     int line = yPositionToVisibleLine(p.y);
1179     int px = p.x;
1180     if (line == 0 && myPrefixText != null) {
1181       px -= myPrefixWidthInPixels;
1182     }
1183
1184     int textLength = myDocument.getTextLength();
1185     LogicalPosition logicalPosition = visualToLogicalPosition(new VisualPosition(line, 0));
1186     int offset = logicalPositionToOffset(logicalPosition);
1187     int plainSpaceSize = EditorUtil.getSpaceWidth(Font.PLAIN, this);
1188
1189     if (offset >= textLength) return new VisualPosition(line, EditorUtil.columnsNumber(p.x, plainSpaceSize));
1190
1191     // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn.
1192     // We mark that we're under such circumstances then.
1193     boolean activeSoftWrapProcessed = logicalPosition.softWrapLinesOnCurrentLogicalLine <= 0;
1194
1195     CharSequence text = myDocument.getImmutableCharSequence();
1196
1197     LogicalPosition endLogicalPosition = visualToLogicalPosition(new VisualPosition(line + 1, 0));
1198     int endOffset = logicalPositionToOffset(endLogicalPosition);
1199
1200     if (offset > endOffset) {
1201       LogMessageEx.error(LOG, "Detected invalid (x; y)->VisualPosition processing", String.format(
1202         "Given point: %s, mapped to visual line %d. Visual(%d; %d) is mapped to "
1203         + "logical position '%s' which is mapped to offset %d (start offset). Visual(%d; %d) is mapped to logical '%s' which is mapped "
1204         + "to offset %d (end offset). State: %s",
1205         p, line, line, 0, logicalPosition, offset, line + 1, 0, endLogicalPosition, endOffset, dumpState()
1206       ));
1207       return new VisualPosition(line, EditorUtil.columnsNumber(p.x, plainSpaceSize));
1208     }
1209     IterationState state = new IterationState(this, offset, endOffset, false);
1210
1211     int fontType = state.getMergedAttributes().getFontType();
1212
1213     int x = 0;
1214     int charWidth;
1215     boolean onSoftWrapDrawing = false;
1216     char c = ' ';
1217     int prevX = 0;
1218     int column = 0;
1219     outer:
1220     while (true) {
1221       charWidth = -1;
1222       if (offset >= textLength) {
1223         break;
1224       }
1225
1226       if (offset >= state.getEndOffset()) {
1227         state.advance();
1228         fontType = state.getMergedAttributes().getFontType();
1229       }
1230
1231       SoftWrap softWrap = mySoftWrapModel.getSoftWrap(offset);
1232       if (softWrap != null) {
1233         if (activeSoftWrapProcessed) {
1234           prevX = x;
1235           charWidth = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
1236           x += charWidth;
1237           if (x >= px) {
1238             onSoftWrapDrawing = true;
1239           }
1240           else {
1241             column++;
1242           }
1243           break;
1244         }
1245         else {
1246           CharSequence softWrapText = softWrap.getText();
1247           for (int i = 1/*Assuming line feed is located at the first position*/; i < softWrapText.length(); i++) {
1248             c = softWrapText.charAt(i);
1249             prevX = x;
1250             charWidth = charToVisibleWidth(c, fontType, x);
1251             x += charWidth;
1252             if (x >= px) {
1253               break outer;
1254             }
1255             column += EditorUtil.columnsNumber(c, x, prevX, plainSpaceSize);
1256           }
1257
1258           // Process 'after soft wrap' sign.
1259           prevX = x;
1260           charWidth = mySoftWrapModel.getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
1261           x += charWidth;
1262           if (x >= px) {
1263             onSoftWrapDrawing = true;
1264             break;
1265           }
1266           column++;
1267           activeSoftWrapProcessed = true;
1268         }
1269       }
1270       FoldRegion region = state.getCurrentFold();
1271       if (region != null) {
1272         char[] placeholder = region.getPlaceholderText().toCharArray();
1273         for (char aPlaceholder : placeholder) {
1274           c = aPlaceholder;
1275           x += EditorUtil.charWidth(c, fontType, this);
1276           if (x >= px) {
1277             break outer;
1278           }
1279           column++;
1280         }
1281         offset = region.getEndOffset();
1282       }
1283       else {
1284         prevX = x;
1285         c = text.charAt(offset);
1286         if (c == '\n') {
1287           break;
1288         }
1289         charWidth = charToVisibleWidth(c, fontType, x);
1290         x += charWidth;
1291
1292         if (x >= px) {
1293           break;
1294         }
1295         column += EditorUtil.columnsNumber(c, x, prevX, plainSpaceSize);
1296
1297         offset++;
1298       }
1299     }
1300
1301     if (charWidth < 0) {
1302       charWidth = EditorUtil.charWidth(c, fontType, this);
1303     }
1304
1305     if (charWidth < 0) {
1306       charWidth = plainSpaceSize;
1307     }
1308
1309     if (x >= px && c == '\t' && !onSoftWrapDrawing) {
1310       if (mySettings.isCaretInsideTabs()) {
1311         column += (px - prevX) / plainSpaceSize;
1312         if ((px - prevX) % plainSpaceSize > plainSpaceSize / 2) column++;
1313       }
1314       else if ((x - px) * 2 < x - prevX) {
1315         column += EditorUtil.columnsNumber(c, x, prevX, plainSpaceSize);
1316       }
1317     }
1318     else {
1319       if (x >= px) {
1320         if (c != '\n' && (x - px) * 2 < charWidth) column++;
1321       }
1322       else {
1323         int diff = px - x;
1324         column += diff / plainSpaceSize;
1325         if (diff % plainSpaceSize * 2 >= plainSpaceSize) {
1326           column++;
1327         }
1328       }
1329     }
1330
1331     return new VisualPosition(line, column);
1332   }
1333
1334   /**
1335    * Allows to answer how much width requires given char to be represented on a screen.
1336    *
1337    * @param c        target character
1338    * @param fontType font type to use for representation of the given character
1339    * @param currentX current <code>'x'</code> position on a line where given character should be displayed
1340    * @return width required to represent given char with the given settings on a screen;
1341    *         <code>'0'</code> if given char is a line break
1342    */
1343   private int charToVisibleWidth(char c, @JdkConstants.FontStyle int fontType, int currentX) {
1344     if (c == '\n') {
1345       return 0;
1346     }
1347
1348     if (c == '\t') {
1349       return EditorUtil.nextTabStop(currentX, this) - currentX;
1350     }
1351     return EditorUtil.charWidth(c, fontType, this);
1352   }
1353
1354   @Override
1355   @NotNull
1356   public VisualPosition offsetToVisualPosition(int offset) {
1357     return logicalToVisualPosition(offsetToLogicalPosition(offset));
1358   }
1359
1360   @Override
1361   @NotNull
1362   public LogicalPosition offsetToLogicalPosition(int offset) {
1363     return offsetToLogicalPosition(offset, true);
1364   }
1365
1366   @NotNull
1367   @Override
1368   public LogicalPosition offsetToLogicalPosition(int offset, boolean softWrapAware) {
1369     if (softWrapAware) {
1370       return mySoftWrapModel.offsetToLogicalPosition(offset);
1371     }
1372     int line = offsetToLogicalLine(offset);
1373     int column = calcColumnNumber(offset, line, false, myDocument.getImmutableCharSequence());
1374     return new LogicalPosition(line, column);
1375   }
1376
1377   @TestOnly
1378   public void setCaretActive() {
1379     synchronized (ourCaretBlinkingCommand) {
1380       ourCaretBlinkingCommand.myEditor = this;
1381     }
1382   }
1383
1384   // optimization: do not do column calculations here since we are interested in line number only
1385   public int offsetToVisualLine(int offset) {
1386     int textLength = getDocument().getTextLength();
1387     if (offset >= textLength) {
1388       return Math.max(0, getVisibleLineCount() - 1); // lines are 0 based
1389     }
1390     int line = offsetToLogicalLine(offset);
1391     int lineStartOffset = line >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(line);
1392
1393     int result = logicalToVisualLine(line);
1394
1395     // There is a possible case that logical line that contains target offset is soft-wrapped (represented in more than one visual
1396     // line). Hence, we need to perform necessary adjustments to the visual line that is used to show logical line start if necessary.
1397     int i = getSoftWrapModel().getSoftWrapIndex(lineStartOffset);
1398     if (i < 0) {
1399       i = -i - 1;
1400     }
1401     List<? extends SoftWrap> softWraps = getSoftWrapModel().getRegisteredSoftWraps();
1402     for (; i < softWraps.size(); i++) {
1403       SoftWrap softWrap = softWraps.get(i);
1404       if (softWrap.getStart() > offset) {
1405         break;
1406       }
1407       result++; // Assuming that every soft wrap contains only one virtual line feed symbol
1408     }
1409     return result;
1410   }
1411
1412   private int logicalToVisualLine(int line) {
1413     assertReadAccess();
1414     return logicalToVisualPosition(new LogicalPosition(line, 0)).line;
1415   }
1416
1417   @Override
1418   @NotNull
1419   public LogicalPosition xyToLogicalPosition(@NotNull Point p) {
1420     Point pp = p.x >= 0 && p.y >= 0 ? p : new Point(Math.max(p.x, 0), Math.max(p.y, 0));
1421     return visualToLogicalPosition(xyToVisualPosition(pp));
1422   }
1423
1424   private int logicalLineToY(int line) {
1425     VisualPosition visible = logicalToVisualPosition(new LogicalPosition(line, 0));
1426     return visibleLineToY(visible.line);
1427   }
1428
1429   @Override
1430   @NotNull
1431   public Point logicalPositionToXY(@NotNull LogicalPosition pos) {
1432     VisualPosition visible = logicalToVisualPosition(pos);
1433     return visualPositionToXY(visible);
1434   }
1435
1436   @Override
1437   @NotNull
1438   public Point visualPositionToXY(@NotNull VisualPosition visible) {
1439     int y = visibleLineToY(visible.line);
1440     LogicalPosition logical = visualToLogicalPosition(new VisualPosition(visible.line, 0));
1441     int logLine = logical.line;
1442
1443     int lineStartOffset = -1;
1444     int reserved = 0;
1445     int column = visible.column;
1446
1447     if (logical.softWrapLinesOnCurrentLogicalLine > 0) {
1448       int linesToSkip = logical.softWrapLinesOnCurrentLogicalLine;
1449       List<? extends SoftWrap> softWraps = getSoftWrapModel().getSoftWrapsForLine(logLine);
1450       for (SoftWrap softWrap : softWraps) {
1451         if (myFoldingModel.isOffsetCollapsed(softWrap.getStart()) && myFoldingModel.isOffsetCollapsed(softWrap.getStart() - 1)) {
1452           continue;
1453         }
1454         linesToSkip--; // Assuming here that every soft wrap has exactly one line feed
1455         if (linesToSkip > 0) {
1456           continue;
1457         }
1458         lineStartOffset = softWrap.getStart();
1459         int widthInColumns = softWrap.getIndentInColumns();
1460         int widthInPixels = softWrap.getIndentInPixels();
1461         if (widthInColumns <= column) {
1462           column -= widthInColumns;
1463           reserved = widthInPixels;
1464         }
1465         else {
1466           char[] softWrapChars = softWrap.getChars();
1467           int i = CharArrayUtil.lastIndexOf(softWrapChars, '\n', 0, softWrapChars.length);
1468           int start = 0;
1469           if (i >= 0) {
1470             start = i + 1;
1471           }
1472           return new Point(EditorUtil.textWidth(this, softWrap.getText(), start, column + 1, Font.PLAIN, 0), y);
1473         }
1474         break;
1475       }
1476     }
1477
1478     if (logLine < 0) {
1479       lineStartOffset = 0;
1480     }
1481     else if (lineStartOffset < 0) {
1482       if (logLine >= myDocument.getLineCount()) {
1483         lineStartOffset = myDocument.getTextLength();
1484       }
1485       else {
1486         lineStartOffset = myDocument.getLineStartOffset(logLine);
1487       }
1488     }
1489
1490     int x = getTabbedTextWidth(lineStartOffset, column, reserved);
1491     return new Point(x, y);
1492   }
1493
1494   private int calcEndOffset(int startOffset, int visualColumn) {
1495     FoldRegion[] regions = myFoldingModel.fetchTopLevel();
1496     if (regions == null) {
1497       return startOffset + visualColumn;
1498     }
1499
1500     int low = 0;
1501     int high = regions.length - 1;
1502     int i = -1;
1503
1504     while (low <= high) {
1505       int mid = low + high >>> 1;
1506       FoldRegion midVal = regions[mid];
1507
1508       if (midVal.getStartOffset() <= startOffset && midVal.getEndOffset() > startOffset) {
1509         i = mid;
1510         break;
1511       }
1512
1513       if (midVal.getStartOffset() < startOffset)
1514         low = mid + 1;
1515       else if (midVal.getStartOffset() > startOffset)
1516         high = mid - 1;
1517     }
1518     if (i < 0) {
1519       i = low;
1520     }
1521
1522     int result = startOffset;
1523     int columnsToProcess = visualColumn;
1524     for (; i < regions.length; i++) {
1525       FoldRegion region = regions[i];
1526
1527       // Process text between the last fold region end and current fold region start.
1528       int nonFoldTextColumnsNumber = region.getStartOffset() - result;
1529       if (nonFoldTextColumnsNumber >= columnsToProcess) {
1530         return result + columnsToProcess;
1531       }
1532       columnsToProcess -= nonFoldTextColumnsNumber;
1533
1534       // Process fold region.
1535       int placeHolderLength = region.getPlaceholderText().length();
1536       if (placeHolderLength >= columnsToProcess) {
1537         return region.getEndOffset();
1538       }
1539       result = region.getEndOffset();
1540       columnsToProcess -= placeHolderLength;
1541     }
1542     return result + columnsToProcess;
1543   }
1544
1545   // TODO: tabbed text width is additive, it should be possible to have buckets, containing arguments / values to start with
1546   private final int[] myLastStartOffsets = new int[2];
1547   private final int[] myLastTargetColumns = new int[myLastStartOffsets.length];
1548   private final int[] myLastXOffsets = new int[myLastStartOffsets.length];
1549   private final int[] myLastXs = new int[myLastStartOffsets.length];
1550   private int myCurrentCachePosition;
1551   private int myLastCacheHits;
1552   private int myTotalRequests; // todo remove
1553
1554   private int getTabbedTextWidth(int startOffset, int targetColumn, int xOffset) {
1555     int x = xOffset;
1556     if (startOffset == 0 && myPrefixText != null) {
1557       x += myPrefixWidthInPixels;
1558     }
1559     if (targetColumn <= 0) return x;
1560
1561     ++myTotalRequests;
1562     for(int i = 0; i < myLastStartOffsets.length; ++i) {
1563       if (startOffset == myLastStartOffsets[i] && targetColumn == myLastTargetColumns[i] && xOffset == myLastXOffsets[i]) {
1564         ++myLastCacheHits;
1565         if ((myLastCacheHits & 0xFFF) == 0) {    // todo remove
1566           PsiFile file = myProject != null ? PsiDocumentManager.getInstance(myProject).getCachedPsiFile(myDocument):null;
1567           LOG.info("Cache hits:" + myLastCacheHits + ", total requests:" +
1568                              myTotalRequests + "," + (file != null ? file.getViewProvider().getVirtualFile():null));
1569         }
1570         return myLastXs[i];
1571       }
1572     }
1573
1574     int offset = startOffset;
1575     CharSequence text = myDocument.getImmutableCharSequence();
1576     int textLength = myDocument.getTextLength();
1577
1578     // We need to calculate max offset to provide to the IterationState here based on the given start offset and target
1579     // visual column. The problem is there is a possible case that there is a collapsed fold region at the target interval,
1580     // so, we can't just use 'startOffset + targetColumn' as a max end offset.
1581     IterationState state = new IterationState(this, startOffset, calcEndOffset(startOffset, targetColumn), false);
1582     int fontType = state.getMergedAttributes().getFontType();
1583     int plainSpaceSize = EditorUtil.getSpaceWidth(Font.PLAIN, this);
1584
1585     int column = 0;
1586     outer:
1587     while (column < targetColumn) {
1588       if (offset >= textLength) break;
1589
1590       if (offset >= state.getEndOffset()) {
1591         state.advance();
1592         fontType = state.getMergedAttributes().getFontType();
1593       }
1594       // We need to consider 'before soft wrap drawing'.
1595       SoftWrap softWrap = getSoftWrapModel().getSoftWrap(offset);
1596       if (softWrap != null && offset > startOffset) {
1597         column++;
1598         x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
1599         // Assuming that first soft wrap symbol is line feed or all soft wrap symbols before the first line feed are spaces.
1600         break;
1601       }
1602
1603       FoldRegion region = state.getCurrentFold();
1604
1605       if (region != null) {
1606         char[] placeholder = region.getPlaceholderText().toCharArray();
1607         for (char aPlaceholder : placeholder) {
1608           x += EditorUtil.charWidth(aPlaceholder, fontType, this);
1609           column++;
1610           if (column >= targetColumn) break outer;
1611         }
1612         offset = region.getEndOffset();
1613       }
1614       else {
1615         char c = text.charAt(offset);
1616         if (c == '\n') {
1617           break;
1618         }
1619         if (c == '\t') {
1620           int prevX = x;
1621           x = EditorUtil.nextTabStop(x, this);
1622           int columnDiff = (x - prevX) / plainSpaceSize;
1623           if ((x - prevX) % plainSpaceSize > 0) {
1624             // There is a possible case that tabulation symbol takes more than one visual column to represent and it's shown at
1625             // soft-wrapped line. Soft wrap sign width may be not divisible by space size, hence, part of tabulation symbol represented
1626             // as a separate visual column may take less space than space width.
1627             columnDiff++;
1628           }
1629           column += columnDiff;
1630         }
1631         else {
1632           x += EditorUtil.charWidth(c, fontType, this);
1633           column++;
1634         }
1635         offset++;
1636       }
1637     }
1638
1639     if (column != targetColumn) {
1640       x += EditorUtil.getSpaceWidth(fontType, this) * (targetColumn - column);
1641     }
1642
1643     myLastTargetColumns[myCurrentCachePosition] = targetColumn;
1644     myLastStartOffsets[myCurrentCachePosition] = startOffset;
1645     myLastXs[myCurrentCachePosition] = x;
1646     myLastXOffsets[myCurrentCachePosition] = xOffset;
1647     myCurrentCachePosition = (myCurrentCachePosition + 1) % myLastStartOffsets.length;
1648
1649     return x;
1650   }
1651
1652   private void clearTextWidthCache() {
1653     for(int i = 0; i < myLastStartOffsets.length; ++i) {
1654       myLastTargetColumns[i] = -1;
1655       myLastStartOffsets[i] = - 1;
1656       myLastXs[i] = -1;
1657       myLastXOffsets[i] = -1;
1658     }
1659   }
1660
1661   public int visibleLineToY(int line) {
1662     if (line < 0) throw new IndexOutOfBoundsException("Wrong line: " + line);
1663     return line * getLineHeight();
1664   }
1665
1666   @Override
1667   public void repaint(final int startOffset, int endOffset) {
1668     if (!isShowing() || myDocument.isInBulkUpdate()) {
1669       return;
1670     }
1671
1672     endOffset = Math.min(endOffset, myDocument.getTextLength());
1673     assertIsDispatchThread();
1674
1675     // We do repaint in case of equal offsets because there is a possible case that there is a soft wrap at the same offset and
1676     // it does occupy particular amount of visual space that may be necessary to repaint.
1677     if (startOffset <= endOffset) {
1678       int startLine = myDocument.getLineNumber(startOffset);
1679       int endLine = myDocument.getLineNumber(endOffset);
1680       repaintLines(startLine, endLine);
1681     }
1682   }
1683
1684   private boolean isShowing() {
1685     return myGutterComponent.isShowing();
1686   }
1687
1688   private void repaintToScreenBottom(int startLine) {
1689     Rectangle visibleArea = getScrollingModel().getVisibleArea();
1690     int yStartLine = logicalLineToY(startLine);
1691     int yEndLine = visibleArea.y + visibleArea.height;
1692
1693     myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, yEndLine - yStartLine);
1694     myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
1695     ((EditorMarkupModelImpl)getMarkupModel()).repaint(-1, -1);
1696   }
1697
1698   /**
1699    * Asks to repaint all logical lines from the given <code>[start; end]</code> range.
1700    *
1701    * @param startLine start logical line to repaint (inclusive)
1702    * @param endLine   end logical line to repaint (inclusive)
1703    */
1704   public void repaintLines(int startLine, int endLine) {
1705     if (!isShowing()) return;
1706
1707     Rectangle visibleArea = getScrollingModel().getVisibleArea();
1708     int yStartLine = logicalLineToY(startLine);
1709     int endVisLine;
1710     if (myDocument.getTextLength() <= 0) {
1711       endVisLine = 0;
1712     }
1713     else {
1714       endVisLine = offsetToVisualLine(myDocument.getLineEndOffset(Math.min(myDocument.getLineCount() - 1, endLine)));
1715     }
1716     int height = endVisLine * getLineHeight() - yStartLine + getLineHeight() + WAVE_HEIGHT;
1717
1718     myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, height);
1719     myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), height);
1720   }
1721
1722   private void bulkUpdateStarted() {
1723     saveCaretRelativePosition();
1724     
1725     myCaretModel.onBulkDocumentUpdateStarted();
1726     mySoftWrapModel.onBulkDocumentUpdateStarted();
1727     myFoldingModel.onBulkDocumentUpdateStarted();
1728   }
1729
1730   private void bulkUpdateFinished() {
1731     myFoldingModel.onBulkDocumentUpdateFinished();
1732     mySoftWrapModel.onBulkDocumentUpdateFinished();
1733     myCaretModel.onBulkDocumentUpdateFinished();
1734
1735     clearTextWidthCache();
1736
1737     mySelectionModel.removeBlockSelection();
1738     setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
1739
1740     mySizeContainer.reset();
1741     validateSize();
1742
1743     updateGutterSize();
1744     repaintToScreenBottom(0);
1745     updateCaretCursor();
1746     
1747     restoreCaretRelativePosition();
1748   }
1749
1750   private void beforeChangedUpdate(@NotNull DocumentEvent e) {
1751     if (isStickySelection()) {
1752       setStickySelection(false);
1753     }
1754     if (myDocument.isInBulkUpdate()) {
1755       // Assuming that the job is done at bulk listener callback methods.
1756       return;
1757     }
1758
1759     saveCaretRelativePosition();
1760
1761     // We assume that size container is already notified with the visual line widths during soft wraps processing
1762     if (!mySoftWrapModel.isSoftWrappingEnabled()) {
1763       mySizeContainer.beforeChange(e);
1764     }
1765   }
1766
1767   private void changedUpdate(DocumentEvent e) {
1768     if (myDocument.isInBulkUpdate()) return;
1769
1770     clearTextWidthCache();
1771     mySelectionModel.removeBlockSelection();
1772     setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
1773
1774     // We assume that size container is already notified with the visual line widths during soft wraps processing
1775     if (!mySoftWrapModel.isSoftWrappingEnabled()) {
1776       mySizeContainer.changedUpdate(e);
1777     }
1778     validateSize();
1779
1780     int startLine = offsetToLogicalLine(e.getOffset());
1781     int endLine = offsetToLogicalLine(e.getOffset() + e.getNewLength());
1782
1783     boolean painted = false;
1784     if (myDocument.getTextLength() > 0) {
1785       int startDocLine = myDocument.getLineNumber(e.getOffset());
1786       int endDocLine = myDocument.getLineNumber(e.getOffset() + e.getNewLength());
1787       if (e.getOldLength() > e.getNewLength() || startDocLine != endDocLine || StringUtil.indexOf(e.getOldFragment(), '\n') != -1) {
1788         updateGutterSize();
1789       }
1790
1791       if (countLineFeeds(e.getOldFragment()) != countLineFeeds(e.getNewFragment())) {
1792         // Lines removed. Need to repaint till the end of the screen
1793         repaintToScreenBottom(startLine);
1794         painted = true;
1795       }
1796     }
1797
1798     updateCaretCursor();
1799     if (!painted) {
1800       repaintLines(startLine, endLine);
1801     }
1802
1803     if (getCaretModel().getOffset() < e.getOffset() || getCaretModel().getOffset() > e.getOffset() + e.getNewLength()){
1804       restoreCaretRelativePosition();
1805     }
1806   }
1807
1808   private void saveCaretRelativePosition() {
1809     Rectangle visibleArea = getScrollingModel().getVisibleArea();
1810     Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
1811     myCaretUpdateVShift = pos.y - visibleArea.y;
1812   }
1813
1814   private void restoreCaretRelativePosition() {
1815     Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
1816     int scrollOffset = caretLocation.y - myCaretUpdateVShift;
1817     getScrollingModel().disableAnimation();
1818     getScrollingModel().scrollVertically(scrollOffset);
1819     getScrollingModel().enableAnimation();
1820   }
1821
1822   public boolean hasTabs() {
1823     return !(myDocument instanceof DocumentImpl) || ((DocumentImpl)myDocument).mightContainTabs();
1824   }
1825
1826   public boolean isScrollToCaret() {
1827     return myScrollToCaret;
1828   }
1829
1830   public void setScrollToCaret(boolean scrollToCaret) {
1831     myScrollToCaret = scrollToCaret;
1832   }
1833
1834   @NotNull
1835   public Disposable getDisposable() {
1836     return myDisposable;
1837   }
1838
1839   private static int countLineFeeds(@NotNull CharSequence c) {
1840     return StringUtil.countNewLines(c);
1841   }
1842
1843   private boolean updatingSize; // accessed from EDT only
1844   private void updateGutterSize() {
1845     assertIsDispatchThread();
1846     if (!updatingSize) {
1847       updatingSize = true;
1848       SwingUtilities.invokeLater(new Runnable() {
1849         @Override
1850         public void run() {
1851           try {
1852             if (!isDisposed()) {
1853               myGutterComponent.updateSize();
1854             }
1855           }
1856           finally {
1857             updatingSize = false;
1858           }
1859         }
1860       });
1861     }
1862   }
1863
1864   void validateSize() {
1865     Dimension dim = getPreferredSize();
1866
1867     if (!dim.equals(myPreferredSize) && !myDocument.isInBulkUpdate()) {
1868       dim = mySizeAdjustmentStrategy.adjust(dim, myPreferredSize, this);
1869       if (dim == null) {
1870         return;
1871       }
1872       myPreferredSize = dim;
1873
1874       myGutterComponent.setLineNumberAreaWidth(myLineNumberAreaWidthFunction);
1875       myGutterComponent.updateSize();
1876
1877       myEditorComponent.setSize(dim);
1878       myEditorComponent.fireResized();
1879
1880       myMarkupModel.recalcEditorDimensions();
1881       myMarkupModel.repaint(-1, -1);
1882     }
1883   }
1884
1885   void recalculateSizeAndRepaint() {
1886     mySizeContainer.reset();
1887     validateSize();
1888     myEditorComponent.repaintEditorComponent();
1889   }
1890
1891   @Override
1892   @NotNull
1893   public DocumentEx getDocument() {
1894     return myDocument;
1895   }
1896
1897   @Override
1898   @NotNull
1899   public JComponent getComponent() {
1900     return myPanel;
1901   }
1902
1903   @Override
1904   public void addEditorMouseListener(@NotNull EditorMouseListener listener) {
1905     myMouseListeners.add(listener);
1906   }
1907
1908   @Override
1909   public void removeEditorMouseListener(@NotNull EditorMouseListener listener) {
1910     boolean success = myMouseListeners.remove(listener);
1911     LOG.assertTrue(success || isReleased);
1912   }
1913
1914   @Override
1915   public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
1916     myMouseMotionListeners.add(listener);
1917   }
1918
1919   @Override
1920   public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
1921     boolean success = myMouseMotionListeners.remove(listener);
1922     LOG.assertTrue(success || isReleased);
1923   }
1924
1925   @Override
1926   public boolean isStickySelection() {
1927     return myStickySelection;
1928   }
1929
1930   @Override
1931   public void setStickySelection(boolean enable) {
1932     myStickySelection = enable;
1933     if (enable) {
1934       myStickySelectionStart = getCaretModel().getOffset();
1935     }
1936     else {
1937       mySelectionModel.removeSelection();
1938     }
1939   }
1940
1941   @Override
1942   public boolean isDisposed() {
1943     return isReleased;
1944   }
1945
1946   public void stopDumbLater() {
1947     if (ApplicationManager.getApplication().isUnitTestMode()) return;
1948     final Runnable stopDumbRunnable = new Runnable() {
1949       @Override
1950       public void run() {
1951         stopDumb();
1952       }
1953     };
1954     ApplicationManager.getApplication().invokeLater(stopDumbRunnable, ModalityState.current());
1955   }
1956
1957   void resetPaintersWidth() {
1958     myLinePaintersWidth = 0;
1959   }
1960
1961   public void stopDumb() {
1962     putUserData(BUFFER, null);
1963   }
1964
1965   /**
1966    * {@link #stopDumbLater} or {@link #stopDumb} must be performed in finally
1967    */
1968   public void startDumb() {
1969     if (ApplicationManager.getApplication().isUnitTestMode()) return;
1970     final JComponent component = getContentComponent();
1971     final Rectangle rect = ((JViewport)component.getParent()).getViewRect();
1972     BufferedImage image = UIUtil.createImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
1973     final Graphics2D graphics = image.createGraphics();
1974     UISettings.setupAntialiasing(graphics);
1975     graphics.translate(-rect.x, -rect.y);
1976     graphics.setClip(rect.x, rect.y, rect.width, rect.height);
1977     paint(graphics);
1978     graphics.dispose();
1979     putUserData(BUFFER, image);
1980   }
1981
1982   void paint(@NotNull Graphics2D g) {
1983     Rectangle clip = g.getClipBounds();
1984
1985     if (clip == null) {
1986       return;
1987     }
1988
1989     if (Registry.is("editor.dumb.mode.available")) {
1990       final BufferedImage buffer = getUserData(BUFFER);
1991       if (buffer != null) {
1992         final Rectangle rect = getContentComponent().getVisibleRect();
1993         UIUtil.drawImage(g, buffer, null, rect.x, rect.y);
1994         return;
1995       }
1996     }
1997
1998     if (myUpdateCursor) {
1999       setCursorPosition();
2000       myUpdateCursor = false;
2001     }
2002
2003     if (isReleased) {
2004       g.setColor(new JBColor(new Color(128, 255, 128), new Color(128, 255, 128)));
2005       g.fillRect(clip.x, clip.y, clip.width, clip.height);
2006       return;
2007     }
2008     if (myProject != null && myProject.isDisposed()) return;
2009
2010     VisualPosition clipStartVisualPos = xyToVisualPosition(new Point(0, clip.y));
2011     LogicalPosition clipStartPosition = visualToLogicalPosition(clipStartVisualPos);
2012     int clipStartOffset = logicalPositionToOffset(clipStartPosition);
2013     LogicalPosition clipEndPosition = xyToLogicalPosition(new Point(0, clip.y + clip.height + getLineHeight()));
2014     int clipEndOffset = logicalPositionToOffset(clipEndPosition);
2015     paintBackgrounds(g, clip, clipStartPosition, clipStartVisualPos, clipStartOffset, clipEndOffset);
2016     if (paintPlaceholderText(g, clip)) {
2017       paintCaretCursor(g);
2018       return;
2019     }
2020
2021     paintRectangularSelection(g);
2022     paintRightMargin(g, clip);
2023     paintCustomRenderers(g, clipStartOffset, clipEndOffset);
2024     MarkupModelEx docMarkup = (MarkupModelEx)DocumentMarkupModel.forDocument(myDocument, myProject, true);
2025     paintLineMarkersSeparators(g, clip, docMarkup, clipStartOffset, clipEndOffset);
2026     paintLineMarkersSeparators(g, clip, myMarkupModel, clipStartOffset, clipEndOffset);
2027     paintText(g, clip, clipStartPosition, clipStartOffset, clipEndOffset);
2028     paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip, clipStartOffset, clipEndOffset, docMarkup);
2029     BorderEffect borderEffect = new BorderEffect(this, g, clipStartOffset, clipEndOffset);
2030     borderEffect.paintHighlighters(getHighlighter());
2031     borderEffect.paintHighlighters(docMarkup);
2032     borderEffect.paintHighlighters(myMarkupModel);
2033
2034     paintCaretCursor(g);
2035
2036     paintComposedTextDecoration(g);
2037   }
2038
2039   private static final char IDEOGRAPHIC_SPACE = '\u3000'; // http://www.marathon-studios.com/unicode/U3000/Ideographic_Space
2040   private static final String WHITESPACE_CHARS = " \t" + IDEOGRAPHIC_SPACE;
2041
2042   private void paintCustomRenderers(@NotNull final Graphics2D g, final int clipStartOffset, final int clipEndOffset) {
2043     myMarkupModel.processRangeHighlightersOverlappingWith(clipStartOffset, clipEndOffset, new Processor<RangeHighlighterEx>() {
2044       @Override
2045       public boolean process(@NotNull RangeHighlighterEx highlighter) {
2046         if (!highlighter.getEditorFilter().avaliableIn(EditorImpl.this)) return true;
2047
2048         final CustomHighlighterRenderer customRenderer = highlighter.getCustomRenderer();
2049         if (customRenderer != null && clipStartOffset < highlighter.getEndOffset() && highlighter.getStartOffset() < clipEndOffset) {
2050           customRenderer.paint(EditorImpl.this, highlighter, g);
2051         }
2052         return true;
2053       }
2054     });
2055   }
2056
2057   @NotNull
2058   @Override
2059   public IndentsModel getIndentsModel() {
2060     return myIndentsModel;
2061   }
2062
2063   @Override
2064   public void setHeaderComponent(JComponent header) {
2065     myHeaderPanel.removeAll();
2066     header = header == null ? getPermanentHeaderComponent() : header;
2067     if (header != null) {
2068       myHeaderPanel.add(header);
2069     }
2070
2071     myHeaderPanel.revalidate();
2072   }
2073
2074   @Override
2075   public boolean hasHeaderComponent() {
2076     JComponent header = getHeaderComponent();
2077     return header != null && header != getPermanentHeaderComponent();
2078   }
2079
2080   @Override
2081   @Nullable
2082   public JComponent getPermanentHeaderComponent() {
2083     return getUserData(PERMANENT_HEADER);
2084   }
2085
2086   @Override
2087   public void setPermanentHeaderComponent(@Nullable JComponent component) {
2088     putUserData(PERMANENT_HEADER, component);
2089   }
2090
2091   @Override
2092   @Nullable
2093   public JComponent getHeaderComponent() {
2094     if (myHeaderPanel.getComponentCount() > 0) {
2095       return (JComponent)myHeaderPanel.getComponent(0);
2096     }
2097     return null;
2098   }
2099
2100   @Override
2101   public void setBackgroundColor(Color color) {
2102     myScrollPane.setBackground(color);
2103
2104     if (getBackgroundIgnoreForced().equals(color)) {
2105       myForcedBackground = null;
2106       return;
2107     }
2108     myForcedBackground = color;
2109   }
2110
2111   @NotNull
2112   private Color getForegroundColor() {
2113     return myScheme.getDefaultForeground();
2114   }
2115
2116   @NotNull
2117   @Override
2118   public Color getBackgroundColor() {
2119     if (myForcedBackground != null) return myForcedBackground;
2120
2121     return getBackgroundIgnoreForced();
2122   }
2123
2124   @NotNull
2125   @Override
2126   public TextDrawingCallback getTextDrawingCallback() {
2127     return myTextDrawingCallback;
2128   }
2129
2130   @Override
2131   public void setPlaceholder(@Nullable CharSequence text) {
2132     myPlaceholderText = text;
2133   }
2134
2135   @Override
2136   public void setShowPlaceholderWhenFocused(boolean show) {
2137     myShowPlaceholderWhenFocused = show;
2138   }
2139
2140   Color getBackgroundColor(@NotNull final TextAttributes attributes) {
2141     final Color attrColor = attributes.getBackgroundColor();
2142     return Comparing.equal(attrColor, myScheme.getDefaultBackground()) ? getBackgroundColor() : attrColor;
2143   }
2144
2145   @NotNull
2146   private Color getBackgroundIgnoreForced() {
2147     Color color = myScheme.getDefaultBackground();
2148     if (myDocument.isWritable()) {
2149       return color;
2150     }
2151     Color readOnlyColor = myScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR);
2152     return readOnlyColor != null ? readOnlyColor : color;
2153   }
2154
2155   private void paintComposedTextDecoration(@NotNull Graphics2D g) {
2156     if (myInputMethodRequestsHandler != null
2157         && myInputMethodRequestsHandler.composedText != null
2158         && myInputMethodRequestsHandler.composedTextRange != null) {
2159       VisualPosition visStart =
2160         offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextRange.getStartOffset(), myDocument.getTextLength()));
2161       int y = visibleLineToY(visStart.line) + getAscent() + 1;
2162       Point p1 = visualPositionToXY(visStart);
2163       Point p2 = logicalPositionToXY(
2164         offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextRange.getEndOffset(), myDocument.getTextLength())));
2165
2166       Stroke saved = g.getStroke();
2167       BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2, 0, 2}, 0);
2168       g.setStroke(dotted);
2169       UIUtil.drawLine(g, p1.x, y, p2.x, y);
2170       g.setStroke(saved);
2171     }
2172   }
2173
2174   private void paintRightMargin(@NotNull Graphics g, @NotNull Rectangle clip) {
2175     Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
2176     if (!mySettings.isRightMarginShown() || rightMargin == null) {
2177       return;
2178     }
2179     int x = mySettings.getRightMargin(myProject) * EditorUtil.getSpaceWidth(Font.PLAIN, this);
2180     if (x >= clip.x && x < clip.x + clip.width) {
2181       g.setColor(rightMargin);
2182       UIUtil.drawLine(g, x, clip.y, x, clip.y + clip.height);
2183     }
2184   }
2185
2186   private void paintSegmentHighlightersBorderAndAfterEndOfLine(@NotNull final Graphics g,
2187                                                                @NotNull Rectangle clip,
2188                                                                int clipStartOffset,
2189                                                                int clipEndOffset,
2190                                                                @NotNull MarkupModelEx docMarkup) {
2191     if (myDocument.getLineCount() == 0) return;
2192     final int startLine = yPositionToVisibleLine(clip.y);
2193     final int endLine = yPositionToVisibleLine(clip.y + clip.height) + 1;
2194
2195     Processor<RangeHighlighterEx> paintProcessor = new Processor<RangeHighlighterEx>() {
2196       @Override
2197       public boolean process(@NotNull RangeHighlighterEx highlighter) {
2198         if (!highlighter.getEditorFilter().avaliableIn(EditorImpl.this)) return true;
2199
2200         paintSegmentHighlighterAfterEndOfLine(g, highlighter, startLine, endLine);
2201         return true;
2202       }
2203     };
2204     docMarkup.processRangeHighlightersOverlappingWith(clipStartOffset, clipEndOffset, paintProcessor);
2205     myMarkupModel.processRangeHighlightersOverlappingWith(clipStartOffset, clipEndOffset, paintProcessor);
2206   }
2207
2208   private void paintSegmentHighlighterAfterEndOfLine(@NotNull Graphics g,
2209                                                      @NotNull RangeHighlighterEx segmentHighlighter,
2210                                                      int startLine,
2211                                                      int endLine) {
2212     if (!segmentHighlighter.isAfterEndOfLine()) {
2213       return;
2214     }
2215     int startOffset = segmentHighlighter.getStartOffset();
2216     int visibleStartLine = offsetToVisualLine(startOffset);
2217
2218     if (getFoldingModel().isOffsetCollapsed(startOffset)) {
2219       return;
2220     }
2221     if (visibleStartLine >= startLine && visibleStartLine <= endLine) {
2222       int logStartLine = offsetToLogicalLine(startOffset);
2223       if (logStartLine >= myDocument.getLineCount()) {
2224         return;
2225       }
2226       LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
2227       Point end = logicalPositionToXY(logPosition);
2228       int charWidth = EditorUtil.getSpaceWidth(Font.PLAIN, this);
2229       int lineHeight = getLineHeight();
2230       TextAttributes attributes = segmentHighlighter.getTextAttributes();
2231       if (attributes != null && getBackgroundColor(attributes) != null) {
2232         g.setColor(getBackgroundColor(attributes));
2233         g.fillRect(end.x, end.y, charWidth, lineHeight);
2234       }
2235       if (attributes != null && attributes.getEffectColor() != null) {
2236         int y = visibleLineToY(visibleStartLine) + getAscent() + 1;
2237         g.setColor(attributes.getEffectColor());
2238         if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
2239           drawWave(g, end.x, end.x + charWidth - 1, y);
2240         }
2241         else if (attributes.getEffectType() == EffectType.BOLD_DOTTED_LINE) {
2242           final int dottedAt = SystemInfo.isMac ? y - 1 : y;
2243           UIUtil.drawBoldDottedLine((Graphics2D)g, end.x, end.x + charWidth - 1, dottedAt,
2244                                     getBackgroundColor(attributes), attributes.getEffectColor(), false);
2245         }
2246         else if (attributes.getEffectType() == EffectType.STRIKEOUT) {
2247           int y1 = y - getCharHeight() / 2 - 1;
2248           UIUtil.drawLine(g, end.x, y1, end.x + charWidth - 1, y1);
2249         }
2250         else if (attributes.getEffectType() == EffectType.BOLD_LINE_UNDERSCORE) {
2251           UIUtil.drawLine(g, end.x, y - 1, end.x + charWidth - 1, y - 1);
2252           UIUtil.drawLine(g, end.x, y, end.x + charWidth - 1, y);
2253         }
2254         else if (attributes.getEffectType() != EffectType.BOXED) {
2255           UIUtil.drawLine(g, end.x, y, end.x + charWidth - 1, y);
2256         }
2257       }
2258     }
2259   }
2260
2261   @Override
2262   public int getMaxWidthInRange(int startOffset, int endOffset) {
2263     int width = 0;
2264     int start = offsetToVisualLine(startOffset);
2265     int end = offsetToVisualLine(endOffset);
2266
2267     for (int i = start; i <= end; i++) {
2268       int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
2269       int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
2270
2271       if (lineWidth > width) {
2272         width = lineWidth;
2273       }
2274     }
2275
2276     return width;
2277   }
2278
2279   private void paintBackgrounds(@NotNull Graphics g,
2280                                 @NotNull Rectangle clip,
2281                                 @NotNull LogicalPosition clipStartPosition,
2282                                 @NotNull VisualPosition clipStartVisualPos,
2283                                 int clipStartOffset, int clipEndOffset) {
2284     Color defaultBackground = getBackgroundColor();
2285     if (myEditorComponent.isOpaque()) {
2286       g.setColor(defaultBackground);
2287       g.fillRect(clip.x, clip.y, clip.width, clip.height);
2288     }
2289
2290     int lineHeight = getLineHeight();
2291
2292     int visibleLine = yPositionToVisibleLine(clip.y);
2293
2294     Point position = new Point(0, visibleLine * lineHeight);
2295     CharSequence prefixText = myPrefixText == null ? null : new CharArrayCharSequence(myPrefixText);
2296     if (clipStartVisualPos.line == 0 && prefixText != null) {
2297       position.x = drawBackground(g, myPrefixAttributes.getBackgroundColor(), prefixText, 0, prefixText.length(), position,
2298                                   myPrefixAttributes.getFontType(),
2299                                   defaultBackground, clip);
2300     }
2301
2302     if (clipStartPosition.line >= myDocument.getLineCount() || clipStartPosition.line < 0) {
2303       if (position.x > 0) flushBackground(g, clip);
2304       return;
2305     }
2306
2307     myLastBackgroundPosition = null;
2308     myLastBackgroundColor = null;
2309     mySelectionStartPosition = null;
2310     mySelectionEndPosition = null;
2311
2312     int start = clipStartOffset;
2313
2314     if (!myPurePaintingMode) {
2315       getSoftWrapModel().registerSoftWrapsIfNecessary();
2316     }
2317
2318     LineIterator lIterator = createLineIterator();
2319     lIterator.start(start);
2320     if (lIterator.atEnd()) {
2321       return;
2322     }
2323
2324     IterationState iterationState = new IterationState(this, start, clipEndOffset, isPaintSelection());
2325     TextAttributes attributes = iterationState.getMergedAttributes();
2326     Color backColor = getBackgroundColor(attributes);
2327     int fontType = attributes.getFontType();
2328     int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
2329
2330     // There is a possible case that we need to draw background from the start of soft wrap-introduced visual line. Given position
2331     // has valid 'y' coordinate then at it shouldn't be affected by soft wrap that corresponds to the visual line start offset.
2332     // Hence, we store information about soft wrap to be skipped for further processing and adjust 'x' coordinate value if necessary.
2333     TIntHashSet softWrapsToSkip = new TIntHashSet();
2334     SoftWrap softWrap = getSoftWrapModel().getSoftWrap(start);
2335     if (softWrap != null) {
2336       softWrapsToSkip.add(softWrap.getStart());
2337       Color color = null;
2338       if (backColor != null && !backColor.equals(defaultBackground)) {
2339         color = backColor;
2340       }
2341
2342       // There is a possible case that target clip points to soft wrap-introduced visual line and that it's an active
2343       // line (caret cursor is located on it). We want to draw corresponding 'caret line' background for soft wraps-introduced
2344       // virtual space then.
2345       if (color == null && position.y == getCaretModel().getVisualPosition().line * getLineHeight()) {
2346         color = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
2347       }
2348
2349       if (color != null) {
2350         drawBackground(g, color, softWrap.getIndentInPixels(), position, defaultBackground, clip);
2351       }
2352       position.x = softWrap.getIndentInPixels();
2353     }
2354
2355     // There is a possible case that caret is located at soft-wrapped line. We don't need to paint caret row background
2356     // on a last visual line of that soft-wrapped line then. Below is a holder for the flag that indicates if caret row
2357     // background is already drawn.
2358     boolean[] caretRowPainted = new boolean[1];
2359
2360     CharSequence text = myDocument.getImmutableCharSequence();
2361
2362     while (!iterationState.atEnd() && !lIterator.atEnd()) {
2363       int hEnd = iterationState.getEndOffset();
2364       int lEnd = lIterator.getEnd();
2365
2366       if (hEnd >= lEnd) {
2367         FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
2368         if (collapsedFolderAt == null) {
2369           position.x = drawSoftWrapAwareBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength(), position, fontType,
2370                                                    defaultBackground, clip, softWrapsToSkip, caretRowPainted);
2371
2372           paintAfterLineEndBackgroundSegments(g, iterationState, position, defaultBackground, lineHeight);
2373
2374           if (lIterator.getLineNumber() < lastLineIndex) {
2375             if (backColor != null && !backColor.equals(defaultBackground)) {
2376               g.setColor(backColor);
2377               g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
2378             }
2379           }
2380           else {
2381             if (iterationState.hasPastFileEndBackgroundSegments()) {
2382               paintAfterLineEndBackgroundSegments(g, iterationState, position, defaultBackground, lineHeight);
2383             }
2384             paintAfterFileEndBackground(iterationState,
2385                                         g,
2386                                         position, clip,
2387                                         lineHeight, defaultBackground, caretRowPainted);
2388             break;
2389           }
2390
2391           position.x = 0;
2392           if (position.y > clip.y + clip.height) break;
2393           position.y += lineHeight;
2394           start = lEnd;
2395         }
2396         else if (collapsedFolderAt.getEndOffset() == clipEndOffset) {
2397           softWrap = mySoftWrapModel.getSoftWrap(collapsedFolderAt.getStartOffset());
2398           if (softWrap != null) {
2399             position.x = drawSoftWrapAwareBackground(
2400               g, backColor, text, collapsedFolderAt.getStartOffset(), collapsedFolderAt.getStartOffset(), position, fontType,
2401               defaultBackground, clip, softWrapsToSkip, caretRowPainted
2402             );
2403           }
2404           CharSequence chars = collapsedFolderAt.getPlaceholderText();
2405           position.x = drawBackground(g, backColor, chars, 0, chars.length(), position, fontType, defaultBackground, clip);
2406         }
2407
2408         lIterator.advance();
2409       }
2410       else {
2411         FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
2412         if (collapsedFolderAt != null) {
2413           softWrap = mySoftWrapModel.getSoftWrap(collapsedFolderAt.getStartOffset());
2414           if (softWrap != null) {
2415             position.x = drawSoftWrapAwareBackground(
2416               g, backColor, text, collapsedFolderAt.getStartOffset(), collapsedFolderAt.getStartOffset(), position, fontType,
2417               defaultBackground, clip, softWrapsToSkip, caretRowPainted
2418             );
2419           }
2420           CharSequence chars = collapsedFolderAt.getPlaceholderText();
2421           position.x = drawBackground(g, backColor, chars, 0, chars.length(), position, fontType, defaultBackground, clip);
2422         }
2423         else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
2424           position.x = drawSoftWrapAwareBackground(
2425             g, backColor, text, start, lEnd - lIterator.getSeparatorLength(), position, fontType,
2426             defaultBackground, clip, softWrapsToSkip, caretRowPainted
2427           );
2428         }
2429         else {
2430           position.x = drawSoftWrapAwareBackground(
2431             g, backColor, text, start, hEnd, position, fontType, defaultBackground, clip, softWrapsToSkip, caretRowPainted
2432           );
2433         }
2434
2435         iterationState.advance();
2436         attributes = iterationState.getMergedAttributes();
2437         backColor = getBackgroundColor(attributes);
2438         fontType = attributes.getFontType();
2439         start = iterationState.getStartOffset();
2440       }
2441     }
2442
2443     flushBackground(g, clip);
2444
2445     if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
2446       paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground, caretRowPainted);
2447     }
2448
2449     // Perform additional activity if soft wrap is added or removed during repainting.
2450     if (mySoftWrapsChanged) {
2451       mySoftWrapsChanged = false;
2452       clearTextWidthCache();
2453       validateSize();
2454
2455       // Repaint editor to the bottom in order to ensure that its content is shown correctly after new soft wrap introduction.
2456       repaintToScreenBottom(EditorUtil.yPositionToLogicalLine(this, position));
2457
2458       // Repaint gutter at all space that is located after active clip in order to ensure that line numbers are correctly redrawn
2459       // in accordance with the newly introduced soft wrap(s).
2460       myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y);
2461     }
2462   }
2463
2464   private void paintAfterLineEndBackgroundSegments(@NotNull Graphics g,
2465                                                    @NotNull IterationState iterationState,
2466                                                    @NotNull Point position,
2467                                                    @NotNull Color defaultBackground,
2468                                                    int lineHeight) {
2469     while (iterationState.hasPastLineEndBackgroundSegment()) {
2470       TextAttributes backgroundAttributes = iterationState.getPastLineEndBackgroundAttributes();
2471       int width = EditorUtil.getSpaceWidth(backgroundAttributes.getFontType(), this) * iterationState.getPastLineEndBackgroundSegmentWidth();
2472       Color color = getBackgroundColor(backgroundAttributes);
2473       if (color != null && !color.equals(defaultBackground)) {
2474         g.setColor(color);
2475         g.fillRect(position.x, position.y, width, lineHeight);
2476       }
2477       position.x += width;
2478       iterationState.advanceToNextPastLineEndBackgroundSegment();
2479     }
2480   }
2481
2482   private void paintRectangularSelection(@NotNull Graphics g) {
2483     final SelectionModel model = getSelectionModel();
2484     if (!model.hasBlockSelection()) return;
2485     final LogicalPosition blockStart = model.getBlockStart();
2486     final LogicalPosition blockEnd = model.getBlockEnd();
2487     assert blockStart != null;
2488     assert blockEnd != null;
2489
2490     final Point start = logicalPositionToXY(blockStart);
2491     final Point end = logicalPositionToXY(blockEnd);
2492     g.setColor(myScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR));
2493     final int y;
2494     final int height;
2495     if (start.y <= end.y) {
2496       y = start.y;
2497       height = end.y - y + getLineHeight();
2498     }
2499     else {
2500       y = end.y;
2501       height = start.y - end.y + getLineHeight();
2502     }
2503     final int x = Math.min(start.x, end.x);
2504     final int width = Math.max(2, Math.abs(end.x - start.x));
2505     g.fillRect(x, y, width, height);
2506   }
2507
2508   private void paintAfterFileEndBackground(@NotNull IterationState iterationState,
2509                                            @NotNull Graphics g,
2510                                            @NotNull Point position,
2511                                            @NotNull Rectangle clip,
2512                                            int lineHeight,
2513                                            @NotNull Color defaultBackground,
2514                                            @NotNull boolean[] caretRowPainted) {
2515     Color backColor = iterationState.getPastFileEndBackground();
2516     if (backColor == null || backColor.equals(defaultBackground)) {
2517       return;
2518     }
2519     if (caretRowPainted[0] && backColor.equals(getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR))) {
2520       return;
2521     }
2522     g.setColor(backColor);
2523     g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
2524   }
2525
2526   private int drawSoftWrapAwareBackground(@NotNull Graphics g,
2527                                           Color backColor,
2528                                           @NotNull CharSequence text,
2529                                           int start,
2530                                           int end,
2531                                           @NotNull Point position,
2532                                           @JdkConstants.FontStyle int fontType,
2533                                           @NotNull Color defaultBackground,
2534                                           @NotNull Rectangle clip,
2535                                           @NotNull TIntHashSet softWrapsToSkip,
2536                                           @NotNull boolean[] caretRowPainted) {
2537     int startToUse = start;
2538     // Given 'end' offset is exclusive though SoftWrapModel.getSoftWrapsForRange() uses inclusive end offset.
2539     // Hence, we decrement it if necessary. Please note that we don't do that if start is equal to end. That is the case,
2540     // for example, for soft-wrapped collapsed fold region - we need to draw soft wrap before it.
2541     int softWrapRetrievalEndOffset = end;
2542     if (end > start) {
2543       softWrapRetrievalEndOffset--;
2544     }
2545     List<? extends SoftWrap> softWraps = getSoftWrapModel().getSoftWrapsForRange(start, softWrapRetrievalEndOffset);
2546     for (SoftWrap softWrap : softWraps) {
2547       int softWrapStart = softWrap.getStart();
2548       if (softWrapsToSkip.contains(softWrapStart)) {
2549         continue;
2550       }
2551       if (startToUse < softWrapStart) {
2552         position.x = drawBackground(g, backColor, text, startToUse, softWrapStart, position, fontType, defaultBackground, clip);
2553       }
2554       boolean drawCustomBackgroundAtSoftWrapVirtualSpace =
2555         !Comparing.equal(backColor, defaultBackground) && (softWrapStart > start || Comparing.equal(myLastBackgroundColor, backColor));
2556       drawSoftWrap(
2557         g, softWrap, position, fontType, backColor, drawCustomBackgroundAtSoftWrapVirtualSpace, defaultBackground, clip, caretRowPainted
2558       );
2559       startToUse = softWrapStart;
2560     }
2561
2562     if (startToUse < end) {
2563       position.x = drawBackground(g, backColor, text, startToUse, end, position, fontType, defaultBackground, clip);
2564     }
2565     return position.x;
2566   }
2567
2568   private void drawSoftWrap(@NotNull Graphics g,
2569                             @NotNull SoftWrap softWrap,
2570                             @NotNull Point position,
2571                             @JdkConstants.FontStyle int fontType,
2572                             @Nullable Color backColor,
2573                             boolean drawCustomBackgroundAtSoftWrapVirtualSpace,
2574                             @NotNull Color defaultBackground,
2575                             @NotNull Rectangle clip,
2576                             @NotNull boolean[] caretRowPainted) {
2577     // The main idea is to to do the following:
2578     //     *) update given drawing position coordinates in accordance with the current soft wrap;
2579     //     *) draw background at soft wrap-introduced virtual space if necessary;
2580
2581     CharSequence softWrapText = softWrap.getText();
2582     int activeRowY = getCaretModel().getVisualPosition().line * getLineHeight();
2583     int afterSoftWrapWidth = clip.x + clip.width - position.x;
2584     if (drawCustomBackgroundAtSoftWrapVirtualSpace && backColor != null) {
2585       drawBackground(g, backColor, afterSoftWrapWidth, position, defaultBackground, clip);
2586     }
2587     else if (position.y == activeRowY) {
2588       // Draw 'active line' background after soft wrap.
2589       Color caretRowColor = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
2590       drawBackground(g, caretRowColor, afterSoftWrapWidth, position, defaultBackground, clip);
2591       caretRowPainted[0] = true;
2592     }
2593
2594     paintSelectionOnFirstSoftWrapLineIfNecessary(g, position, clip, defaultBackground, fontType);
2595
2596     int i = CharArrayUtil.lastIndexOf(softWrapText, "\n", softWrapText.length()) + 1;
2597     int width = getTextSegmentWidth(softWrapText, i, softWrapText.length(), 0, fontType, clip)
2598                 + getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
2599     position.x = 0;
2600     position.y += getLineHeight();
2601
2602     if (drawCustomBackgroundAtSoftWrapVirtualSpace && backColor != null) {
2603       drawBackground(g, backColor, width, position, defaultBackground, clip);
2604     }
2605     else if (position.y == activeRowY) {
2606       // Draw 'active line' background for the soft wrap-introduced virtual space.
2607       Color caretRowColor = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
2608       drawBackground(g, caretRowColor, width, position, defaultBackground, clip);
2609     }
2610
2611     position.x = 0;
2612     paintSelectionOnSecondSoftWrapLineIfNecessary(g, position, clip, defaultBackground, fontType, softWrap);
2613     position.x = width;
2614   }
2615
2616
2617   private VisualPosition getSelectionStartPositionForPaint() {
2618     if (mySelectionStartPosition == null) {
2619       // We cache the value to avoid repeated invocations of Editor.logicalPositionToOffset which is currently slow for long lines
2620       mySelectionStartPosition = getSelectionModel().getSelectionStartPosition();
2621     }
2622     return mySelectionStartPosition;
2623   }
2624
2625   private VisualPosition getSelectionEndPositionForPaint() {
2626     if (mySelectionEndPosition == null) {
2627       // We cache the value to avoid repeated invocations of Editor.logicalPositionToOffset which is currently slow for long lines
2628       mySelectionEndPosition = getSelectionModel().getSelectionEndPosition();
2629     }
2630     return mySelectionEndPosition;
2631   }
2632
2633   /**
2634    * End user is allowed to perform selection by visual coordinates (e.g. by dragging mouse with left button hold). There is a possible
2635    * case that such a move intersects with soft wrap introduced virtual space. We want to draw corresponding selection background
2636    * there then.
2637    * <p/>
2638    * This method encapsulates functionality of drawing selection background on the first soft wrap line (e.g. on a visual line where
2639    * it is applied).
2640    *
2641    * @param g                 graphics to draw on
2642    * @param position          current position (assumed to be position of soft wrap appliance)
2643    * @param clip              target drawing area boundaries
2644    * @param defaultBackground default background
2645    * @param fontType          current font type
2646    */
2647   private void paintSelectionOnFirstSoftWrapLineIfNecessary(@NotNull Graphics g,
2648                                                             @NotNull Point position,
2649                                                             @NotNull Rectangle clip,
2650                                                             @NotNull Color defaultBackground,
2651                                                             @JdkConstants.FontStyle int fontType) {
2652     // There is a possible case that the user performed selection at soft wrap virtual space. We need to paint corresponding background
2653     // there then.
2654     VisualPosition selectionStartPosition = getSelectionStartPositionForPaint();
2655     VisualPosition selectionEndPosition = getSelectionEndPositionForPaint();
2656     if (selectionStartPosition.equals(selectionEndPosition)) {
2657       return;
2658     }
2659
2660     int currentVisualLine = position.y / getLineHeight();
2661     int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, currentVisualLine);
2662
2663     // Check if the first soft wrap line is within the visual selection.
2664     if (currentVisualLine < selectionStartPosition.line || currentVisualLine > selectionEndPosition.line
2665         || currentVisualLine == selectionEndPosition.line && selectionEndPosition.column <= lastColumn) {
2666       return;
2667     }
2668
2669     // Adjust 'x' if selection starts at soft wrap virtual space.
2670     final int columnsToSkip = selectionStartPosition.column - lastColumn;
2671     if (columnsToSkip > 0) {
2672       position.x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
2673       position.x += (columnsToSkip - 1) * EditorUtil.getSpaceWidth(Font.PLAIN, this);
2674     }
2675
2676     // Calculate selection width.
2677     final int width;
2678     if (selectionEndPosition.line > currentVisualLine) {
2679       width = clip.x + clip.width - position.x;
2680     }
2681     else if (selectionStartPosition.line < currentVisualLine || selectionStartPosition.column <= lastColumn) {
2682       width = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED)
2683               + (selectionEndPosition.column - lastColumn - 1) * EditorUtil.getSpaceWidth(fontType, this);
2684     }
2685     else {
2686       width = (selectionEndPosition.column - selectionStartPosition.column) * EditorUtil.getSpaceWidth(fontType, this);
2687     }
2688
2689     drawBackground(g, getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR), width, position, defaultBackground, clip);
2690   }
2691
2692   /**
2693    * End user is allowed to perform selection by visual coordinates (e.g. by dragging mouse with left button hold). There is a possible
2694    * case that such a move intersects with soft wrap introduced virtual space. We want to draw corresponding selection background
2695    * there then.
2696    * <p/>
2697    * This method encapsulates functionality of drawing selection background on the second soft wrap line (e.g. on a visual line after
2698    * the one where it is applied).
2699    *
2700    * @param g                 graphics to draw on
2701    * @param position          current position (assumed to be position of soft wrap appliance)
2702    * @param clip              target drawing area boundaries
2703    * @param defaultBackground default background
2704    * @param fontType          current font type
2705    * @param softWrap          target soft wrap which second line virtual space may contain selection
2706    */
2707   private void paintSelectionOnSecondSoftWrapLineIfNecessary(@NotNull Graphics g,
2708                                                              @NotNull Point position,
2709                                                              @NotNull Rectangle clip,
2710                                                              @NotNull Color defaultBackground,
2711                                                              @JdkConstants.FontStyle int fontType,
2712                                                              @NotNull SoftWrap softWrap) {
2713     // There is a possible case that the user performed selection at soft wrap virtual space. We need to paint corresponding background
2714     // there then.
2715     VisualPosition selectionStartPosition = getSelectionStartPositionForPaint();
2716     VisualPosition selectionEndPosition = getSelectionEndPositionForPaint();
2717     if (selectionStartPosition.equals(selectionEndPosition)) {
2718       return;
2719     }
2720
2721     int currentVisualLine = position.y / getLineHeight();
2722
2723     // Check if the second soft wrap line is within the visual selection.
2724     if (currentVisualLine < selectionStartPosition.line || currentVisualLine > selectionEndPosition.line
2725         || currentVisualLine == selectionStartPosition.line && selectionStartPosition.column >= softWrap.getIndentInColumns()) {
2726       return;
2727     }
2728
2729     // Adjust 'x' if selection starts at soft wrap virtual space.
2730     if (selectionStartPosition.line == currentVisualLine && selectionStartPosition.column > 0) {
2731       position.x += selectionStartPosition.column * EditorUtil.getSpaceWidth(fontType, this);
2732     }
2733
2734     // Calculate selection width.
2735     final int width;
2736     if (selectionEndPosition.line > currentVisualLine || selectionEndPosition.column >= softWrap.getIndentInColumns()) {
2737       width = softWrap.getIndentInPixels() - position.x;
2738     }
2739     else {
2740       width = selectionEndPosition.column * EditorUtil.getSpaceWidth(fontType, this) - position.x;
2741     }
2742
2743     drawBackground(g, getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR), width, position, defaultBackground, clip);
2744   }
2745
2746   private int drawBackground(@NotNull Graphics g,
2747                              Color backColor,
2748                              @NotNull CharSequence text,
2749                              int start,
2750                              int end,
2751                              @NotNull Point position,
2752                              @JdkConstants.FontStyle int fontType,
2753                              @NotNull Color defaultBackground,
2754                              @NotNull Rectangle clip) {
2755     int width = getTextSegmentWidth(text, start, end, position.x, fontType, clip);
2756     return drawBackground(g, backColor, width, position, defaultBackground, clip);
2757   }
2758
2759   private int drawBackground(@NotNull Graphics g,
2760                              @Nullable Color backColor,
2761                              int width,
2762                              @NotNull Point position,
2763                              @NotNull Color defaultBackground,
2764                              @NotNull Rectangle clip) {
2765     if (backColor != null && !backColor.equals(defaultBackground) && clip.intersects(position.x, position.y, width, getLineHeight())) {
2766       if (backColor.equals(myLastBackgroundColor) && myLastBackgroundPosition.y == position.y &&
2767           myLastBackgroundPosition.x + myLastBackgroundWidth == position.x) {
2768         myLastBackgroundWidth += width;
2769       }
2770       else {
2771         flushBackground(g, clip);
2772         myLastBackgroundColor = backColor;
2773         myLastBackgroundPosition = new Point(position);
2774         myLastBackgroundWidth = width;
2775       }
2776     }
2777
2778     return position.x + width;
2779   }
2780
2781   private void flushBackground(@NotNull Graphics g, @NotNull final Rectangle clip) {
2782     if (myLastBackgroundColor != null) {
2783       final Point position = myLastBackgroundPosition;
2784       final int w = myLastBackgroundWidth;
2785       final int height = getLineHeight();
2786       if (clip.intersects(position.x, position.y, w, height)) {
2787         g.setColor(myLastBackgroundColor);
2788         g.fillRect(position.x, position.y, w, height);
2789       }
2790       myLastBackgroundColor = null;
2791     }
2792   }
2793
2794   @NotNull
2795   private LineIterator createLineIterator() {
2796     return myDocument.createLineIterator();
2797   }
2798
2799   private void paintText(@NotNull Graphics g,
2800                          @NotNull Rectangle clip,
2801                          @NotNull LogicalPosition clipStartPosition,
2802                          int clipStartOffset,
2803                          int clipEndOffset) {
2804     myCurrentFontType = null;
2805     myLastCache = null;
2806
2807     int lineHeight = getLineHeight();
2808
2809     int visibleLine = clip.y / lineHeight;
2810
2811     int startLine = clipStartPosition.line;
2812     int start = clipStartOffset;
2813
2814     Point position = new Point(0, visibleLine * lineHeight);
2815     if (startLine == 0 && myPrefixText != null) {
2816       position.x = drawStringWithSoftWraps(g, new CharArrayCharSequence(myPrefixText), 0, myPrefixText.length, position, clip,
2817                                            myPrefixAttributes.getEffectColor(), myPrefixAttributes.getEffectType(),
2818                                            myPrefixAttributes.getFontType(), myPrefixAttributes.getForegroundColor(), -1,
2819                                            PAINT_NO_WHITESPACE);
2820     }
2821     if (startLine >= myDocument.getLineCount() || startLine < 0) {
2822       if (position.x > 0) flushCachedChars(g);
2823       return;
2824     }
2825
2826     LineIterator lIterator = createLineIterator();
2827     lIterator.start(start);
2828     if (lIterator.atEnd()) {
2829       return;
2830     }
2831
2832     IterationState iterationState = new IterationState(this, start, clipEndOffset, isPaintSelection());
2833     TextAttributes attributes = iterationState.getMergedAttributes();
2834     Color currentColor = attributes.getForegroundColor();
2835     if (currentColor == null) {