do lighter checks before calling isChainable that might load AST
[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 = new Disposable() {
1178       @Override
1179       public void dispose() {
1180         document.removeDocumentListener(highlighter);
1181       }
1182     };
1183     Disposer.register(myDisposable, myHighlighterDisposable);
1184     highlighter.setEditor(this);
1185     highlighter.setText(document.getImmutableCharSequence());
1186     EditorHighlighterCache.rememberEditorHighlighterForCachesOptimization(document, highlighter);
1187
1188     if (myPanel != null) {
1189       reinitSettings();
1190     }
1191   }
1192
1193   @NotNull
1194   @Override
1195   public EditorHighlighter getHighlighter() {
1196     assertReadAccess();
1197     return myHighlighter;
1198   }
1199
1200   @Override
1201   @NotNull
1202   public EditorComponentImpl getContentComponent() {
1203     return myEditorComponent;
1204   }
1205
1206   @NotNull
1207   @Override
1208   public EditorGutterComponentImpl getGutterComponentEx() {
1209     return myGutterComponent;
1210   }
1211
1212   @Override
1213   public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
1214     myPropertyChangeSupport.addPropertyChangeListener(listener);
1215   }
1216   @Override
1217   public void addPropertyChangeListener(@NotNull final PropertyChangeListener listener, @NotNull Disposable parentDisposable) {
1218     addPropertyChangeListener(listener);
1219     Disposer.register(parentDisposable, new Disposable() {
1220       @Override
1221       public void dispose() {
1222         removePropertyChangeListener(listener);
1223       }
1224     });
1225   }
1226
1227   @Override
1228   public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
1229     myPropertyChangeSupport.removePropertyChangeListener(listener);
1230   }
1231
1232   @Override
1233   public void setInsertMode(boolean mode) {
1234     assertIsDispatchThread();
1235     boolean oldValue = myIsInsertMode;
1236     myIsInsertMode = mode;
1237     myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, mode);
1238     myCaretCursor.repaint();
1239   }
1240
1241   @Override
1242   public boolean isInsertMode() {
1243     return myIsInsertMode;
1244   }
1245
1246   @Override
1247   public void setColumnMode(boolean mode) {
1248     assertIsDispatchThread();
1249     boolean oldValue = myIsColumnMode;
1250     myIsColumnMode = mode;
1251     myPropertyChangeSupport.firePropertyChange(PROP_COLUMN_MODE, oldValue, mode);
1252   }
1253
1254   @Override
1255   public boolean isColumnMode() {
1256     return myIsColumnMode;
1257   }
1258
1259   public int yToVisibleLine(int y) {
1260     if (myUseNewRendering) return myView.yToVisualLine(y);
1261     assert y >= 0 : y;
1262     return y / getLineHeight();
1263   }
1264
1265   @Override
1266   @NotNull
1267   public VisualPosition xyToVisualPosition(@NotNull Point p) {
1268     if (myUseNewRendering) return myView.xyToVisualPosition(p);
1269     int line = yToVisibleLine(Math.max(p.y, 0));
1270     int px = p.x;
1271     if (line == 0 && myPrefixText != null) {
1272       px -= myPrefixWidthInPixels;
1273     }
1274     if (px < 0) {
1275       px = 0;
1276     }
1277
1278     int textLength = myDocument.getTextLength();
1279     LogicalPosition logicalPosition = visualToLogicalPosition(new VisualPosition(line, 0));
1280     int offset = logicalPositionToOffset(logicalPosition);
1281     int plainSpaceSize = EditorUtil.getSpaceWidth(Font.PLAIN, this);
1282
1283     if (offset >= textLength) return new VisualPosition(line, EditorUtil.columnsNumber(px, plainSpaceSize));
1284
1285     // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn.
1286     // We mark that we're under such circumstances then.
1287     boolean activeSoftWrapProcessed = logicalPosition.softWrapLinesOnCurrentLogicalLine <= 0;
1288
1289     CharSequence text = myDocument.getImmutableCharSequence();
1290
1291     LogicalPosition endLogicalPosition = visualToLogicalPosition(new VisualPosition(line + 1, 0));
1292     int endOffset = logicalPositionToOffset(endLogicalPosition);
1293
1294     if (offset > endOffset) {
1295       LogMessageEx.error(LOG, "Detected invalid (x; y)->VisualPosition processing", String.format(
1296         "Given point: %s, mapped to visual line %d. Visual(%d; %d) is mapped to "
1297         + "logical position '%s' which is mapped to offset %d (start offset). Visual(%d; %d) is mapped to logical '%s' which is mapped "
1298         + "to offset %d (end offset). State: %s",
1299         p, line, line, 0, logicalPosition, offset, line + 1, 0, endLogicalPosition, endOffset, dumpState()
1300       ));
1301       return new VisualPosition(line, EditorUtil.columnsNumber(px, plainSpaceSize));
1302     }
1303     IterationState state = new IterationState(this, offset, endOffset, false);
1304
1305     int fontType = state.getMergedAttributes().getFontType();
1306
1307     int x = 0;
1308     int charWidth;
1309     boolean onSoftWrapDrawing = false;
1310     char c = ' ';
1311     int prevX = 0;
1312     int column = 0;
1313     outer:
1314     while (true) {
1315       charWidth = -1;
1316       if (offset >= textLength) {
1317         break;
1318       }
1319
1320       if (offset >= state.getEndOffset()) {
1321         state.advance();
1322         fontType = state.getMergedAttributes().getFontType();
1323       }
1324
1325       SoftWrap softWrap = mySoftWrapModel.getSoftWrap(offset);
1326       if (softWrap != null) {
1327         if (activeSoftWrapProcessed) {
1328           prevX = x;
1329           charWidth = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
1330           x += charWidth;
1331           if (x >= px) {
1332             onSoftWrapDrawing = true;
1333           }
1334           else {
1335             column++;
1336           }
1337           break;
1338         }
1339         else {
1340           CharSequence softWrapText = softWrap.getText();
1341           for (int i = 1/*Assuming line feed is located at the first position*/; i < softWrapText.length(); i++) {
1342             c = softWrapText.charAt(i);
1343             prevX = x;
1344             charWidth = charToVisibleWidth(c, fontType, x);
1345             x += charWidth;
1346             if (x >= px) {
1347               break outer;
1348             }
1349             column += EditorUtil.columnsNumber(c, x, prevX, plainSpaceSize);
1350           }
1351
1352           // Process 'after soft wrap' sign.
1353           prevX = x;
1354           charWidth = mySoftWrapModel.getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
1355           x += charWidth;
1356           if (x >= px) {
1357             onSoftWrapDrawing = true;
1358             break;
1359           }
1360           column++;
1361           activeSoftWrapProcessed = true;
1362         }
1363       }
1364       FoldRegion region = state.getCurrentFold();
1365       if (region != null) {
1366         char[] placeholder = region.getPlaceholderText().toCharArray();
1367         for (char aPlaceholder : placeholder) {
1368           c = aPlaceholder;
1369           x += EditorUtil.charWidth(c, fontType, this);
1370           if (x >= px) {
1371             break outer;
1372           }
1373           column++;
1374         }
1375         offset = region.getEndOffset();
1376       }
1377       else {
1378         prevX = x;
1379         c = text.charAt(offset);
1380         if (c == '\n') {
1381           break;
1382         }
1383         charWidth = charToVisibleWidth(c, fontType, x);
1384         x += charWidth;
1385
1386         if (x >= px) {
1387           break;
1388         }
1389         column += EditorUtil.columnsNumber(c, x, prevX, plainSpaceSize);
1390
1391         offset++;
1392       }
1393     }
1394
1395     if (charWidth < 0) {
1396       charWidth = EditorUtil.charWidth(c, fontType, this);
1397     }
1398
1399     if (charWidth < 0) {
1400       charWidth = plainSpaceSize;
1401     }
1402
1403     if (x >= px && c == '\t' && !onSoftWrapDrawing) {
1404       if (mySettings.isCaretInsideTabs()) {
1405         column += (px - prevX) / plainSpaceSize;
1406         if ((px - prevX) % plainSpaceSize > plainSpaceSize / 2) column++;
1407       }
1408       else if ((x - px) * 2 < x - prevX) {
1409         column += EditorUtil.columnsNumber(c, x, prevX, plainSpaceSize);
1410       }
1411     }
1412     else {
1413       if (x >= px) {
1414         if (c != '\n' && (x - px) * 2 < charWidth) column++;
1415       }
1416       else {
1417         int diff = px - x;
1418         column += diff / plainSpaceSize;
1419         if (diff % plainSpaceSize * 2 >= plainSpaceSize) {
1420           column++;
1421         }
1422       }
1423     }
1424
1425     return new VisualPosition(line, column);
1426   }
1427
1428   /**
1429    * Allows to answer how much width requires given char to be represented on a screen.
1430    *
1431    * @param c        target character
1432    * @param fontType font type to use for representation of the given character
1433    * @param currentX current <code>'x'</code> position on a line where given character should be displayed
1434    * @return width required to represent given char with the given settings on a screen;
1435    *         <code>'0'</code> if given char is a line break
1436    */
1437   private int charToVisibleWidth(char c, @JdkConstants.FontStyle int fontType, int currentX) {
1438     if (c == '\n') {
1439       return 0;
1440     }
1441
1442     if (c == '\t') {
1443       return EditorUtil.nextTabStop(currentX, this) - currentX;
1444     }
1445     return EditorUtil.charWidth(c, fontType, this);
1446   }
1447
1448   @NotNull
1449   public Point offsetToXY(int offset, boolean leanTowardsLargerOffsets) {
1450     return myUseNewRendering ? myView.offsetToXY(offset, leanTowardsLargerOffsets, false) : 
1451            visualPositionToXY(offsetToVisualPosition(offset, leanTowardsLargerOffsets, false));
1452   }
1453   
1454   @Override
1455   @NotNull
1456   public VisualPosition offsetToVisualPosition(int offset) {
1457     return offsetToVisualPosition(offset, false, false);
1458   }
1459
1460   @Override
1461   @NotNull
1462   public VisualPosition offsetToVisualPosition(int offset, boolean leanForward, boolean beforeSoftWrap) {
1463     if (myUseNewRendering) return myView.offsetToVisualPosition(offset, leanForward, beforeSoftWrap);
1464     return logicalToVisualPosition(offsetToLogicalPosition(offset));
1465   }
1466
1467   @Override
1468   @NotNull
1469   public LogicalPosition offsetToLogicalPosition(int offset) {
1470     return offsetToLogicalPosition(offset, true);
1471   }
1472
1473   @NotNull
1474   public LogicalPosition offsetToLogicalPosition(int offset, boolean softWrapAware) {
1475     if (myUseNewRendering) return myView.offsetToLogicalPosition(offset);
1476     if (softWrapAware) {
1477       return mySoftWrapModel.offsetToLogicalPosition(offset);
1478     }
1479     int line = offsetToLogicalLine(offset);
1480     int column = calcColumnNumber(offset, line, false, myDocument.getImmutableCharSequence());
1481     return new LogicalPosition(line, column);
1482   }
1483
1484   @TestOnly
1485   public void setCaretActive() {
1486     synchronized (ourCaretBlinkingCommand) {
1487       ourCaretBlinkingCommand.myEditor = this;
1488     }
1489   }
1490
1491   // optimization: do not do column calculations here since we are interested in line number only
1492   public int offsetToVisualLine(int offset) {
1493     if (myUseNewRendering) return myView.offsetToVisualLine(offset, false);
1494     int textLength = getDocument().getTextLength();
1495     if (offset >= textLength) {
1496       return Math.max(0, getVisibleLineCount() - 1); // lines are 0 based
1497     }
1498     int line = offsetToLogicalLine(offset);
1499     int lineStartOffset = line >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(line);
1500
1501     int result = logicalToVisualLine(line);
1502
1503     // There is a possible case that logical line that contains target offset is soft-wrapped (represented in more than one visual
1504     // line). Hence, we need to perform necessary adjustments to the visual line that is used to show logical line start if necessary.
1505     int i = getSoftWrapModel().getSoftWrapIndex(lineStartOffset);
1506     if (i < 0) {
1507       i = -i - 1;
1508     }
1509     List<? extends SoftWrap> softWraps = getSoftWrapModel().getRegisteredSoftWraps();
1510     for (; i < softWraps.size(); i++) {
1511       SoftWrap softWrap = softWraps.get(i);
1512       if (softWrap.getStart() > offset) {
1513         break;
1514       }
1515       result++; // Assuming that every soft wrap contains only one virtual line feed symbol
1516     }
1517     return result;
1518   }
1519   
1520   public int visualLineStartOffset(int visualLine) {
1521     if (myUseNewRendering) return myView.visualLineToOffset(visualLine);
1522     throw new UnsupportedOperationException();
1523   }
1524
1525   private int logicalToVisualLine(int line) {
1526     assertReadAccess();
1527     return logicalToVisualPosition(new LogicalPosition(line, 0)).line;
1528   }
1529
1530   @Override
1531   @NotNull
1532   public LogicalPosition xyToLogicalPosition(@NotNull Point p) {
1533     Point pp = p.x >= 0 && p.y >= 0 ? p : new Point(Math.max(p.x, 0), Math.max(p.y, 0));
1534     return visualToLogicalPosition(xyToVisualPosition(pp));
1535   }
1536
1537   private int logicalLineToY(int line) {
1538     int visualLine = myUseNewRendering && line < myDocument.getLineCount() ? offsetToVisualLine(myDocument.getLineStartOffset(line)) : 
1539                      logicalToVisualPosition(new LogicalPosition(line, 0)).line;
1540     return visibleLineToY(visualLine);
1541   }
1542
1543   @Override
1544   @NotNull
1545   public Point logicalPositionToXY(@NotNull LogicalPosition pos) {
1546     VisualPosition visible = logicalToVisualPosition(pos);
1547     return visualPositionToXY(visible);
1548   }
1549
1550   @Override
1551   @NotNull
1552   public Point visualPositionToXY(@NotNull VisualPosition visible) {
1553     if (myUseNewRendering) return myView.visualPositionToXY(visible); 
1554     int y = visibleLineToY(visible.line);
1555     LogicalPosition logical = visualToLogicalPosition(new VisualPosition(visible.line, 0));
1556     int logLine = logical.line;
1557
1558     int lineStartOffset = -1;
1559     int reserved = 0;
1560     int column = visible.column;
1561
1562     if (logical.softWrapLinesOnCurrentLogicalLine > 0) {
1563       int linesToSkip = logical.softWrapLinesOnCurrentLogicalLine;
1564       List<? extends SoftWrap> softWraps = getSoftWrapModel().getSoftWrapsForLine(logLine);
1565       for (SoftWrap softWrap : softWraps) {
1566         if (myFoldingModel.isOffsetCollapsed(softWrap.getStart()) && myFoldingModel.isOffsetCollapsed(softWrap.getStart() - 1)) {
1567           continue;
1568         }
1569         linesToSkip--; // Assuming here that every soft wrap has exactly one line feed
1570         if (linesToSkip > 0) {
1571           continue;
1572         }
1573         lineStartOffset = softWrap.getStart();
1574         int widthInColumns = softWrap.getIndentInColumns();
1575         int widthInPixels = softWrap.getIndentInPixels();
1576         if (widthInColumns <= column) {
1577           column -= widthInColumns;
1578           reserved = widthInPixels;
1579         }
1580         else {
1581           char[] softWrapChars = softWrap.getChars();
1582           int i = CharArrayUtil.lastIndexOf(softWrapChars, '\n', 0, softWrapChars.length);
1583           int start = 0;
1584           if (i >= 0) {
1585             start = i + 1;
1586           }
1587           return new Point(EditorUtil.textWidth(this, softWrap.getText(), start, column + 1, Font.PLAIN, 0), y);
1588         }
1589         break;
1590       }
1591     }
1592
1593     if (logLine < 0) {
1594       lineStartOffset = 0;
1595     }
1596     else if (lineStartOffset < 0) {
1597       lineStartOffset = logLine >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(logLine);
1598     }
1599
1600     int x = getTabbedTextWidth(lineStartOffset, column, reserved);
1601     return new Point(x, y);
1602   }
1603
1604   /**
1605    * Returns how much current line height bigger than the normal (16px)
1606    * This method is used to scale editors elements such as gutter icons, folding elements, and others
1607    */
1608   public float getScale() {
1609     if (!Registry.is("editor.scale.gutter.icons")) return 1f;
1610     float normLineHeight = getLineHeight() / myScheme.getLineSpacing(); // normalized, as for 1.0f line spacing
1611     return normLineHeight / JBUI.scale(16f);
1612   }
1613
1614   private int calcEndOffset(int startOffset, int visualColumn) {
1615     FoldRegion[] regions = myFoldingModel.fetchTopLevel();
1616     if (regions == null) {
1617       return startOffset + visualColumn;
1618     }
1619
1620     int low = 0;
1621     int high = regions.length - 1;
1622     int i = -1;
1623
1624     while (low <= high) {
1625       int mid = low + high >>> 1;
1626       FoldRegion midVal = regions[mid];
1627
1628       if (midVal.getStartOffset() <= startOffset && midVal.getEndOffset() > startOffset) {
1629         i = mid;
1630         break;
1631       }
1632
1633       if (midVal.getStartOffset() < startOffset)
1634         low = mid + 1;
1635       else if (midVal.getStartOffset() > startOffset)
1636         high = mid - 1;
1637     }
1638     if (i < 0) {
1639       i = low;
1640     }
1641
1642     int result = startOffset;
1643     int columnsToProcess = visualColumn;
1644     for (; i < regions.length; i++) {
1645       FoldRegion region = regions[i];
1646
1647       // Process text between the last fold region end and current fold region start.
1648       int nonFoldTextColumnsNumber = region.getStartOffset() - result;
1649       if (nonFoldTextColumnsNumber >= columnsToProcess) {
1650         return result + columnsToProcess;
1651       }
1652       columnsToProcess -= nonFoldTextColumnsNumber;
1653
1654       // Process fold region.
1655       int placeHolderLength = region.getPlaceholderText().length();
1656       if (placeHolderLength >= columnsToProcess) {
1657         return region.getEndOffset();
1658       }
1659       result = region.getEndOffset();
1660       columnsToProcess -= placeHolderLength;
1661     }
1662     return result + columnsToProcess;
1663   }
1664
1665   public int findNearestDirectionBoundary(int offset, boolean lookForward) {
1666     return myUseNewRendering ? myView.findNearestDirectionBoundary(offset, lookForward) : -1;
1667   }
1668
1669   // TODO: tabbed text width is additive, it should be possible to have buckets, containing arguments / values to start with
1670   private final int[] myLastStartOffsets = new int[2];
1671   private final int[] myLastTargetColumns = new int[myLastStartOffsets.length];
1672   private final int[] myLastXOffsets = new int[myLastStartOffsets.length];
1673   private final int[] myLastXs = new int[myLastStartOffsets.length];
1674   private int myCurrentCachePosition;
1675   private int myLastCacheHits;
1676   private int myTotalRequests; // todo remove
1677
1678   private int getTabbedTextWidth(int startOffset, int targetColumn, int xOffset) {
1679     int x = xOffset;
1680     if (startOffset == 0 && myPrefixText != null) {
1681       x += myPrefixWidthInPixels;
1682     }
1683     if (targetColumn <= 0) return x;
1684
1685     ++myTotalRequests;
1686     for(int i = 0; i < myLastStartOffsets.length; ++i) {
1687       if (startOffset == myLastStartOffsets[i] && targetColumn == myLastTargetColumns[i] && xOffset == myLastXOffsets[i]) {
1688         ++myLastCacheHits;
1689         if ((myLastCacheHits & 0xFFF) == 0) {    // todo remove
1690           PsiFile file = myProject != null ? PsiDocumentManager.getInstance(myProject).getCachedPsiFile(myDocument):null;
1691           LOG.info("Cache hits:" + myLastCacheHits + ", total requests:" +
1692                              myTotalRequests + "," + (file != null ? file.getViewProvider().getVirtualFile():null));
1693         }
1694         return myLastXs[i];
1695       }
1696     }
1697
1698     int offset = startOffset;
1699     CharSequence text = myDocument.getImmutableCharSequence();
1700     int textLength = myDocument.getTextLength();
1701
1702     // We need to calculate max offset to provide to the IterationState here based on the given start offset and target
1703     // visual column. The problem is there is a possible case that there is a collapsed fold region at the target interval,
1704     // so, we can't just use 'startOffset + targetColumn' as a max end offset.
1705     IterationState state = new IterationState(this, startOffset, calcEndOffset(startOffset, targetColumn), false);
1706     int fontType = state.getMergedAttributes().getFontType();
1707     int plainSpaceSize = EditorUtil.getSpaceWidth(Font.PLAIN, this);
1708
1709     int column = 0;
1710     outer:
1711     while (column < targetColumn) {
1712       if (offset >= textLength) break;
1713
1714       if (offset >= state.getEndOffset()) {
1715         state.advance();
1716         fontType = state.getMergedAttributes().getFontType();
1717       }
1718       // We need to consider 'before soft wrap drawing'.
1719       SoftWrap softWrap = getSoftWrapModel().getSoftWrap(offset);
1720       if (softWrap != null && offset > startOffset) {
1721         column++;
1722         x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
1723         // Assuming that first soft wrap symbol is line feed or all soft wrap symbols before the first line feed are spaces.
1724         break;
1725       }
1726
1727       FoldRegion region = state.getCurrentFold();
1728
1729       if (region != null) {
1730         char[] placeholder = region.getPlaceholderText().toCharArray();
1731         for (char aPlaceholder : placeholder) {
1732           x += EditorUtil.charWidth(aPlaceholder, fontType, this);
1733           column++;
1734           if (column >= targetColumn) break outer;
1735         }
1736         offset = region.getEndOffset();
1737       }
1738       else {
1739         char c = text.charAt(offset);
1740         if (c == '\n') {
1741           break;
1742         }
1743         if (c == '\t') {
1744           int prevX = x;
1745           x = EditorUtil.nextTabStop(x, this);
1746           int columnDiff = (x - prevX) / plainSpaceSize;
1747           if ((x - prevX) % plainSpaceSize > 0) {
1748             // There is a possible case that tabulation symbol takes more than one visual column to represent and it's shown at
1749             // soft-wrapped line. Soft wrap sign width may be not divisible by space size, hence, part of tabulation symbol represented
1750             // as a separate visual column may take less space than space width.
1751             columnDiff++;
1752           }
1753           column += columnDiff;
1754         }
1755         else {
1756           x += EditorUtil.charWidth(c, fontType, this);
1757           column++;
1758         }
1759         offset++;
1760       }
1761     }
1762
1763     if (column != targetColumn) {
1764       x += EditorUtil.getSpaceWidth(fontType, this) * (targetColumn - column);
1765     }
1766
1767     myLastTargetColumns[myCurrentCachePosition] = targetColumn;
1768     myLastStartOffsets[myCurrentCachePosition] = startOffset;
1769     myLastXs[myCurrentCachePosition] = x;
1770     myLastXOffsets[myCurrentCachePosition] = xOffset;
1771     myCurrentCachePosition = (myCurrentCachePosition + 1) % myLastStartOffsets.length;
1772
1773     return x;
1774   }
1775
1776   private void clearTextWidthCache() {
1777     for(int i = 0; i < myLastStartOffsets.length; ++i) {
1778       myLastTargetColumns[i] = -1;
1779       myLastStartOffsets[i] = - 1;
1780       myLastXs[i] = -1;
1781       myLastXOffsets[i] = -1;
1782     }
1783   }
1784
1785   public int visibleLineToY(int line) {
1786     if (myUseNewRendering) return myView.visualLineToY(line);
1787     if (line < 0) throw new IndexOutOfBoundsException("Wrong line: " + line);
1788     return line * getLineHeight();
1789   }
1790
1791   @Override
1792   public void repaint(int startOffset, int endOffset) {
1793     repaint(startOffset, endOffset, true);
1794   }
1795   
1796   void repaint(int startOffset, int endOffset, boolean invalidateTextLayout) {
1797     if (myDocument.isInBulkUpdate()) {
1798       return;
1799     }
1800     if (myUseNewRendering) {
1801       assertIsDispatchThread();
1802       endOffset = Math.min(endOffset, myDocument.getTextLength());
1803
1804       if (invalidateTextLayout) {
1805         myView.invalidateRange(startOffset, endOffset);
1806       }
1807
1808       if (!isShowing()) {
1809         return;
1810       }
1811     }
1812     else {
1813       if (!isShowing()) {
1814         return;
1815       }
1816
1817       endOffset = Math.min(endOffset, myDocument.getTextLength());
1818       assertIsDispatchThread();
1819     }
1820     // 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
1821     // it does occupy particular amount of visual space that may be necessary to repaint.
1822     if (startOffset <= endOffset) {
1823       int startLine = myDocument.getLineNumber(startOffset);
1824       int endLine = myDocument.getLineNumber(endOffset);
1825       repaintLines(startLine, endLine);
1826     }
1827   }
1828
1829   private boolean isShowing() {
1830     return myGutterComponent.isShowing();
1831   }
1832
1833   private void repaintToScreenBottom(int startLine) {
1834     Rectangle visibleArea = getScrollingModel().getVisibleArea();
1835     int yStartLine = logicalLineToY(startLine);
1836     int yEndLine = visibleArea.y + visibleArea.height;
1837
1838     myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, yEndLine - yStartLine);
1839     myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
1840     ((EditorMarkupModelImpl)getMarkupModel()).repaint(-1, -1);
1841   }
1842
1843   /**
1844    * Asks to repaint all logical lines from the given <code>[start; end]</code> range.
1845    *
1846    * @param startLine start logical line to repaint (inclusive)
1847    * @param endLine   end logical line to repaint (inclusive)
1848    */
1849   private void repaintLines(int startLine, int endLine) {
1850     if (!isShowing()) return;
1851
1852     Rectangle visibleArea = getScrollingModel().getVisibleArea();
1853     int yStartLine = logicalLineToY(startLine);
1854     int endVisLine = myDocument.getTextLength() <= 0
1855                      ? 0
1856                      : offsetToVisualLine(myDocument.getLineEndOffset(Math.min(myDocument.getLineCount() - 1, endLine)));
1857     int height = endVisLine * getLineHeight() - yStartLine + getLineHeight() + 2;
1858
1859     myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, height);
1860     myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), height);
1861   }
1862
1863   private void bulkUpdateStarted() {
1864     if (myUseNewRendering) {
1865       myView.getPreferredSize(); // make sure size is calculated (in case it will be required while bulk mode is active)
1866     }
1867
1868     myScrollingModel.onBulkDocumentUpdateStarted();
1869     
1870     saveCaretRelativePosition();
1871
1872     myCaretModel.onBulkDocumentUpdateStarted();
1873     mySoftWrapModel.onBulkDocumentUpdateStarted();
1874     myFoldingModel.onBulkDocumentUpdateStarted();
1875   }
1876
1877   private void bulkUpdateFinished() {
1878     myFoldingModel.onBulkDocumentUpdateFinished();
1879     mySoftWrapModel.onBulkDocumentUpdateFinished();
1880     if (myUseNewRendering) {
1881       myView.reset();
1882     }
1883     myCaretModel.onBulkDocumentUpdateFinished();
1884
1885     clearTextWidthCache();
1886
1887     setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
1888
1889     if (!myUseNewRendering) {
1890       mySizeContainer.reset();
1891     }
1892     validateSize();
1893
1894     updateGutterSize();
1895     repaintToScreenBottom(0);
1896     updateCaretCursor();
1897
1898     if (!Boolean.TRUE.equals(getUserData(DISABLE_CARET_POSITION_KEEPING))) {
1899       restoreCaretRelativePosition();
1900     }
1901   }
1902
1903   private void beforeChangedUpdate(@NotNull DocumentEvent e) {
1904     ApplicationManager.getApplication().assertIsDispatchThread();
1905     
1906     myDocumentChangeInProgress = true;
1907     if (isStickySelection()) {
1908       setStickySelection(false);
1909     }
1910     if (myDocument.isInBulkUpdate()) {
1911       // Assuming that the job is done at bulk listener callback methods.
1912       return;
1913     }
1914
1915     saveCaretRelativePosition();
1916
1917     // We assume that size container is already notified with the visual line widths during soft wraps processing
1918     if (!mySoftWrapModel.isSoftWrappingEnabled() && !myUseNewRendering) {
1919       mySizeContainer.beforeChange(e);
1920     }
1921
1922     myImmediatePainter.beforeUpdate(e);
1923   }
1924
1925   private void changedUpdate(DocumentEvent e) {
1926     myDocumentChangeInProgress = false;
1927     if (myDocument.isInBulkUpdate()) return;
1928
1929     myImmediatePainter.paintUpdate(myEditorComponent.getGraphics(), e);
1930
1931     if (myErrorStripeNeedsRepaint) {
1932       myMarkupModel.repaint(e.getOffset(), e.getOffset() + e.getNewLength());
1933       myErrorStripeNeedsRepaint = false;
1934     }
1935
1936     clearTextWidthCache();
1937     setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
1938
1939     // We assume that size container is already notified with the visual line widths during soft wraps processing
1940     if (!mySoftWrapModel.isSoftWrappingEnabled() && !myUseNewRendering) {
1941       mySizeContainer.changedUpdate(e);
1942     }
1943     validateSize();
1944
1945     int startLine = offsetToLogicalLine(e.getOffset());
1946     int endLine = offsetToLogicalLine(e.getOffset() + e.getNewLength());
1947
1948     boolean painted = false;
1949     if (myDocument.getTextLength() > 0) {
1950       if (startLine != endLine || StringUtil.indexOf(e.getOldFragment(), '\n') != -1) {
1951         myGutterComponent.clearLineToGutterRenderersCache();
1952       }
1953
1954       if (countLineFeeds(e.getOldFragment()) != countLineFeeds(e.getNewFragment())) {
1955         // Lines removed. Need to repaint till the end of the screen
1956         repaintToScreenBottom(startLine);
1957         painted = true;
1958       }
1959     }
1960
1961     updateCaretCursor();
1962     if (!painted) {
1963       repaintLines(startLine, endLine);
1964     }
1965
1966     if (!Boolean.TRUE.equals(getUserData(DISABLE_CARET_POSITION_KEEPING)) &&
1967         (getCaretModel().getOffset() < e.getOffset() || getCaretModel().getOffset() > e.getOffset() + e.getNewLength())) {
1968       restoreCaretRelativePosition();
1969     }
1970   }
1971
1972   private void saveCaretRelativePosition() {
1973     Rectangle visibleArea = getScrollingModel().getVisibleArea();
1974     Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
1975     myCaretUpdateVShift = pos.y - visibleArea.y;
1976   }
1977
1978   private void restoreCaretRelativePosition() {
1979     Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
1980     int scrollOffset = caretLocation.y - myCaretUpdateVShift;
1981     getScrollingModel().disableAnimation();
1982     getScrollingModel().scrollVertically(scrollOffset);
1983     getScrollingModel().enableAnimation();
1984   }
1985
1986   public boolean hasTabs() {
1987     return myUseNewRendering || !(myDocument instanceof DocumentImpl) || ((DocumentImpl)myDocument).mightContainTabs();
1988   }
1989
1990   public boolean isScrollToCaret() {
1991     return myScrollToCaret;
1992   }
1993
1994   public void setScrollToCaret(boolean scrollToCaret) {
1995     myScrollToCaret = scrollToCaret;
1996   }
1997
1998   @NotNull
1999   public Disposable getDisposable() {
2000     return myDisposable;
2001   }
2002
2003   private static int countLineFeeds(@NotNull CharSequence c) {
2004     return StringUtil.countNewLines(c);
2005   }
2006
2007   private boolean updatingSize; // accessed from EDT only
2008   private void updateGutterSize() {
2009     assertIsDispatchThread();
2010     if (!updatingSize) {
2011       updatingSize = true;
2012       SwingUtilities.invokeLater(new Runnable() {
2013         @Override
2014         public void run() {
2015           try {
2016             if (!isDisposed()) {
2017               myGutterComponent.updateSize();
2018             }
2019           }
2020           finally {
2021             updatingSize = false;
2022           }
2023         }
2024       });
2025     }
2026   }
2027
2028   void validateSize() {
2029     if (myUseNewRendering && isReleased) return;
2030     
2031     Dimension dim = getPreferredSize();
2032
2033     if (!dim.equals(myPreferredSize) && !myDocument.isInBulkUpdate()) {
2034       dim = mySizeAdjustmentStrategy.adjust(dim, myPreferredSize, this);
2035       myPreferredSize = dim;
2036
2037       updateGutterSize();
2038
2039       myEditorComponent.setSize(dim);
2040       myEditorComponent.fireResized();
2041
2042       myMarkupModel.recalcEditorDimensions();
2043       myMarkupModel.repaint(-1, -1);
2044     }
2045   }
2046
2047   void recalculateSizeAndRepaint() {
2048     if (!myUseNewRendering) mySizeContainer.reset();
2049     validateSize();
2050     myEditorComponent.repaintEditorComponent();
2051   }
2052
2053   @Override
2054   @NotNull
2055   public DocumentEx getDocument() {
2056     return myDocument;
2057   }
2058
2059   @Override
2060   @NotNull
2061   public JComponent getComponent() {
2062     return myPanel;
2063   }
2064
2065   @Override
2066   public void addEditorMouseListener(@NotNull EditorMouseListener listener) {
2067     myMouseListeners.add(listener);
2068   }
2069
2070   @Override
2071   public void removeEditorMouseListener(@NotNull EditorMouseListener listener) {
2072     boolean success = myMouseListeners.remove(listener);
2073     LOG.assertTrue(success || isReleased);
2074   }
2075
2076   @Override
2077   public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
2078     myMouseMotionListeners.add(listener);
2079   }
2080
2081   @Override
2082   public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
2083     boolean success = myMouseMotionListeners.remove(listener);
2084     LOG.assertTrue(success || isReleased);
2085   }
2086
2087   @Override
2088   public boolean isStickySelection() {
2089     return myStickySelection;
2090   }
2091
2092   @Override
2093   public void setStickySelection(boolean enable) {
2094     myStickySelection = enable;
2095     if (enable) {
2096       myStickySelectionStart = getCaretModel().getOffset();
2097     }
2098     else {
2099       mySelectionModel.removeSelection();
2100     }
2101   }
2102
2103   @Override
2104   public boolean isDisposed() {
2105     return isReleased;
2106   }
2107
2108   public void stopDumbLater() {
2109     if (ApplicationManager.getApplication().isUnitTestMode()) return;
2110     final Runnable stopDumbRunnable = new Runnable() {
2111       @Override
2112       public void run() {
2113         stopDumb();
2114       }
2115     };
2116     ApplicationManager.getApplication().invokeLater(stopDumbRunnable, ModalityState.current());
2117   }
2118
2119   void resetPaintersWidth() {
2120     myLinePaintersWidth = 0;
2121   }
2122
2123   private void stopDumb() {
2124     putUserData(BUFFER, null);
2125   }
2126
2127   /**
2128    * {@link #stopDumbLater} or {@link #stopDumb} must be performed in finally
2129    */
2130   public void startDumb() {
2131     if (ApplicationManager.getApplication().isUnitTestMode()) return;
2132     Rectangle rect = ((JViewport)myEditorComponent.getParent()).getViewRect();
2133     BufferedImage image = UIUtil.createImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
2134     Graphics2D graphics = image.createGraphics();
2135     graphics.translate(-rect.x, -rect.y);
2136     graphics.setClip(rect.x, rect.y, rect.width, rect.height);
2137     myEditorComponent.paintComponent(graphics);
2138     graphics.dispose();
2139     putUserData(BUFFER, image);
2140   }
2141
2142   void paint(@NotNull Graphics2D g) {
2143     Rectangle clip = g.getClipBounds();
2144
2145     if (clip == null) {
2146       return;
2147     }
2148
2149     if (Registry.is("editor.dumb.mode.available")) {
2150       final BufferedImage buffer = getUserData(BUFFER);
2151       if (buffer != null) {
2152         final Rectangle rect = getContentComponent().getVisibleRect();
2153         UIUtil.drawImage(g, buffer, null, rect.x, rect.y);
2154         return;
2155       }
2156     }
2157
2158     if (isReleased) {
2159       g.setColor(getDisposedBackground());
2160       g.fillRect(clip.x, clip.y, clip.width, clip.height);
2161       return;
2162     }
2163     if (myUpdateCursor) {
2164       setCursorPosition();
2165       myUpdateCursor = false;
2166     }
2167     if (myProject != null && myProject.isDisposed()) return;
2168
2169     if (myUseNewRendering) {
2170       myView.paint(g);
2171     }
2172     else {
2173       VisualPosition clipStartVisualPos = xyToVisualPosition(new Point(0, clip.y));
2174       LogicalPosition clipStartPosition = visualToLogicalPosition(clipStartVisualPos);
2175       int clipStartOffset = logicalPositionToOffset(clipStartPosition);
2176       LogicalPosition clipEndPosition = xyToLogicalPosition(new Point(0, clip.y + clip.height + getLineHeight()));
2177       int clipEndOffset = logicalPositionToOffset(clipEndPosition);
2178       paintBackgrounds(g, clip, clipStartPosition, clipStartVisualPos, clipStartOffset, clipEndOffset);
2179       if (paintPlaceholderText(g, clip)) {
2180         paintCaretCursor(g);
2181         return;
2182       }
2183
2184       paintRightMargin(g, clip);
2185       paintCustomRenderers(g, clipStartOffset, clipEndOffset);
2186       paintLineMarkersSeparators(g, clip, myDocumentMarkupModel, clipStartOffset, clipEndOffset);
2187       paintLineMarkersSeparators(g, clip, myMarkupModel, clipStartOffset, clipEndOffset);
2188       paintText(g, clip, clipStartPosition, clipStartOffset, clipEndOffset);
2189       paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip, clipStartOffset, clipEndOffset, myDocumentMarkupModel);
2190       BorderEffect borderEffect = new BorderEffect(this, g, clipStartOffset, clipEndOffset);
2191       borderEffect.paintHighlighters(getHighlighter());
2192       borderEffect.paintHighlighters(myDocumentMarkupModel);
2193       borderEffect.paintHighlighters(myMarkupModel);
2194
2195       paintCaretCursor(g);
2196
2197       paintComposedTextDecoration(g);
2198     }
2199   }
2200
2201   Color getDisposedBackground() {
2202     return new JBColor(new Color(128, 255, 128), new Color(128, 255, 128));
2203   }
2204
2205   private static final char IDEOGRAPHIC_SPACE = '\u3000'; // http://www.marathon-studios.com/unicode/U3000/Ideographic_Space
2206   private static final String WHITESPACE_CHARS = " \t" + IDEOGRAPHIC_SPACE;
2207
2208   private void paintCustomRenderers(@NotNull final Graphics2D g, final int clipStartOffset, final int clipEndOffset) {
2209     myMarkupModel.processRangeHighlightersOverlappingWith(clipStartOffset, clipEndOffset, new Processor<RangeHighlighterEx>() {
2210       @Override
2211       public boolean process(@NotNull RangeHighlighterEx highlighter) {
2212         final CustomHighlighterRenderer customRenderer = highlighter.getCustomRenderer();
2213         if (customRenderer != null && clipStartOffset < highlighter.getEndOffset() && highlighter.getStartOffset() < clipEndOffset) {
2214           customRenderer.paint(EditorImpl.this, highlighter, g);
2215         }
2216         return true;
2217       }
2218     });
2219   }
2220
2221   @NotNull
2222   @Override
2223   public IndentsModel getIndentsModel() {
2224     return myIndentsModel;
2225   }
2226
2227   @Override
2228   public void setHeaderComponent(JComponent header) {
2229     myHeaderPanel.removeAll();
2230     header = header == null ? getPermanentHeaderComponent() : header;
2231     if (header != null) {
2232       myHeaderPanel.add(header);
2233     }
2234
2235     myHeaderPanel.revalidate();
2236   }
2237
2238   @Override
2239   public boolean hasHeaderComponent() {
2240     JComponent header = getHeaderComponent();
2241     return header != null && header != getPermanentHeaderComponent();
2242   }
2243
2244   @Override
2245   @Nullable
2246   public JComponent getPermanentHeaderComponent() {
2247     return getUserData(PERMANENT_HEADER);
2248   }
2249
2250   @Override
2251   public void setPermanentHeaderComponent(@Nullable JComponent component) {
2252     putUserData(PERMANENT_HEADER, component);
2253   }
2254
2255   @Override
2256   @Nullable
2257   public JComponent getHeaderComponent() {
2258     if (myHeaderPanel.getComponentCount() > 0) {
2259       return (JComponent)myHeaderPanel.getComponent(0);
2260     }
2261     return null;
2262   }
2263
2264   @Override
2265   public void setBackgroundColor(Color color) {
2266     myScrollPane.setBackground(color);
2267
2268     if (getBackgroundIgnoreForced().equals(color)) {
2269       myForcedBackground = null;
2270       return;
2271     }
2272     myForcedBackground = color;
2273   }
2274
2275   @NotNull
2276   Color getForegroundColor() {
2277     return myScheme.getDefaultForeground();
2278   }
2279
2280   @NotNull
2281   @Override
2282   public Color getBackgroundColor() {
2283     if (myForcedBackground != null) return myForcedBackground;
2284
2285     return getBackgroundIgnoreForced();
2286   }
2287
2288   @NotNull
2289   @Override
2290   public TextDrawingCallback getTextDrawingCallback() {
2291     return myTextDrawingCallback;
2292   }
2293
2294   @Override
2295   public void setPlaceholder(@Nullable CharSequence text) {
2296     myPlaceholderText = text;
2297   }
2298
2299   @Override
2300   public void setPlaceholderAttributes(@Nullable TextAttributes attributes) {
2301     myPlaceholderAttributes = attributes;
2302   }
2303
2304   public CharSequence getPlaceholder() {
2305     return myPlaceholderText;
2306   }
2307   
2308   @Override
2309   public void setShowPlaceholderWhenFocused(boolean show) {
2310     myShowPlaceholderWhenFocused = show;
2311   }
2312
2313   public boolean getShowPlaceholderWhenFocused() {
2314     return myShowPlaceholderWhenFocused;
2315   }
2316
2317   Color getBackgroundColor(@NotNull final TextAttributes attributes) {
2318     final Color attrColor = attributes.getBackgroundColor();
2319     return Comparing.equal(attrColor, myScheme.getDefaultBackground()) ? getBackgroundColor() : attrColor;
2320   }
2321
2322   @NotNull
2323   private Color getBackgroundIgnoreForced() {
2324     Color color = myScheme.getDefaultBackground();
2325     if (myDocument.isWritable()) {
2326       return color;
2327     }
2328     Color readOnlyColor = myScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR);
2329     return readOnlyColor != null ? readOnlyColor : color;
2330   }
2331
2332   private void paintComposedTextDecoration(@NotNull Graphics2D g) {
2333     TextRange composedTextRange = getComposedTextRange();
2334     if (composedTextRange != null) {
2335       VisualPosition visStart = offsetToVisualPosition(Math.min(composedTextRange.getStartOffset(), myDocument.getTextLength()));
2336       int y = visibleLineToY(visStart.line) + getAscent() + 1;
2337       Point p1 = visualPositionToXY(visStart);
2338       Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(composedTextRange.getEndOffset(), myDocument.getTextLength())));
2339
2340       Stroke saved = g.getStroke();
2341       BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2, 0, 2}, 0);
2342       g.setStroke(dotted);
2343       UIUtil.drawLine(g, p1.x, y, p2.x, y);
2344       g.setStroke(saved);
2345     }
2346   }
2347
2348   @Nullable
2349   public TextRange getComposedTextRange() {
2350     return myInputMethodRequestsHandler == null || myInputMethodRequestsHandler.composedText == null ?
2351            null : myInputMethodRequestsHandler.composedTextRange;
2352   }
2353
2354   private void paintRightMargin(@NotNull Graphics g, @NotNull Rectangle clip) {
2355     Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
2356     if (!mySettings.isRightMarginShown() || rightMargin == null) {
2357       return;
2358     }
2359     int x = mySettings.getRightMargin(myProject) * EditorUtil.getSpaceWidth(Font.PLAIN, this);
2360     if (x >= clip.x && x < clip.x + clip.width) {
2361       g.setColor(rightMargin);
2362       UIUtil.drawLine(g, x, clip.y, x, clip.y + clip.height);
2363     }
2364   }
2365
2366   private void paintSegmentHighlightersBorderAndAfterEndOfLine(@NotNull final Graphics g,
2367                                                                @NotNull Rectangle clip,
2368                                                                int clipStartOffset,
2369                                                                int clipEndOffset,
2370                                                                @NotNull MarkupModelEx docMarkup) {
2371     if (myDocument.getLineCount() == 0) return;
2372     final int startLine = yToVisibleLine(clip.y);
2373     final int endLine = yToVisibleLine(clip.y + clip.height) + 1;
2374
2375     Processor<RangeHighlighterEx> paintProcessor = new Processor<RangeHighlighterEx>() {
2376       @Override
2377       public boolean process(@NotNull RangeHighlighterEx highlighter) {
2378         paintSegmentHighlighterAfterEndOfLine(g, highlighter, startLine, endLine);
2379         return true;
2380       }
2381     };
2382     docMarkup.processRangeHighlightersOverlappingWith(clipStartOffset, clipEndOffset, paintProcessor);
2383     myMarkupModel.processRangeHighlightersOverlappingWith(clipStartOffset, clipEndOffset, paintProcessor);
2384   }
2385
2386   private void paintSegmentHighlighterAfterEndOfLine(@NotNull Graphics g,
2387                                                      @NotNull RangeHighlighterEx segmentHighlighter,
2388                                                      int startLine,
2389                                                      int endLine) {
2390     if (!segmentHighlighter.isAfterEndOfLine()) {
2391       return;
2392     }
2393     int startOffset = segmentHighlighter.getStartOffset();
2394     int visibleStartLine = offsetToVisualLine(startOffset);
2395
2396     if (getFoldingModel().isOffsetCollapsed(startOffset)) {
2397       return;
2398     }
2399     if (visibleStartLine >= startLine && visibleStartLine <= endLine) {
2400       int logStartLine = offsetToLogicalLine(startOffset);
2401       if (logStartLine >= myDocument.getLineCount()) {
2402         return;
2403       }
2404       LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
2405       Point end = logicalPositionToXY(logPosition);
2406       int charWidth = EditorUtil.getSpaceWidth(Font.PLAIN, this);
2407       int lineHeight = getLineHeight();
2408       TextAttributes attributes = segmentHighlighter.getTextAttributes();
2409       if (attributes != null && getBackgroundColor(attributes) != null) {
2410         g.setColor(getBackgroundColor(attributes));
2411         g.fillRect(end.x, end.y, charWidth, lineHeight);
2412       }
2413       if (attributes != null && attributes.getEffectColor() != null) {
2414         int y = visibleLineToY(visibleStartLine) + getAscent() + 1;
2415         g.setColor(attributes.getEffectColor());
2416         if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
2417           UIUtil.drawWave((Graphics2D)g, new Rectangle(end.x, y, charWidth - 1, getDescent()- 1));
2418         }
2419         else if (attributes.getEffectType() == EffectType.BOLD_DOTTED_LINE) {
2420           final int dottedAt = SystemInfo.isMac ? y - 1 : y;
2421           UIUtil.drawBoldDottedLine((Graphics2D)g, end.x, end.x + charWidth - 1, dottedAt,
2422                                     getBackgroundColor(attributes), attributes.getEffectColor(), false);
2423         }
2424         else if (attributes.getEffectType() == EffectType.STRIKEOUT) {
2425           int y1 = y - getCharHeight() / 2 - 1;
2426           UIUtil.drawLine(g, end.x, y1, end.x + charWidth - 1, y1);
2427         }
2428         else if (attributes.getEffectType() == EffectType.BOLD_LINE_UNDERSCORE) {
2429           drawBoldLineUnderScore(g, end.x, y - 1, charWidth - 1);
2430         }
2431         else if (attributes.getEffectType() != EffectType.BOXED) {
2432           UIUtil.drawLine(g, end.x, y, end.x + charWidth - 1, y);
2433         }
2434       }
2435     }
2436   }
2437
2438   private static void drawBoldLineUnderScore(Graphics g, int x, int y, int width) {
2439     int height = JBUI.scale(Registry.intValue("editor.bold.underline.height", 2));
2440     g.fillRect(x, y, width, height);
2441   }
2442
2443   @Override
2444   public int getMaxWidthInRange(int startOffset, int endOffset) {
2445     if (myUseNewRendering) return myView.getMaxWidthInRange(startOffset, endOffset);
2446     int start = offsetToVisualLine(startOffset);
2447     int end = offsetToVisualLine(endOffset);
2448
2449     return getMaxWidthInVisualLineRange(start, end, true);
2450   }
2451
2452   int getMaxWidthInVisualLineRange(int startVisualLine, int endVisualLine, boolean addOneColumn) {
2453     int width = 0;
2454
2455     for (int i = startVisualLine; i <= endVisualLine; i++) {
2456       int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + (addOneColumn ? 1 : 0);
2457       int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
2458
2459       if (lineWidth > width) {
2460         width = lineWidth;
2461       }
2462     }
2463
2464     return width;
2465   }
2466
2467   private void paintBackgrounds(@NotNull Graphics g,
2468                                 @NotNull Rectangle clip,
2469                                 @NotNull LogicalPosition clipStartPosition,
2470                                 @NotNull VisualPosition clipStartVisualPos,
2471                                 int clipStartOffset, int clipEndOffset) {
2472     Color defaultBackground = getBackgroundColor();
2473     if (myEditorComponent.isOpaque()) {
2474       g.setColor(defaultBackground);
2475       g.fillRect(clip.x, clip.y, clip.width, clip.height);
2476     }
2477     Color prevBackColor = null;
2478
2479     int lineHeight = getLineHeight();
2480
2481     int visibleLine = yToVisibleLine(clip.y);
2482
2483     Point position = new Point(0, visibleLine * lineHeight);
2484     CharSequence prefixText = myPrefixText == null ? null : new CharArrayCharSequence(myPrefixText);
2485     if (clipStartVisualPos.line == 0 && prefixText != null) {
2486       Color backColor = myPrefixAttributes.getBackgroundColor();
2487       position.x = drawBackground(g, backColor, prefixText, 0, prefixText.length(), position,
2488                                   myPrefixAttributes.getFontType(),
2489                                   defaultBackground, clip);
2490       prevBackColor = backColor;
2491     }
2492
2493     if (clipStartPosition.line >= myDocument.getLineCount() || clipStartPosition.line < 0) {
2494       if (position.x > 0) flushBackground(g, clip);
2495       return;
2496     }
2497
2498     myLastBackgroundPosition = null;
2499     myLastBackgroundColor = null;
2500     mySelectionStartPosition = null;
2501     mySelectionEndPosition = null;
2502
2503     int start = clipStartOffset;
2504
2505     if (!myPurePaintingMode) {
2506       getSoftWrapModel().registerSoftWrapsIfNecessary();
2507     }
2508
2509     LineIterator lIterator = createLineIterator();
2510     lIterator.start(start);
2511     if (lIterator.atEnd()) {
2512       return;
2513     }
2514
2515     IterationState iterationState = new IterationState(this, start, clipEndOffset, isPaintSelection());
2516     TextAttributes attributes = iterationState.getMergedAttributes();
2517     Color backColor = getBackgroundColor(attributes);
2518     int fontType = attributes.getFontType();
2519     int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
2520
2521     // There is a possible case that we need to draw background from the start of soft wrap-introduced visual line. Given position
2522     // has valid 'y' coordinate then at it shouldn't be affected by soft wrap that corresponds to the visual line start offset.
2523     // Hence, we store information about soft wrap to be skipped for further processing and adjust 'x' coordinate value if necessary.
2524     TIntHashSet softWrapsToSkip = new TIntHashSet();
2525     SoftWrap softWrap = getSoftWrapModel().getSoftWrap(start);
2526     if (softWrap != null) {
2527       softWrapsToSkip.add(softWrap.getStart());
2528       Color color = null;
2529       if (backColor != null && !backColor.equals(defaultBackground)) {
2530         color = backColor;
2531       }
2532
2533       // There is a possible case that target clip points to soft wrap-introduced visual line and that it's an active
2534       // line (caret cursor is located on it). We want to draw corresponding 'caret line' background for soft wraps-introduced
2535       // virtual space then.
2536       if (color == null && position.y == getCaretModel().getVisualPosition().line * getLineHeight()) {
2537         color = mySettings.isCaretRowShown() ? getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR) : null;
2538       }
2539
2540       if (color != null) {
2541         drawBackground(g, color, softWrap.getIndentInPixels(), position, defaultBackground, clip);
2542         prevBackColor = color;
2543       }
2544       position.x = softWrap.getIndentInPixels();
2545     }
2546
2547     // There is a possible case that caret is located at soft-wrapped line. We don't need to paint caret row background
2548     // on a last visual line of that soft-wrapped line then. Below is a holder for the flag that indicates if caret row
2549     // background is already drawn.
2550     boolean[] caretRowPainted = new boolean[1];
2551
2552     CharSequence text = myDocument.getImmutableCharSequence();
2553
2554     while (!iterationState.atEnd() && !lIterator.atEnd()) {
2555       int hEnd = iterationState.getEndOffset();
2556       int lEnd = lIterator.getEnd();
2557
2558       if (hEnd >= lEnd) {
2559         FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
2560         if (collapsedFolderAt == null) {
2561           position.x = drawSoftWrapAwareBackground(g, backColor, prevBackColor, text, start, lEnd - lIterator.getSeparatorLength(), 
2562                                                    position, fontType, defaultBackground, clip, softWrapsToSkip, caretRowPainted);
2563           prevBackColor = backColor;
2564
2565           paintAfterLineEndBackgroundSegments(g, iterationState, position, defaultBackground, lineHeight);
2566
2567           if (lIterator.getLineNumber() < lastLineIndex) {
2568             if (backColor != null && !backColor.equals(defaultBackground)) {
2569               g.setColor(backColor);
2570               g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
2571             }
2572           }
2573           else {
2574             if (iterationState.hasPastFileEndBackgroundSegments()) {
2575               paintAfterLineEndBackgroundSegments(g, iterationState, position, defaultBackground, lineHeight);
2576             }
2577             paintAfterFileEndBackground(iterationState,
2578                                         g,
2579                                         position, clip,
2580                                         lineHeight, defaultBackground, caretRowPainted);
2581             break;
2582           }
2583
2584           position.x = 0;
2585           if (position.y > clip.y + clip.height) break;
2586           position.y += lineHeight;
2587           start = lEnd;
2588         }
2589         else if (collapsedFolderAt.getEndOffset() == clipEndOffset) {
2590           drawCollapsedFolderBackground(g, clip, defaultBackground, prevBackColor, position, backColor, fontType, softWrapsToSkip, caretRowPainted, text,
2591                                         collapsedFolderAt);
2592           prevBackColor = backColor;
2593         }
2594
2595         lIterator.advance();
2596       }
2597       else {
2598         FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
2599         if (collapsedFolderAt != null) {
2600           drawCollapsedFolderBackground(g, clip, defaultBackground, prevBackColor, position, backColor, fontType, softWrapsToSkip, caretRowPainted, text,
2601                                         collapsedFolderAt);
2602           prevBackColor = backColor;
2603         }
2604         else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
2605           position.x = drawSoftWrapAwareBackground(
2606             g, backColor, prevBackColor, text, start, lEnd - lIterator.getSeparatorLength(), position, fontType,
2607             defaultBackground, clip, softWrapsToSkip, caretRowPainted
2608           );
2609           prevBackColor = backColor;
2610         }
2611         else {
2612           position.x = drawSoftWrapAwareBackground(
2613             g, backColor, prevBackColor, text, start, hEnd, position, fontType, defaultBackground, clip, softWrapsToSkip, caretRowPainted
2614           );
2615           prevBackColor = backColor;
2616         }
2617
2618         iterationState.advance();
2619         attributes = iterationState.getMergedAttributes();
2620         backColor = getBackgroundColor(attributes);
2621         fontType = attributes.getFontType();
2622         start = iterationState.getStartOffset();
2623       }
2624     }
2625
2626     flushBackground(g, clip);
2627
2628     if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
2629       paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground, caretRowPainted);
2630     }
2631
2632     // Perform additional activity if soft wrap is added or removed during repainting.
2633     if (mySoftWrapsChanged) {
2634       mySoftWrapsChanged = false;
2635       clearTextWidthCache();
2636       validateSize();
2637
2638       // Repaint editor to the bottom in order to ensure that its content is shown correctly after new soft wrap introduction.
2639       repaintToScreenBottom(EditorUtil.yPositionToLogicalLine(this, position));
2640
2641       // Repaint gutter at all space that is located after active clip in order to ensure that line numbers are correctly redrawn
2642       // in accordance with the newly introduced soft wrap(s).
2643       myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y);
2644     }
2645   }
2646
2647   private void drawCollapsedFolderBackground(@NotNull Graphics g,
2648                                              @NotNull Rectangle clip,
2649                                              @NotNull Color defaultBackground,
2650                                              @Nullable Color prevBackColor,
2651                                              @NotNull Point position,
2652                                              @NotNull Color backColor,
2653                                              int fontType,
2654                                              @NotNull TIntHashSet softWrapsToSkip,
2655                                              @NotNull boolean[] caretRowPainted,
2656                                              @NotNull CharSequence text,
2657                                              @NotNull FoldRegion collapsedFolderAt) {
2658     SoftWrap softWrap = mySoftWrapModel.getSoftWrap(collapsedFolderAt.getStartOffset());
2659     if (softWrap != null) {
2660       position.x = drawSoftWrapAwareBackground(
2661         g, backColor, prevBackColor, text, collapsedFolderAt.getStartOffset(), collapsedFolderAt.getStartOffset(), position, fontType,
2662         defaultBackground, clip, softWrapsToSkip, caretRowPainted
2663       );
2664     }
2665     CharSequence chars = collapsedFolderAt.getPlaceholderText();
2666     position.x = drawBackground(g, backColor, chars, 0, chars.length(), position, fontType, defaultBackground, clip);
2667   }
2668
2669   private void paintAfterLineEndBackgroundSegments(@NotNull Graphics g,
2670                                                    @NotNull IterationState iterationState,
2671                                                    @NotNull Point position,
2672                                                    @NotNull Color defaultBackground,
2673                                                    int lineHeight) {
2674     while (iterationState.hasPastLineEndBackgroundSegment()) {
2675       TextAttributes backgroundAttributes = iterationState.getPastLineEndBackgroundAttributes();
2676       int width = EditorUtil.getSpaceWidth(backgroundAttributes.getFontType(), this) * iterationState.getPastLineEndBackgroundSegmentWidth();
2677       Color color = getBackgroundColor(backgroundAttributes);
2678       if (color != null && !color.equals(defaultBackground)) {
2679         g.setColor(color);
2680         g.fillRect(position.x, position.y, width, lineHeight);
2681       }
2682       position.x += width;
2683       iterationState.advanceToNextPastLineEndBackgroundSegment();
2684     }
2685   }
2686
2687   private void paintAfterFileEndBackground(@NotNull IterationState iterationState,
2688                                            @NotNull Graphics g,
2689                                            @NotNull Point position,
2690                                            @NotNull Rectangle clip,
2691                                            int lineHeight,
2692                                            @NotNull Color defaultBackground,
2693                                            @NotNull boolean[] caretRowPainted) {
2694     Color backColor = iterationState.getPastFileEndBackground();
2695     if (backColor == null || backColor.equals(defaultBackground)) {
2696       return;
2697     }
2698     if (caretRowPainted[0] && backColor.equals(getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR))) {
2699       return;
2700     }
2701     g.setColor(backColor);
2702     g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
2703   }
2704
2705   private int drawSoftWrapAwareBackground(@NotNull Graphics g,
2706                                           @Nullable Color backColor,
2707                                           @Nullable Color prevBackColor,
2708                                           @NotNull CharSequence text,
2709                                           int start,
2710                                           int end,
2711                                           @NotNull Point position,
2712                                           @JdkConstants.FontStyle int fontType,
2713                                           @NotNull Color defaultBackground,
2714                                           @NotNull Rectangle clip,
2715                                           @NotNull TIntHashSet softWrapsToSkip,
2716                                           @NotNull boolean[] caretRowPainted) {
2717     int startToUse = start;
2718     // Given 'end' offset is exclusive though SoftWrapModel.getSoftWrapsForRange() uses inclusive end offset.
2719     // Hence, we decrement it if necessary. Please note that we don't do that if start is equal to end. That is the case,
2720     // for example, for soft-wrapped collapsed fold region - we need to draw soft wrap before it.
2721     int softWrapRetrievalEndOffset = end;
2722     if (end > start) {
2723       softWrapRetrievalEndOffset--;
2724     }
2725     List<? extends SoftWrap> softWraps = getSoftWrapModel().getSoftWrapsForRange(start, softWrapRetrievalEndOffset);
2726     for (SoftWrap softWrap : softWraps) {
2727       int softWrapStart = softWrap.getStart();
2728       if (softWrapsToSkip.contains(softWrapStart)) {
2729         continue;
2730       }
2731       if (startToUse < softWrapStart) {
2732         position.x = drawBackground(g, backColor, text, startToUse, softWrapStart, position, fontType, defaultBackground, clip);
2733       }
2734       boolean drawCustomBackgroundAtSoftWrapVirtualSpace =
2735         !Comparing.equal(backColor, defaultBackground) && (softWrapStart > start || Comparing.equal(prevBackColor, backColor));
2736       drawSoftWrap(
2737         g, softWrap, position, fontType, backColor, drawCustomBackgroundAtSoftWrapVirtualSpace, defaultBackground, clip, caretRowPainted
2738       );
2739       startToUse = softWrapStart;
2740     }
2741
2742     if (startToUse < end) {
2743       position.x = drawBackground(g, backColor, text, startToUse, end, position, fontType, defaultBackground, clip);
2744     }
2745     return position.x;
2746   }
2747
2748   private void drawSoftWrap(@NotNull Graphics g,
2749                             @NotNull SoftWrap softWrap,
2750                             @NotNull Point position,
2751                             @JdkConstants.FontStyle int fontType,
2752                             @Nullable Color backColor,
2753                             boolean drawCustomBackgroundAtSoftWrapVirtualSpace,
2754                             @NotNull Color defaultBackground,
2755                             @NotNull Rectangle clip,
2756                             @NotNull boolean[] caretRowPainted) {
2757     // The main idea is to to do the following:
2758     //     *) update given drawing position coordinates in accordance with the current soft wrap;
2759     //     *) draw background at soft wrap-introduced virtual space if necessary;
2760
2761     CharSequence softWrapText = softWrap.getText();
2762     int activeRowY = getCaretModel().getVisualPosition().line * getLineHeight();
2763     int afterSoftWrapWidth = clip.x + clip.width - position.x;
2764     if (drawCustomBackgroundAtSoftWrapVirtualSpace && backColor != null) {
2765       drawBackground(g, backColor, afterSoftWrapWidth, position, defaultBackground, clip);
2766     }
2767     else if (position.y == activeRowY) {
2768       // Draw 'active line' background after soft wrap.
2769       Color caretRowColor = mySettings.isCaretRowShown()? getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR) : null;
2770       drawBackground(g, caretRowColor, afterSoftWrapWidth, position, defaultBackground, clip);
2771       caretRowPainted[0] = true;
2772     }
2773
2774     paintSelectionOnFirstSoftWrapLineIfNecessary(g, position, clip, defaultBackground, fontType);
2775
2776     int i = CharArrayUtil.lastIndexOf(softWrapText, "\n", softWrapText.length()) + 1;
2777     int width = getTextSegmentWidth(softWrapText, i, softWrapText.length(), 0, fontType, clip)
2778                 + getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
2779     position.x = 0;
2780     position.y += getLineHeight();
2781
2782     if (drawCustomBackgroundAtSoftWrapVirtualSpace && backColor != null) {
2783       drawBackground(g, backColor, width, position, defaultBackground, clip);
2784     }
2785     else if (position.y == activeRowY) {
2786       // Draw 'active line' background for the soft wrap-introduced virtual space.
2787       Color caretRowColor = mySettings.isCaretRowShown()? getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR) : null;
2788       drawBackground(g, caretRowColor, width, position, defaultBackground, clip);
2789     }
2790
2791     position.x = 0;
2792     paintSelectionOnSecondSoftWrapLineIfNecessary(g, position, clip, defaultBackground, fontType, softWrap);
2793     position.x = width;
2794   }
2795
2796
2797   private VisualPosition getSelectionStartPositionForPaint() {
2798     if (mySelectionStartPosition == null) {
2799       // We cache the value to avoid repeated invocations of Editor.logicalPositionToOffset which is currently slow for long lines
2800       mySelectionStartPosition = getSelectionModel().getSelectionStartPosition();
2801     }
2802     return mySelectionStartPosition;
2803   }
2804
2805   private VisualPosition getSelectionEndPositionForPaint() {
2806     if (mySelectionEndPosition == null) {
2807       // We cache the value to avoid repeated invocations of Editor.logicalPositionToOffset which is currently slow for long lines
2808       mySelectionEndPosition = getSelectionModel().getSelectionEndPosition();
2809     }
2810     return mySelectionEndPosition;
2811   }
2812
2813   /**
2814    * End user is allowed to perform selection by visual coordinates (e.g. by dragging mouse with left button hold). There is a possible
2815    * case that such a move intersects with soft wrap introduced virtual space. We want to draw corresponding selection background
2816    * there then.
2817    * <p/>
2818    * This method encapsulates functionality of drawing selection background on the first soft wrap line (e.g. on a visual line where
2819    * it is applied).
2820    *
2821    * @param g                 graphics to draw on
2822    * @param position          current position (assumed to be position of soft wrap appliance)
2823    * @param clip              target drawing area boundaries
2824    * @param defaultBackground default background
2825    * @param fontType          current font type
2826    */
2827   private void paintSelectionOnFirstSoftWrapLineIfNecessary(@NotNull Graphics g,
2828                                                             @NotNull Point position,
2829                                                             @NotNull Rectangle clip,
2830                                                             @NotNull Color defaultBackground,
2831                                                             @JdkConstants.FontStyle int fontType) {
2832     // There is a possible case that the user performed selection at soft wrap virtual space. We need to paint corresponding background
2833     // there then.
2834     VisualPosition selectionStartPosition = getSelectionStartPositionForPaint();
2835     VisualPosition selectionEndPosition = getSelectionEndPositionForPaint();
2836     if (selectionStartPosition.equals(selectionEndPosition)) {
2837       return;
2838     }
2839
2840     int currentVisualLine = position.y / getLineHeight();
2841     int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, currentVisualLine);
2842
2843     // Check if the first soft wrap line is within the visual selection.
2844     if (currentVisualLine < selectionStartPosition.line || currentVisualLine > selectionEndPosition.line
2845         || currentVisualLine == selectionEndPosition.line && selectionEndPosition.column <= lastColumn) {
2846       return;
2847     }
2848
2849     // Adjust 'x' if selection starts at soft wrap virtual space.
2850     final int columnsToSkip = selectionStartPosition.column - lastColumn;