@Nullable
  State processBaseChange(int oldLine1, int oldLine2, int shift) {
    int line1 = getStartLine();
    int line2 = getEndLine();

    UpdatedLineRange newRange =
        DiffUtil.updateRangeOnModification(line1, line2, oldLine1, oldLine2, shift);

    boolean rangeAffected =
        newRange.damaged
            || (oldLine2 >= line1
                && oldLine1 <= line2); // RangeMarker can be updated in a different way
    State oldState = rangeAffected ? storeState() : null;

    if (newRange.startLine == newRange.endLine
        && getDiffType() == TextDiffType.DELETED
        && !isResolved()) {
      if (oldState == null) oldState = storeState();
      myViewer.markChangeResolved(this);
    }

    setStartLine(newRange.startLine);
    setEndLine(newRange.endLine);

    return oldState;
  }
  @CalledInAwt
  public void doReinstallHighlighter() {
    destroyHighlighter();
    installHighlighter();

    if (!myInnerFragmentsDamaged) {
      destroyInnerHighlighter();
      installInnerHighlighter();
    }

    myViewer.repaintDividers();
  }
  private void createHighlighter(@NotNull ThreeSide side) {
    Editor editor = side.select(myViewer.getEditors());

    TextDiffType type = getDiffType();
    boolean resolved = isResolved(side);
    int startLine = getStartLine(side);
    int endLine = getEndLine(side);

    boolean ignored = !resolved && myInnerFragments != null;
    myHighlighters.addAll(
        DiffDrawUtil.createHighlighter(editor, startLine, endLine, type, ignored, resolved));
    myHighlighters.addAll(
        DiffDrawUtil.createLineMarker(editor, startLine, endLine, type, resolved));
  }
  private void createInnerHighlighter(@NotNull ThreeSide side) {
    if (isResolved(side)) return;
    if (myInnerFragments == null) return;

    Editor editor = myViewer.getEditor(side);
    int start =
        DiffUtil.getLinesRange(editor.getDocument(), getStartLine(side), getEndLine(side))
            .getStartOffset();
    for (MergeWordFragment fragment : myInnerFragments) {
      int innerStart = start + fragment.getStartOffset(side);
      int innerEnd = start + fragment.getEndOffset(side);
      myInnerHighlighters.addAll(
          DiffDrawUtil.createInlineHighlighter(editor, innerStart, innerEnd, getDiffType()));
    }
  }
  @CalledInAwt
  void setResolved(@NotNull Side side, boolean value) {
    myResolved[side.getIndex()] = value;

    markInnerFragmentsDamaged();
    if (isResolved()) {
      destroyInnerHighlighter();
    } else {
      // Destroy only resolved side to reduce blinking
      Document document =
          myViewer.getEditor(side.select(ThreeSide.LEFT, ThreeSide.RIGHT)).getDocument();
      for (RangeHighlighter highlighter : myInnerHighlighters) {
        if (document.equals(highlighter.getDocument())) {
          highlighter.dispose(); // it's OK to call dispose() few times
        }
      }
    }
  }
  @Nullable
  private MyGutterOperation createOperation(@NotNull ThreeSide side, @NotNull OperationType type) {
    if (isResolved(side)) return null;

    EditorEx editor = myViewer.getEditor(side);
    Document document = editor.getDocument();

    int line = getStartLine(side);
    int offset =
        line == DiffUtil.getLineCount(document)
            ? document.getTextLength()
            : document.getLineStartOffset(line);

    RangeHighlighter highlighter =
        editor
            .getMarkupModel()
            .addRangeHighlighter(
                offset,
                offset,
                HighlighterLayer.ADDITIONAL_SYNTAX,
                null,
                HighlighterTargetArea.LINES_IN_RANGE);
    return new MyGutterOperation(side, highlighter, type);
  }