@NotNull
  private static List<Range> compareLines(@NotNull List<String> beforeLines,
                                          @NotNull List<String> afterLines,
                                          boolean beforeNoNewlineAtEOF,
                                          boolean afterNoNewlineAtEOF) {
    // patch treats "X\n" vs "X" as modification of a single line, while we treat it as a deletion of an empty line
    // so we have to adjust output accordingly

    if (!beforeNoNewlineAtEOF && !afterNoNewlineAtEOF) return doCompareLines(beforeLines, afterLines);

    int beforeLastLine = beforeLines.size() - 1;
    int afterLastLine = afterLines.size() - 1;

    List<String> beforeComparedLines = beforeNoNewlineAtEOF ? beforeLines.subList(0, beforeLastLine) : beforeLines;
    List<String> afterComparedLines = afterNoNewlineAtEOF ? afterLines.subList(0, afterLastLine) : afterLines;
    List<Range> ranges = doCompareLines(beforeComparedLines, afterComparedLines);

    if (beforeNoNewlineAtEOF && afterNoNewlineAtEOF) {
      if (beforeLines.get(beforeLastLine).equals(afterLines.get(afterLastLine))) return ranges;
      Range range = new Range(beforeLastLine, beforeLastLine + 1, afterLastLine, afterLastLine + 1);
      return appendRange(ranges, range);
    }
    else if (beforeNoNewlineAtEOF) {
      Range range = new Range(beforeLastLine, beforeLastLine + 1, afterLastLine + 1, afterLastLine + 1);
      return appendRange(ranges, range);
    }
    else {
      Range range = new Range(beforeLastLine + 1, beforeLastLine + 1, afterLastLine, afterLastLine + 1);
      return appendRange(ranges, range);
    }
  }
@NotNull
  private static List<Range> compareLines(@NotNull List<String> beforeLines,
                                          @NotNull List<String> afterLines,
                                          boolean beforeNoNewlineAtEOF,
                                          boolean afterNoNewlineAtEOF)