/*
 * Copyright 2000-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.openapi.editor.impl;

import com.intellij.application.options.OptionsConstants;
import com.intellij.codeInsight.hint.DocumentFragmentTooltipRenderer;
import com.intellij.codeInsight.hint.EditorFragmentComponent;
import com.intellij.codeInsight.hint.TooltipController;
import com.intellij.codeInsight.hint.TooltipGroup;
import com.intellij.concurrency.JobScheduler;
import com.intellij.diagnostic.Dumpable;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.ide.dnd.DnDManager;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.actionSystem.impl.MouseGestureManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.util.concurrency.ThreadingAssertions;
import com.intellij.util.concurrency.ThreadingAssertions;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.impl.DelegateColorScheme;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.HighlighterClient;
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType;
import com.intellij.openapi.editor.impl.view.EditorView;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeGlassPane;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
import com.intellij.openapi.wm.impl.IdeBackgroundUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.ui.components.JBLayeredPane;
import com.intellij.ui.components.JBScrollBar;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.text.CharArrayCharSequence;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.ui.update.Activatable;
import com.intellij.util.ui.update.UiNotifyConnector;
import gnu.trove.TIntArrayList;
import gnu.trove.TIntFunction;
import gnu.trove.TIntHashSet;
import gnu.trove.TIntIntHashMap;
import org.intellij.lang.annotations.JdkConstants;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.ScrollBarUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.font.TextHitInfo;
import java.awt.im.InputMethodRequests;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public final class XXXXX extends UserDataHolderBase implements EditorEx, HighlighterClient, Queryable, Dumpable {
  private static final boolean isOracleRetina = UIUtil.isRetina() && SystemInfo.isOracleJvm;
  private static final int MIN_FONT_SIZE = 8;
  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
  private static final Key DND_COMMAND_KEY = Key.create("DndCommand");
  @NonNls public static final Object IGNORE_MOUSE_TRACKING = "ignore_mouse_tracking";
  public static final Key<JComponent> PERMANENT_HEADER = Key.create("PERMANENT_HEADER");
  public static final Key<Boolean> DO_DOCUMENT_UPDATE_TEST = Key.create("DoDocumentUpdateTest");
  public static final Key<Boolean> FORCED_SOFT_WRAPS = Key.create("forced.soft.wraps");
  private static final boolean HONOR_CAMEL_HUMPS_ON_TRIPLE_CLICK =
    Boolean.parseBoolean(System.getProperty("idea.honor.camel.humps.on.triple.click"));
  private static final Key<BufferedImage> BUFFER = Key.create("buffer");
  public static final Color CURSOR_FOREGROUND_LIGHT = Gray._255;
  public static final Color CURSOR_FOREGROUND_DARK = Gray._0;
  @NotNull private final DocumentEx myDocument;

  private final JPanel myPanel;
  @NotNull private final JScrollPane myScrollPane;
  @NotNull private final EditorComponentImpl myEditorComponent;
  @NotNull private final EditorGutterComponentImpl myGutterComponent;
  private final TraceableDisposable myTraceableDisposable = new TraceableDisposable(new Throwable());
  private int myLinePaintersWidth = 0;

  static {
    ComplementaryFontsRegistry.getFontAbleToDisplay(' ', 0, 0, UIManager.getFont("Label.font").getFamily()); // load costly font info
  }

  private final CommandProcessor myCommandProcessor;
  @NotNull private final MyScrollBar myVerticalScrollBar;

  private final List<EditorMouseListener> myMouseListeners = ContainerUtil.createLockFreeCopyOnWriteList();
  @NotNull private final List<EditorMouseMotionListener> myMouseMotionListeners = ContainerUtil.createLockFreeCopyOnWriteList();

  private int myCharHeight = -1;
  private int myLineHeight = -1;
  private int myDescent = -1;

  private boolean myIsInsertMode = true;

  @NotNull private final CaretCursor myCaretCursor;
  private final ScrollingTimer myScrollingTimer = new ScrollingTimer();

  @SuppressWarnings("RedundantStringConstructorCall")
  private final Object MOUSE_DRAGGED_GROUP = new String("MouseDraggedGroup");

  @NotNull private final SettingsImpl mySettings;

  private boolean isReleased = false;

  @Nullable private MouseEvent myMousePressedEvent = null;
  @Nullable private MouseEvent myMouseMovedEvent = null;

  /**
   * Holds information about area where mouse was pressed.
   */
  @Nullable private EditorMouseEventArea myMousePressArea;
  private int mySavedSelectionStart = -1;
  private int mySavedSelectionEnd = -1;
  private int myLastColumnNumber = 0;

  private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
  private MyEditable myEditable;

  @NotNull
  private EditorColorsScheme myScheme;
  private ArrowPainter myTabPainter;
  private boolean myIsViewer;
  @NotNull private final SelectionModelImpl mySelectionModel;
  @NotNull private final EditorMarkupModelImpl myMarkupModel;
  @NotNull private final FoldingModelImpl myFoldingModel;
  @NotNull private final ScrollingModelImpl myScrollingModel;
  @NotNull private final CaretModelImpl myCaretModel;
  @NotNull private final SoftWrapModelImpl mySoftWrapModel;

  @NotNull private static final RepaintCursorCommand ourCaretBlinkingCommand = new RepaintCursorCommand();
  private MessageBusConnection myConnection;

  private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
  @Nullable private FoldRegion myMouseSelectedRegion = null;

  private static final int MOUSE_SELECTION_STATE_NONE = 0;
  private static final int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
  private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;

  private EditorHighlighter myHighlighter;
  private Disposable myHighlighterDisposable = Disposer.newDisposable();
  private final TextDrawingCallback myTextDrawingCallback = new MyTextDrawingCallback();

  @MagicConstant(intValues = {VERTICAL_SCROLLBAR_LEFT, VERTICAL_SCROLLBAR_RIGHT})
  private int myScrollBarOrientation;
  private boolean myMousePressedInsideSelection;
  private FontMetrics myPlainFontMetrics;
  private FontMetrics myBoldFontMetrics;
  private FontMetrics myItalicFontMetrics;
  private FontMetrics myBoldItalicFontMetrics;

  private static final int CACHED_CHARS_BUFFER_SIZE = 300;

  private final ArrayList<CachedFontContent> myFontCache = new ArrayList<CachedFontContent>();
  @Nullable private FontInfo myCurrentFontType = null;

  private final EditorSizeContainer mySizeContainer = new EditorSizeContainer();

  private boolean myUpdateCursor;
  private int myCaretUpdateVShift;

  @Nullable
  private final Project myProject;
  private long myMouseSelectionChangeTimestamp;
  private int mySavedCaretOffsetForDNDUndoHack;
  private final List<FocusChangeListener> myFocusListeners = ContainerUtil.createLockFreeCopyOnWriteList();

  private MyInputMethodHandler myInputMethodRequestsHandler;
  private InputMethodRequests myInputMethodRequestsSwingWrapper;
  private boolean myIsOneLineMode;
  private boolean myIsRendererMode;
  private VirtualFile myVirtualFile;
  private boolean myIsColumnMode = false;
  @Nullable private Color myForcedBackground = null;
  @Nullable private Dimension myPreferredSize;
  private int myVirtualPageHeight;

  private final Alarm myMouseSelectionStateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
  private Runnable myMouseSelectionStateResetRunnable;

  private boolean myEmbeddedIntoDialogWrapper;
  @Nullable private CachedFontContent myLastCache;
  private int myDragOnGutterSelectionStartLine = -1;
  private RangeMarker myDraggedRange;

  private boolean mySoftWrapsChanged;

  // transient fields used during painting
  private VisualPosition mySelectionStartPosition;
  private VisualPosition mySelectionEndPosition;

  private Color myLastBackgroundColor = null;
  private Point myLastBackgroundPosition = null;
  private int myLastBackgroundWidth;
  private static final boolean ourIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
  @NotNull private final JPanel myHeaderPanel;

  @Nullable private MouseEvent myInitialMouseEvent;
  private boolean myIgnoreMouseEventsConsecutiveToInitial;

  private EditorDropHandler myDropHandler;

  private char[] myPrefixText;
  private TextAttributes myPrefixAttributes;
  private int myPrefixWidthInPixels;
  @NotNull private final IndentsModel myIndentsModel;

  @Nullable
  private CharSequence myPlaceholderText;
  private int myLastPaintedPlaceholderWidth;
  private boolean myShowPlaceholderWhenFocused;

  private boolean myStickySelection;
  private int myStickySelectionStart;
  private boolean myScrollToCaret = true;

  private boolean myPurePaintingMode;
  private boolean myPaintSelection;

  private final EditorSizeAdjustmentStrategy mySizeAdjustmentStrategy = new EditorSizeAdjustmentStrategy();
  private final Disposable myDisposable = Disposer.newDisposable();

  private List<CaretState> myCaretStateBeforeLastPress;
  private LogicalPosition myLastMousePressedLocation;
  private VisualPosition myTargetMultiSelectionPosition;
  private boolean myMultiSelectionInProgress;
  private boolean myRectangularSelectionInProgress;
  private boolean myLastPressCreatedCaret;
  // Set when the selection (normal or block one) initiated by mouse drag becomes noticeable (at least one character is selected).
  // Reset on mouse press event.
  private boolean myCurrentDragIsSubstantial;

  private CaretImpl myPrimaryCaret;

  public final boolean myDisableRtl = Registry.is("editor.disable.rtl");
  public final boolean myUseNewRendering = Registry.is("editor.new.rendering");
  final EditorView myView;

  private boolean myCharKeyPressed;
  private boolean myNeedToSelectPreviousChar;

  private boolean myDocumentChangeInProgress;
  private boolean myErrorStripeNeedsRepaint;

  static {
    ourCaretBlinkingCommand.start();
  }

  private int myExpectedCaretOffset = -1;

  EditorImpl(@NotNull Document document, boolean viewer, @Nullable Project project) {
    assertIsDispatchThread();
    myProject = project;
    myDocument = (DocumentEx)document;
    if (myDocument instanceof DocumentImpl) {
      ((DocumentImpl)myDocument).requestTabTracking();
    }
    myScheme = createBoundColorSchemeDelegate(null);
    initTabPainter();
    myIsViewer = viewer;
    mySettings = new SettingsImpl(this, project);
    if (shouldSoftWrapsBeForced()) {
      mySettings.setUseSoftWrapsQuiet();
      putUserData(FORCED_SOFT_WRAPS, Boolean.TRUE);
    }

    mySelectionModel = new SelectionModelImpl(this);
    myMarkupModel = new EditorMarkupModelImpl(this);
    myFoldingModel = new FoldingModelImpl(this);
    myCaretModel = new CaretModelImpl(this);
    mySoftWrapModel = new SoftWrapModelImpl(this);
    if (!myUseNewRendering) mySizeContainer.reset();

    myCommandProcessor = CommandProcessor.getInstance();

    if (project != null) {
      myConnection = project.getMessageBus().connect();
      myConnection.subscribe(DocumentBulkUpdateListener.TOPIC, new EditorDocumentBulkUpdateAdapter());
    }

    MarkupModelListener markupModelListener = new MarkupModelListener() {
      private boolean areRenderersInvolved(@NotNull RangeHighlighterEx highlighter) {
        return highlighter.getCustomRenderer() != null ||
               highlighter.getGutterIconRenderer() != null ||
               highlighter.getLineMarkerRenderer() != null ||
               highlighter.getLineSeparatorRenderer() != null;
      }

      @Override
      public void afterAdded(@NotNull RangeHighlighterEx highlighter) {
        attributesChanged(highlighter, areRenderersInvolved(highlighter));
      }

      @Override
      public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
        attributesChanged(highlighter, areRenderersInvolved(highlighter));
      }

      @Override
      public void attributesChanged(@NotNull RangeHighlighterEx highlighter, boolean renderersChanged) {
        if (myDocument.isInBulkUpdate()) return; // bulkUpdateFinished() will repaint anything

        if (myUseNewRendering && renderersChanged) {
          updateGutterSize();
        }

        boolean errorStripeNeedsRepaint = renderersChanged || highlighter.getErrorStripeMarkColor() != null;
        if (myUseNewRendering && myDocumentChangeInProgress) {
          // postpone repaint request, as folding model can be in inconsistent state and so coordinate
          // conversions might give incorrect results
          myErrorStripeNeedsRepaint |= errorStripeNeedsRepaint;
          return;
        }

        int textLength = myDocument.getTextLength();

        clearTextWidthCache();

        int start = Math.min(Math.max(highlighter.getAffectedAreaStartOffset(), 0), textLength);
        int end = Math.min(Math.max(highlighter.getAffectedAreaEndOffset(), 0), textLength);

        int startLine = start == -1 ? 0 : myDocument.getLineNumber(start);
        int endLine = end == -1 ? myDocument.getLineCount() : myDocument.getLineNumber(end);
        TextAttributes attributes = highlighter.getTextAttributes();
        if (myUseNewRendering && start != end && attributes != null && attributes.getFontType() != Font.PLAIN) {
          myView.invalidateRange(start, end);
        }
        repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount()));

        // optimization: there is no need to repaint error stripe if the highlighter is invisible on it
        if (errorStripeNeedsRepaint) {
          ((EditorMarkupModelImpl)getMarkupModel()).repaint(start, end);
        }

        if (!myUseNewRendering && renderersChanged) {
          updateGutterSize();
        }

        updateCaretCursor();
      }
    };

    ((MarkupModelEx)DocumentMarkupModel.forDocument(myDocument, myProject, true)).addMarkupModelListener(myCaretModel, markupModelListener);
    getMarkupModel().addMarkupModelListener(myCaretModel, markupModelListener);

    myDocument.addDocumentListener(myFoldingModel, myCaretModel);
    myDocument.addDocumentListener(myCaretModel, myCaretModel);
    myDocument.addDocumentListener(mySelectionModel, myCaretModel);

    myDocument.addDocumentListener(new EditorDocumentAdapter(), myCaretModel);
    myDocument.addDocumentListener(mySoftWrapModel, myCaretModel);

    myFoldingModel.addListener(mySoftWrapModel, myCaretModel);

    myIndentsModel = new IndentsModelImpl(this);
    myCaretModel.addCaretListener(new CaretListener() {
      @Nullable private LightweightHint myCurrentHint = null;
      @Nullable private IndentGuideDescriptor myCurrentCaretGuide = null;

      @Override
      public void caretPositionChanged(CaretEvent e) {
        if (myStickySelection) {
          int selectionStart = Math.min(myStickySelectionStart, getDocument().getTextLength() - 1);
          mySelectionModel.setSelection(selectionStart, myCaretModel.getVisualPosition(), myCaretModel.getOffset());
        }

        final IndentGuideDescriptor newGuide = myIndentsModel.getCaretIndentGuide();
        if (!Comparing.equal(myCurrentCaretGuide, newGuide)) {
          repaintGuide(newGuide);
          repaintGuide(myCurrentCaretGuide);
          myCurrentCaretGuide = newGuide;

          if (myCurrentHint != null) {
            myCurrentHint.hide();
            myCurrentHint = null;
          }

          if (newGuide != null) {
            final Rectangle visibleArea = getScrollingModel().getVisibleArea();
            final int line = newGuide.startLine;
            if (logicalLineToY(line) < visibleArea.y) {
              TextRange textRange = new TextRange(myDocument.getLineStartOffset(line), myDocument.getLineEndOffset(line));

              myCurrentHint = EditorFragmentComponent.showEditorFragmentHint(EditorImpl.this, textRange, false, false);
            }
          }
        }
      }

      @Override
      public void caretAdded(CaretEvent e) {
        if (myPrimaryCaret != null) {
          myPrimaryCaret.updateVisualPosition(); // repainting old primary caret's row background
        }
        repaintCaretRegion(e);
        myPrimaryCaret = myCaretModel.getPrimaryCaret();
      }

      @Override
      public void caretRemoved(CaretEvent e) {
        repaintCaretRegion(e);
        myPrimaryCaret = myCaretModel.getPrimaryCaret(); // repainting new primary caret's row background
        myPrimaryCaret.updateVisualPosition();
      }
    });

    myCaretCursor = new CaretCursor();

    myFoldingModel.flushCaretShift();
    myScrollBarOrientation = VERTICAL_SCROLLBAR_RIGHT;

    mySoftWrapModel.addSoftWrapChangeListener(new SoftWrapChangeListenerAdapter() {
      @Override
      public void recalculationEnds() {
        if (myCaretModel.isUpToDate()) {
          myCaretModel.updateVisualPosition();
        }
      }

      @Override
      public void softWrapsChanged() {
        mySoftWrapsChanged = true;
      }
    });

    if (!myUseNewRendering) {
      mySoftWrapModel.addVisualSizeChangeListener(new VisualSizeChangeListener() {
        @Override
        public void onLineWidthsChange(int startLine, int oldEndLine, int newEndLine, @NotNull TIntIntHashMap lineWidths) {
          mySizeContainer.update(startLine, newEndLine, oldEndLine);
          for (int i = startLine; i <= newEndLine; i++) {
            if (lineWidths.contains(i)) {
              int width = lineWidths.get(i);
              if (width >= 0) {
                mySizeContainer.updateLineWidthIfNecessary(i, width);
              }
            }
          }
        }
      });
    }

    EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
    setHighlighter(highlighter);

    myEditorComponent = new EditorComponentImpl(this);
    myScrollPane = new MyScrollPane();
    myVerticalScrollBar = (MyScrollBar)myScrollPane.getVerticalScrollBar();
    myVerticalScrollBar.setOpaque(false);
    myPanel = new JPanel();

    myView = myUseNewRendering ? new EditorView(this) : null;

    UIUtil.putClientProperty(
      myPanel, JBSwingUtilities.NOT_IN_HIERARCHY_COMPONENTS, new Iterable<JComponent>() {
        @Override
        public Iterator<JComponent> iterator() {
          JComponent component = getPermanentHeaderComponent();
          if (component != null && !component.isValid()) {
            return Collections.singleton(component).iterator();
          }
          return ContainerUtil.emptyIterator();
        }
      });

    myHeaderPanel = new MyHeaderPanel();
    myGutterComponent = new EditorGutterComponentImpl(this);
    initComponent();
    myScrollingModel = new ScrollingModelImpl(this);
    if (UISettings.getInstance().getPresentationMode()) {
      setFontSize(UISettings.getInstance().PRESENTATION_MODE_FONT_SIZE);
    }

    myGutterComponent.setLineNumberAreaWidthFunction(new TIntFunction() {
      @Override
      public int execute(int lineNumber) {
        return getFontMetrics(Font.PLAIN).stringWidth(Integer.toString(lineNumber + 1));
      }
    });
    myGutterComponent.updateSize();
    Dimension preferredSize = getPreferredSize();
    myEditorComponent.setSize(preferredSize);

    updateCaretCursor();

    // This hacks context layout problem where editor appears scrolled to the right just after it is created.
    if (!ourIsUnitTestMode) {
      UiNotifyConnector.doWhenFirstShown(myEditorComponent, new Runnable() {
        @Override
        public void run() {
          if (!isDisposed() && !myScrollingModel.isScrollingNow()) {
            myScrollingModel.disableAnimation();
            myScrollingModel.scrollHorizontally(0);
            myScrollingModel.enableAnimation();
          }
        }
      });
    }
  }

  private boolean shouldSoftWrapsBeForced() {
    if (myUseNewRendering || mySettings.isUseSoftWraps() ||
        // Disable checking for files in intermediate states - e.g. for files during refactoring.
        (myProject != null && PsiDocumentManager.getInstance(myProject).isDocumentBlockedByPsi(myDocument))) {
      return false;
    }
    int lineWidthLimit = Registry.intValue("editor.soft.wrap.force.limit");
    for (int i = 0; i < myDocument.getLineCount(); i++) {
      if (myDocument.getLineEndOffset(i) - myDocument.getLineStartOffset(i) > lineWidthLimit) {
        return true;
      }
    }
    return false;
  }

  @NotNull
  static Color adjustThumbColor(@NotNull Color base, boolean dark) {
    return dark ? ColorUtil.withAlpha(ColorUtil.shift(base, 1.35), 0.5)
                : ColorUtil.withAlpha(ColorUtil.shift(base, 0.68), 0.4);
  }

  boolean isDarkEnough() {
    return ColorUtil.isDark(getBackgroundColor());
  }

  private void repaintCaretRegion(CaretEvent e) {
    CaretImpl caretImpl = (CaretImpl)e.getCaret();
    if (caretImpl != null) {
      caretImpl.updateVisualPosition();
      if (caretImpl.hasSelection()) {
        repaint(caretImpl.getSelectionStart(), caretImpl.getSelectionEnd(), false);
      }
    }
  }

  @NotNull
  @Override
  public EditorColorsScheme createBoundColorSchemeDelegate(@Nullable final EditorColorsScheme customGlobalScheme) {
    return new MyColorSchemeDelegate(customGlobalScheme);
  }

  private void repaintGuide(@Nullable IndentGuideDescriptor guide) {
    if (guide != null) {
      repaintLines(guide.startLine, guide.endLine);
    }
  }

  @Override
  public int getPrefixTextWidthInPixels() {
    return myUseNewRendering ? (int)myView.getPrefixTextWidthInPixels() : myPrefixWidthInPixels;
  }

  @Override
  public void setPrefixTextAndAttributes(@Nullable String prefixText, @Nullable TextAttributes attributes) {
    myPrefixText = prefixText == null ? null : prefixText.toCharArray();
    myPrefixAttributes = attributes;
    myPrefixWidthInPixels = 0;
    if (myPrefixText != null) {
      for (char c : myPrefixText) {
        LOG.assertTrue(myPrefixAttributes != null);
        if (myPrefixAttributes != null) {
          myPrefixWidthInPixels += EditorUtil.charWidth(c, myPrefixAttributes.getFontType(), this);
        }
      }
    }
    mySoftWrapModel.recalculate();
    if (myUseNewRendering) myView.setPrefix(prefixText, attributes);
  }

  @Override
  public boolean isPurePaintingMode() {
    return myPurePaintingMode;
  }

  @Override
  public void setPurePaintingMode(boolean enabled) {
    myPurePaintingMode = enabled;
  }

  @Override
  public void registerScrollBarRepaintCallback(@Nullable ButtonlessScrollBarUI.ScrollbarRepaintCallback callback) {
    myVerticalScrollBar.registerRepaintCallback(callback);
  }

  @Override
  public int getExpectedCaretOffset() {
    return myExpectedCaretOffset == -1 ? getCaretModel().getOffset() : myExpectedCaretOffset;
  }

  @Override
  public void setViewer(boolean isViewer) {
    myIsViewer = isViewer;
  }

  @Override
  public boolean isViewer() {
    return myIsViewer || myIsRendererMode;
  }

  @Override
  public boolean isRendererMode() {
    return myIsRendererMode;
  }

  @Override
  public void setRendererMode(boolean isRendererMode) {
    myIsRendererMode = isRendererMode;
  }

  @Override
  public void setFile(VirtualFile vFile) {
    myVirtualFile = vFile;
    reinitSettings();
  }

  @Override
  public VirtualFile getVirtualFile() {
    return myVirtualFile;
  }

  @Override
  public void setSoftWrapAppliancePlace(@NotNull SoftWrapAppliancePlaces place) {
    mySettings.setSoftWrapAppliancePlace(place);
  }

  @Override
  @NotNull
  public SelectionModelImpl getSelectionModel() {
    return mySelectionModel;
  }

  @Override
  @NotNull
  public MarkupModelEx getMarkupModel() {
    return myMarkupModel;
  }

  @Override
  @NotNull
  public FoldingModelImpl getFoldingModel() {
    return myFoldingModel;
  }

  @Override
  @NotNull
  public CaretModelImpl getCaretModel() {
    return myCaretModel;
  }

  @Override
  @NotNull
  public ScrollingModelEx getScrollingModel() {
    return myScrollingModel;
  }

  @Override
  @NotNull
  public SoftWrapModelImpl getSoftWrapModel() {
    return mySoftWrapModel;
  }

  @Override
  @NotNull
  public EditorSettings getSettings() {
    assertReadAccess();
    return mySettings;
  }

  public void resetSizes() {
    if (myUseNewRendering) {
      myView.reset();
    }
    else {
      mySizeContainer.reset();
    }
  }

  @Override
  public void reinitSettings() {
    assertIsDispatchThread();
    clearSettingsCache();

    reinitDocumentIndentOptions();

    for (EditorColorsScheme scheme = myScheme;
         scheme instanceof DelegateColorScheme;
         scheme = ((DelegateColorScheme)scheme).getDelegate()) {
      if (scheme instanceof MyColorSchemeDelegate) {
        ((MyColorSchemeDelegate)scheme).updateGlobalScheme();
        break;
      }
    }

    boolean softWrapsUsedBefore = mySoftWrapModel.isSoftWrappingEnabled();

    mySettings.reinitSettings();
    mySoftWrapModel.reinitSettings();
    myCaretModel.reinitSettings();
    mySelectionModel.reinitSettings();
    ourCaretBlinkingCommand.setBlinkCaret(mySettings.isBlinkCaret());
    ourCaretBlinkingCommand.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
    if (myUseNewRendering) {
      myView.reinitSettings();
    }
    else {
      mySizeContainer.reset();
    }
    myFoldingModel.rebuild();

    if (softWrapsUsedBefore ^ mySoftWrapModel.isSoftWrappingEnabled()) {
      mySizeContainer.reset();
      validateSize();
    }

    myHighlighter.setColorScheme(myScheme);
    myFoldingModel.refreshSettings();

    myGutterComponent.reinitSettings();
    myGutterComponent.revalidate();

    myEditorComponent.repaint();

    initTabPainter();
    updateCaretCursor();

    if (myInitialMouseEvent != null) {
      myIgnoreMouseEventsConsecutiveToInitial = true;
    }

    myCaretModel.updateVisualPosition();

    // make sure carets won't appear at invalid positions (e.g. on Tab width change)
    for (Caret caret : getCaretModel().getAllCarets()) {
      caret.moveToOffset(caret.getOffset());
    }
  }

  private void clearSettingsCache() {
    myCharHeight = -1;
    myLineHeight = -1;
    myDescent = -1;
    myPlainFontMetrics = null;

    clearTextWidthCache();
  }

  private void reinitDocumentIndentOptions() {
    if (myProject != null && !myProject.isDisposed()) {
      CodeStyleSettingsManager.updateDocumentIndentOptions(myProject, myDocument);
    }
  }

  private void initTabPainter() {
    myTabPainter = new ArrowPainter(
      ColorProvider.byColorsScheme(myScheme, EditorColors.WHITESPACES_COLOR),
      new Computable.PredefinedValueComputable<Integer>(EditorUtil.getSpaceWidth(Font.PLAIN, this)),
      new Computable<Integer>() {
        @Override
        public Integer compute() {
          return getCharHeight();
        }
      }
    );
  }

  public void throwDisposalError(@NonNls @NotNull String msg) {
    myTraceableDisposable.throwDisposalError(msg);
  }

  public void release() {
    assertIsDispatchThread();
    if (isReleased) {
      throwDisposalError("Double release of editor:");
    }
    myTraceableDisposable.kill(null);

    isReleased = true;
    clearSettingsCache();

    myFoldingModel.dispose();
    mySoftWrapModel.release();
    myMarkupModel.dispose();

    myScrollingModel.dispose();
    myGutterComponent.dispose();
    myMousePressedEvent = null;
    myMouseMovedEvent = null;
    Disposer.dispose(myCaretModel);
    Disposer.dispose(mySoftWrapModel);
    if (myUseNewRendering) Disposer.dispose(myView);
    clearCaretThread();

    myFocusListeners.clear();
    myMouseListeners.clear();
    myMouseMotionListeners.clear();

    if (myConnection != null) {
      myConnection.disconnect();
    }
    if (myDocument instanceof DocumentImpl) {
      ((DocumentImpl)myDocument).giveUpTabTracking();
    }
    Disposer.dispose(myDisposable);
  }

  private void clearCaretThread() {
    synchronized (ourCaretBlinkingCommand) {
      if (ourCaretBlinkingCommand.myEditor == this) {
        ourCaretBlinkingCommand.myEditor = null;
      }
    }
  }

  private static boolean firstCharTyped = true;

  private void initComponent() {
    myPanel.setLayout(new BorderLayout());

    myPanel.add(myHeaderPanel, BorderLayout.NORTH);

    myGutterComponent.setOpaque(true);

    myScrollPane.setViewportView(myEditorComponent);
    //myScrollPane.setBorder(null);
    myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

    myScrollPane.setRowHeaderView(myGutterComponent);

    myEditorComponent.setTransferHandler(new MyTransferHandler());
    myEditorComponent.setAutoscrolls(true);

   /*  Default mode till 1.4.0
    *   myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
    */

    if (mayShowToolbar()) {
      JLayeredPane layeredPane = new JBLayeredPane() {
        @Override
        public void doLayout() {
          final Component[] components = getComponents();
          final Rectangle r = getBounds();
          for (Component c : components) {
            if (c instanceof JScrollPane) {
              c.setBounds(0, 0, r.width, r.height);
            }
            else {
              final Dimension d = c.getPreferredSize();
              final MyScrollBar scrollBar = getVerticalScrollBar();
