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