private void doUpdateRanges(
      int beforeChangedLine1, int beforeChangedLine2, int linesShift, int beforeTotalLines) {
    List<Range> rangesBeforeChange = new ArrayList<Range>();
    List<Range> rangesAfterChange = new ArrayList<Range>();
    List<Range> changedRanges = new ArrayList<Range>();

    sortRanges(
        beforeChangedLine1,
        beforeChangedLine2,
        linesShift,
        rangesBeforeChange,
        changedRanges,
        rangesAfterChange);

    Range firstChangedRange = ContainerUtil.getFirstItem(changedRanges);
    Range lastChangedRange = ContainerUtil.getLastItem(changedRanges);

    if (firstChangedRange != null && firstChangedRange.getLine1() < beforeChangedLine1) {
      beforeChangedLine1 = firstChangedRange.getLine1();
    }
    if (lastChangedRange != null && lastChangedRange.getLine2() > beforeChangedLine2) {
      beforeChangedLine2 = lastChangedRange.getLine2();
    }

    doUpdateRanges(
        beforeChangedLine1,
        beforeChangedLine2,
        linesShift,
        beforeTotalLines,
        rangesBeforeChange,
        changedRanges,
        rangesAfterChange);
  }
  private void doRollbackRange(@NotNull Range range) {
    DiffUtil.applyModification(
        myDocument,
        range.getLine1(),
        range.getLine2(),
        myVcsDocument,
        range.getVcsLine1(),
        range.getVcsLine2());

    markLinesUnchanged(
        range.getLine1(), range.getLine1() + range.getVcsLine2() - range.getVcsLine1());
  }
  @Nullable
  private RangeHighlighter createHighlighter(@NotNull Range range) {
    myApplication.assertIsDispatchThread();

    LOG.assertTrue(!myReleased, "Already released");

    if (myMode == Mode.SILENT) return null;

    int first =
        range.getLine1() >= getLineCount(myDocument)
            ? myDocument.getTextLength()
            : myDocument.getLineStartOffset(range.getLine1());

    int second =
        range.getLine2() >= getLineCount(myDocument)
            ? myDocument.getTextLength()
            : myDocument.getLineStartOffset(range.getLine2());

    final TextAttributes attr = LineStatusTrackerDrawing.getAttributesFor(range);
    final RangeHighlighter highlighter =
        DocumentMarkupModel.forDocument(myDocument, myProject, true)
            .addRangeHighlighter(
                first,
                second,
                HighlighterLayer.FIRST - 1,
                attr,
                HighlighterTargetArea.LINES_IN_RANGE);

    highlighter.setThinErrorStripeMark(true);
    highlighter.setGreedyToLeft(true);
    highlighter.setGreedyToRight(true);
    highlighter.setLineMarkerRenderer(LineStatusTrackerDrawing.createRenderer(range, this));
    highlighter.setEditorFilter(MarkupEditorFilterFactory.createIsNotDiffFilter());

    final String tooltip;
    if (range.getLine1() == range.getLine2()) {
      if (range.getVcsLine1() + 1 == range.getVcsLine2()) {
        tooltip = VcsBundle.message("tooltip.text.line.before.deleted", range.getLine1() + 1);
      } else {
        tooltip =
            VcsBundle.message(
                "tooltip.text.lines.before.deleted",
                range.getLine1() + 1,
                range.getVcsLine2() - range.getVcsLine1());
      }
    } else if (range.getLine1() + 1 == range.getLine2()) {
      tooltip = VcsBundle.message("tooltip.text.line.changed", range.getLine1() + 1);
    } else {
      tooltip =
          VcsBundle.message("tooltip.text.lines.changed", range.getLine1() + 1, range.getLine2());
    }

    highlighter.setErrorStripeTooltip(tooltip);
    return highlighter;
  }
  @Override
  public void paint(Editor editor, Graphics g, Rectangle r) {
    Color gutterColor = getGutterColor(myRange, editor);
    Color borderColor = getGutterBorderColor(editor);

    Rectangle area = getMarkerArea(editor, r, myRange.getLine1(), myRange.getLine2());
    final int x = area.x;
    final int endX = area.x + area.width;
    final int y = area.y;
    final int endY = area.y + area.height;

    if (myRange.getInnerRanges() == null) { // Mode.DEFAULT
      if (y != endY) {
        paintRect(g, gutterColor, borderColor, x, y, endX, endY);
      } else {
        paintTriangle(g, gutterColor, borderColor, x, endX, y);
      }
    } else { // Mode.SMART
      if (y == endY) {
        paintTriangle(g, gutterColor, borderColor, x, endX, y);
      } else {
        List<Range.InnerRange> innerRanges = myRange.getInnerRanges();
        for (Range.InnerRange innerRange : innerRanges) {
          if (innerRange.getType() == Range.DELETED) continue;

          int start = lineToY(editor, innerRange.getLine1());
          int end = lineToY(editor, innerRange.getLine2());

          paintRect(g, getGutterColor(innerRange, editor), null, x, start, endX, end);
        }

        for (int i = 0; i < innerRanges.size(); i++) {
          Range.InnerRange innerRange = innerRanges.get(i);
          if (innerRange.getType() != Range.DELETED) continue;

          int start;
          int end;

          if (i == 0) {
            start = lineToY(editor, innerRange.getLine1());
            end = lineToY(editor, innerRange.getLine2()) + 5;
          } else if (i == innerRanges.size() - 1) {
            start = lineToY(editor, innerRange.getLine1()) - 5;
            end = lineToY(editor, innerRange.getLine2());
          } else {
            start = lineToY(editor, innerRange.getLine1()) - 3;
            end = lineToY(editor, innerRange.getLine2()) + 3;
          }

          paintRect(g, getGutterColor(innerRange, editor), null, x, start, endX, end);
        }

        paintRect(g, null, borderColor, x, y, endX, endY);
      }
    }
  }
  @NotNull
  public TextRange getCurrentTextRange(@NotNull Range range) {
    myApplication.assertReadAccessAllowed();

    synchronized (myLock) {
      if (!range.isValid()) {
        LOG.warn("Current TextRange of invalid range");
      }

      return DiffUtil.getLinesRange(myDocument, range.getLine1(), range.getLine2());
    }
  }
 @Nullable
 public Range getPrevRange(int line) {
   synchronized (myLock) {
     for (int i = myRanges.size() - 1; i >= 0; i--) {
       Range range = myRanges.get(i);
       if (line > range.getLine1() && !range.isSelectedByLine(line)) {
         return range;
       }
     }
     return null;
   }
 }
 @NotNull
 private static String getTooltipText(@NotNull Range range) {
   if (range.getLine1() == range.getLine2()) {
     if (range.getVcsLine1() + 1 == range.getVcsLine2()) {
       return VcsBundle.message("tooltip.text.line.before.deleted", range.getLine1() + 1);
     } else {
       return VcsBundle.message(
           "tooltip.text.lines.before.deleted",
           range.getLine1() + 1,
           range.getVcsLine2() - range.getVcsLine1());
     }
   } else if (range.getLine1() + 1 == range.getLine2()) {
     return VcsBundle.message("tooltip.text.line.changed", range.getLine1() + 1);
   } else {
     return VcsBundle.message(
         "tooltip.text.lines.changed", range.getLine1() + 1, range.getLine2());
   }
 }
  private void sortRanges(
      int beforeChangedLine1,
      int beforeChangedLine2,
      int linesShift,
      @NotNull List<Range> rangesBeforeChange,
      @NotNull List<Range> changedRanges,
      @NotNull List<Range> rangesAfterChange) {
    if (!Registry.is("diff.status.tracker.skip.spaces")) {
      for (Range range : myRanges) {
        if (range.getLine2() < beforeChangedLine1) {
          rangesBeforeChange.add(range);
        } else if (range.getLine1() > beforeChangedLine2) {
          rangesAfterChange.add(range);
        } else {
          changedRanges.add(range);
        }
      }
    } else {
      int lastBefore = -1;
      int firstAfter = myRanges.size();
      for (int i = 0; i < myRanges.size(); i++) {
        Range range = myRanges.get(i);

        if (range.getLine2() < beforeChangedLine1) {
          lastBefore = i;
        } else if (range.getLine1() > beforeChangedLine2) {
          firstAfter = i;
          break;
        }
      }

      // Expand on ranges, that are separated from changes only by empty/whitespaces lines
      // This is needed to reduce amount of confusing cases, when changed blocks are matched wrong
      // due to matched empty lines between them
      // TODO: try to simplify logic, it's too high change that current one is broken somehow
      CharSequence sequence = myDocument.getCharsSequence();
      int lineCount = getLineCount(myDocument);

      while (true) {
        if (lastBefore == -1) break;

        if (lastBefore < myRanges.size() - 1 && firstAfter - lastBefore > 1) {
          Range firstChangedRange = myRanges.get(lastBefore + 1);
          if (firstChangedRange.getLine1() < beforeChangedLine1) {
            beforeChangedLine1 = firstChangedRange.getLine1();
          }
        }

        if (beforeChangedLine1 < 0) break;
        if (beforeChangedLine1 >= lineCount) break;
        int offset1 = myDocument.getLineStartOffset(beforeChangedLine1) - 2;

        int deltaLines = 0;
        while (offset1 > 0) {
          char c = sequence.charAt(offset1);
          if (!StringUtil.isWhiteSpace(c)) break;
          if (c == '\n') deltaLines++;
          offset1--;
        }

        if (deltaLines == 0) break;
        beforeChangedLine1 -= deltaLines;

        if (myRanges.get(lastBefore).getLine2() < beforeChangedLine1) break;
        while (lastBefore != -1 && myRanges.get(lastBefore).getLine2() >= beforeChangedLine1) {
          lastBefore--;
        }
      }

      while (true) {
        if (firstAfter == myRanges.size()) break;

        if (firstAfter > 0 && firstAfter - lastBefore > 1) {
          Range lastChangedRange = myRanges.get(firstAfter - 1);
          if (lastChangedRange.getLine2() > beforeChangedLine2) {
            beforeChangedLine2 = lastChangedRange.getLine2();
          }
        }

        // TODO: "afterChangedLine2 >= getLineCount(myDocument)" shouldn't ever be true, but it is
        // sometimes for some reason
        int afterChangedLine2 = beforeChangedLine2 + linesShift - 1;
        if (afterChangedLine2 < 0) break;
        if (afterChangedLine2 >= lineCount) break;
        int offset2 = myDocument.getLineEndOffset(afterChangedLine2) + 1;

        int deltaLines = 0;
        while (offset2 < sequence.length()) {
          char c = sequence.charAt(offset2);
          if (!StringUtil.isWhiteSpace(c)) break;
          if (c == '\n') deltaLines++;
          offset2++;
        }

        if (deltaLines == 0) break;
        beforeChangedLine2 += deltaLines;

        if (myRanges.get(firstAfter).getLine1() > beforeChangedLine2) break;
        while (firstAfter != myRanges.size()
            && myRanges.get(firstAfter).getLine1() <= beforeChangedLine2) {
          firstAfter++;
        }
      }

      for (int i = 0; i < myRanges.size(); i++) {
        Range range = myRanges.get(i);
        if (i <= lastBefore) {
          rangesBeforeChange.add(range);
        } else if (i >= firstAfter) {
          rangesAfterChange.add(range);
        } else {
          changedRanges.add(range);
        }
      }
    }
  }
 private static int getVcsLine2(
     @Nullable Range range, int line, int totalLinesBefore, int totalLinesAfter) {
   return range == null
       ? totalLinesAfter - totalLinesBefore + line
       : line + range.getVcsLine1() - range.getLine1();
 }