/**
  * We know that there are problems with incremental soft wraps cache update at the moment. Hence,
  * we may implement full cache reconstruction when the problem is encountered in order to avoid
  * customer annoyance.
  *
  * <p>However, the problems still should be fixed, hence, we report them only if dedicated flag is
  * set.
  *
  * <p>Current method encapsulates the logic mentioned above.
  *
  * @param task command object that which execution may trigger incremental update of update soft
  *     wraps cache
  */
 @SuppressWarnings({"UseOfArchaicSystemPropertyAccessors"})
 private void executeSafely(SoftWrapAwareTask task) {
   try {
     task.run(true);
   } catch (Throwable e) {
     if (Boolean.getBoolean(DEBUG_PROPERTY_NAME)
         || ApplicationManager.getApplication().isUnitTestMode()) {
       String info = myEditor.dumpState();
       LOG.error(
           String.format("Unexpected exception occurred during performing '%s'", task), e, info);
     }
     myEditor.getFoldingModel().rebuild();
     myDataMapper.release();
     myApplianceManager.reset();
     myStorage.removeAll();
     myApplianceManager.recalculateIfNecessary();
     try {
       task.run(true);
     } catch (Throwable e1) {
       String info = myEditor.dumpState();
       LOG.error(
           String.format("Can't perform %s even with complete soft wraps cache re-parsing", task),
           e1,
           info);
       myEditor.getSettings().setUseSoftWraps(false);
       task.run(false);
     }
   }
 }
  @Override
  public boolean isSoftWrappingEnabled() {
    if (!myUseSoftWraps
        || (!myEditor.myUseNewRendering && myEditor.isOneLineMode())
        || myEditor.isPurePaintingMode()) {
      return false;
    }

    // We check that current thread is EDT because attempt to retrieve information about visible
    // area width may fail otherwise
    Application application = ApplicationManager.getApplication();
    Thread lastEdt = myLastEdt.get();
    Thread currentThread = Thread.currentThread();
    if (lastEdt != currentThread) {
      if (application.isDispatchThread()) {
        myLastEdt = new SoftReference<Thread>(currentThread);
      } else {
        myLastEdt = new SoftReference<Thread>(null);
        return false;
      }
    }

    Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
    return visibleArea.width > 0 && visibleArea.height > 0;
  }
  /**
   * Called on editor settings change. Current model is expected to drop all cached information
   * about the settings if any.
   */
  public void reinitSettings() {
    boolean softWrapsUsedBefore = myUseSoftWraps;
    myUseSoftWraps = areSoftWrapsEnabledInEditor();

    int tabWidthBefore = myTabWidth;
    myTabWidth = EditorUtil.getTabSize(myEditor);

    boolean fontsChanged = false;
    if (!myFontPreferences.equals(myEditor.getColorsScheme().getFontPreferences())
        && myEditorTextRepresentationHelper instanceof DefaultEditorTextRepresentationHelper) {
      fontsChanged = true;
      myEditor.getColorsScheme().getFontPreferences().copyTo(myFontPreferences);
      ((DefaultEditorTextRepresentationHelper) myEditorTextRepresentationHelper)
          .clearSymbolWidthCache();
      myPainter.reinit();
    }

    if ((myUseSoftWraps ^ softWrapsUsedBefore)
        || (tabWidthBefore >= 0 && myTabWidth != tabWidthBefore)
        || fontsChanged) {
      myApplianceManager.reset();
      myDeferredFoldRegions.clear();
      myStorage.removeAll();
      myEditor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
    }
  }
  public SoftWrapModelImpl(@NotNull EditorImpl editor) {
    myEditor = editor;
    myStorage = new SoftWrapsStorage();
    myPainter = new CompositeSoftWrapPainter(editor);
    myEditorTextRepresentationHelper = new DefaultEditorTextRepresentationHelper(editor);
    myDataMapper = new CachingSoftWrapDataMapper(editor, myStorage);
    myApplianceManager = new SoftWrapApplianceManager(myStorage, editor, myPainter, myDataMapper);
    myVisualSizeManager = new SoftWrapAwareVisualSizeManager(myPainter);

    myApplianceManager.addListener(myVisualSizeManager);
    myApplianceManager.addListener(
        new SoftWrapAwareDocumentParsingListenerAdapter() {
          @Override
          public void recalculationEnds() {
            for (SoftWrapChangeListener listener : mySoftWrapListeners) {
              listener.recalculationEnds();
            }
          }
        });
    myUseSoftWraps = areSoftWrapsEnabledInEditor();
    myEditor.getColorsScheme().getFontPreferences().copyTo(myFontPreferences);

    editor.addPropertyChangeListener(this, this);

    myApplianceManager.addListener(myDataMapper);
  }
  private boolean isInsideSoftWrap(@NotNull VisualPosition visual, boolean countBeforeSoftWrap) {
    if (!isSoftWrappingEnabled()) {
      return false;
    }
    SoftWrapModel model = myEditor.getSoftWrapModel();
    if (!model.isSoftWrappingEnabled()) {
      return false;
    }
    LogicalPosition logical = myEditor.visualToLogicalPosition(visual);
    int offset = myEditor.logicalPositionToOffset(logical);
    if (offset <= 0) {
      // Never expect to be here, just a defensive programming.
      return false;
    }

    SoftWrap softWrap = model.getSoftWrap(offset);
    if (softWrap == null) {
      return false;
    }

    // We consider visual positions that point after the last symbol before soft wrap and the first
    // symbol after soft wrap to not
    // belong to soft wrap-introduced virtual space.
    VisualPosition visualAfterSoftWrap = myEditor.offsetToVisualPosition(offset);
    if (visualAfterSoftWrap.line == visual.line && visualAfterSoftWrap.column <= visual.column) {
      return false;
    }

    if (myEditor.myUseNewRendering) {
      VisualPosition beforeSoftWrap = myEditor.offsetToVisualPosition(offset, true, true);
      return visual.line > beforeSoftWrap.line
          || visual.column > beforeSoftWrap.column
          || visual.column == beforeSoftWrap.column && countBeforeSoftWrap;
    } else {
      VisualPosition visualBeforeSoftWrap = myEditor.offsetToVisualPosition(offset - 1);
      int x = 0;
      LogicalPosition logLineStart =
          myEditor.visualToLogicalPosition(new VisualPosition(visualBeforeSoftWrap.line, 0));
      if (logLineStart.softWrapLinesOnCurrentLogicalLine > 0) {
        int offsetLineStart = myEditor.logicalPositionToOffset(logLineStart);
        softWrap = model.getSoftWrap(offsetLineStart);
        if (softWrap != null) {
          x = softWrap.getIndentInPixels();
        }
      }
      int width =
          EditorUtil.textWidthInColumns(
              myEditor, myEditor.getDocument().getCharsSequence(), offset - 1, offset, x);
      int softWrapStartColumn = visualBeforeSoftWrap.column + width;
      if (visual.line > visualBeforeSoftWrap.line) {
        return true;
      }
      return countBeforeSoftWrap
          ? visual.column >= softWrapStartColumn
          : visual.column > softWrapStartColumn;
    }
  }
 @Override
 public List<? extends SoftWrap> getRegisteredSoftWraps() {
   if (!isSoftWrappingEnabled()) {
     return Collections.emptyList();
   }
   List<SoftWrapImpl> softWraps = myStorage.getSoftWraps();
   if (!softWraps.isEmpty()
       && softWraps.get(softWraps.size() - 1).getStart()
           >= myEditor.getDocument().getTextLength()) {
     LOG.error(
         "Unexpected soft wrap location", new Attachment("editorState.txt", myEditor.dumpState()));
   }
   return softWraps;
 }
 public void recalculate() {
   myApplianceManager.reset();
   myStorage.removeAll();
   myDeferredFoldRegions.clear();
   myEditor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
   myApplianceManager.recalculateIfNecessary();
 }
 @Override
 public int paint(
     @NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
   if (!isSoftWrappingEnabled()) {
     return 0;
   }
   if (!myEditor.getSettings().isAllSoftWrapsShown()) {
     int visualLine = y / lineHeight;
     LogicalPosition position =
         myEditor.visualToLogicalPosition(new VisualPosition(visualLine, 0));
     if (position.line != myEditor.getCaretModel().getLogicalPosition().line) {
       return myPainter.getDrawingHorizontalOffset(g, drawingType, x, y, lineHeight);
     }
   }
   return doPaint(g, drawingType, x, y, lineHeight);
 }
  @Override
  public void beforeDocumentChangeAtCaret() {
    CaretModel caretModel = myEditor.getCaretModel();
    VisualPosition visualCaretPosition = caretModel.getVisualPosition();
    if (!isInsideSoftWrap(visualCaretPosition)) {
      return;
    }

    SoftWrap softWrap = myStorage.getSoftWrap(caretModel.getOffset());
    if (softWrap == null) {
      return;
    }

    myEditor
        .getDocument()
        .replaceString(softWrap.getStart(), softWrap.getEnd(), softWrap.getText());
    caretModel.moveToVisualPosition(visualCaretPosition);
  }
 @Override
 public int logicalPositionToOffset(@NotNull LogicalPosition logicalPosition) {
   if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
     return myEditor.logicalPositionToOffset(logicalPosition, false);
   }
   myActive++;
   try {
     myLogicalToOffsetTask.input = logicalPosition;
     executeSafely(myLogicalToOffsetTask);
     return myLogicalToOffsetTask.output;
   } finally {
     myActive--;
   }
 }
 @NotNull
 @Override
 public LogicalPosition offsetToLogicalPosition(int offset) {
   if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
     return myEditor.offsetToLogicalPosition(offset, false);
   }
   myActive++;
   try {
     myOffsetToLogicalTask.input = offset;
     executeSafely(myOffsetToLogicalTask);
     return myOffsetToLogicalTask.output;
   } finally {
     myActive--;
   }
 }
 @NotNull
 @Override
 public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visual) {
   if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
     return myEditor.visualToLogicalPosition(visual, false);
   }
   myActive++;
   try {
     myVisualToLogicalTask.input = visual;
     executeSafely(myVisualToLogicalTask);
     return myVisualToLogicalTask.output;
   } finally {
     myActive--;
   }
 }
  @Override
  public boolean isVisible(SoftWrap softWrap) {
    FoldingModel foldingModel = myEditor.getFoldingModel();
    int start = softWrap.getStart();
    if (foldingModel.isOffsetCollapsed(start)) {
      return false;
    }

    // There is a possible case that soft wrap and collapsed folding region share the same offset,
    // i.e. soft wrap is represented
    // before the folding. We need to return 'true' in such situation. Hence, we check if offset
    // just before the soft wrap
    // is collapsed as well.
    return start <= 0 || !foldingModel.isOffsetCollapsed(start - 1);
  }
  /**
   * Encapsulates preparations for performing document dimension mapping (e.g. visual to logical
   * position) and answers if soft wraps-aware processing should be used (e.g. there is no need to
   * consider soft wraps if user configured them not to be used).
   *
   * @return <code>true</code> if soft wraps-aware processing should be used; <code>false</code>
   *     otherwise
   */
  private boolean prepareToMapping() {
    boolean useSoftWraps =
        myActive <= 0 && isSoftWrappingEnabled() && myEditor.getDocument().getTextLength() > 0;

    if (!useSoftWraps) {
      return false;
    }

    if (myDirty) {
      myApplianceManager.reset();
      myDeferredFoldRegions.clear();
      myDirty = false;
    }

    return myApplianceManager.recalculateIfNecessary();
  }
  /**
   * Encapsulates preparations for performing document dimension mapping (e.g. visual to logical
   * position) and answers if soft wraps-aware processing should be used (e.g. there is no need to
   * consider soft wraps if user configured them not to be used).
   *
   * @return <code>true</code> if soft wraps-aware processing should be used; <code>false</code>
   *     otherwise
   */
  private boolean prepareToMapping() {
    if (myUpdateInProgress
        || myBulkUpdateInProgress
        || myActive > 0
        || !isSoftWrappingEnabled()
        || myEditor.getDocument().getTextLength() <= 0) {
      return false;
    }

    if (myDirty) {
      myApplianceManager.reset();
      myDeferredFoldRegions.clear();
      myDirty = false;
    }

    return myApplianceManager.recalculateIfNecessary();
  }
 @Override
 @NotNull
 public List<? extends SoftWrap> getSoftWrapsForLine(int documentLine) {
   if (!isSoftWrappingEnabled() || documentLine < 0) {
     return Collections.emptyList();
   }
   Document document = myEditor.getDocument();
   if (documentLine >= document.getLineCount()) {
     return Collections.emptyList();
   }
   int start = document.getLineStartOffset(documentLine);
   int end = document.getLineEndOffset(documentLine);
   return getSoftWrapsForRange(
       start,
       end
           + 1 /* it's theoretically possible that soft wrap is registered just before the line feed,
                * hence, we add '1' here assuming that end line offset points to line feed symbol */);
 }
 private boolean areSoftWrapsEnabledInEditor() {
   return myEditor.getSettings().isUseSoftWraps()
       && !myEditor.myUseNewRendering
       && (!(myEditor.getDocument() instanceof DocumentImpl)
           || !((DocumentImpl) myEditor.getDocument()).acceptsSlashR());
 }