diffsToHtml function

String diffsToHtml(
  1. List<Diff> diffs, {
  2. required String title,
  3. required String subtitle,
})

Implementation

String diffsToHtml(
  List<dmp.Diff> diffs, {
  required String title,
  required String subtitle,
}) {
  final buffer = StringBuffer();

  // Add HTML header with enhanced styling
  buffer.write('''
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>$title</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      margin: 0;
      padding: 20px;
      background-color: #f5f5f5;
      color: #333;
    }

    .diff-container {
      font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
      white-space: pre-wrap;
      line-height: 1.6;
      padding: 16px;
      background-color: #ffffff;
      border-radius: 6px;
      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
      overflow-x: auto;
      font-size: 14px;
      margin-bottom: 20px;
    }

    .diff-header {
      margin-bottom: 15px;
      font-size: 16px;
      font-weight: 600;
      border-bottom: 1px solid #e1e4e8;
      padding-bottom: 10px;
    }

    .diff-stats {
      display: flex;
      margin-bottom: 15px;
      font-size: 13px;
    }

    .diff-stats div {
      margin-right: 20px;
      display: flex;
      align-items: center;
    }

    .diff-stats-icon {
      display: inline-block;
      width: 8px;
      height: 8px;
      border-radius: 50%;
      margin-right: 6px;
    }

    .diff-addition {
      background-color: #dafbe1;
      color: #22863a;
      padding: 1px 2px;
      border-radius: 2px;
    }

    .diff-deletion {
      background-color: #ffeef0;
      color: #cb2431;
      padding: 1px 2px;
      border-radius: 2px;
    }

    .line-number {
      user-select: none;
      text-align: right;
      color: #999;
      padding-right: 10px;
      min-width: 40px;
      display: inline-block;
      border-right: 1px solid #e1e4e8;
      margin-right: 10px;
    }

    .diff-line {
      display: block;
      margin: 0;
      padding: 0 0 0 10px;
      border-left: 2px solid transparent;
    }

    .diff-line.addition {
      background-color: #f0fff4;
      border-left: 2px solid #34d058;
    }

    .diff-line.deletion {
      background-color: #ffeef0;
      border-left: 2px solid #d73a49;
    }

    .diff-line.change {
      background-color: #fff5b1;
      border-left: 2px solid #f6a623;
    }

    @media (prefers-color-scheme: dark) {
      body {
        background-color: #1e1e1e;
        color: #d4d4d4;
      }
      .diff-container {
        background-color: #252526;
        box-shadow: 0 1px 3px rgba(0,0,0,0.3);
      }
      .diff-addition {
        background-color: #133929;
        color: #56d364;
      }
      .diff-deletion {
        background-color: #331c1e;
        color: #f85149;
      }
      .diff-line.addition {
        background-color: #0d1a12;
        border-left-color: #238636;
      }
      .diff-line.deletion {
        background-color: #291a1d;
        border-left-color: #da3633;
      }
      .diff-line.change {
        background-color: #3c3f00;
        border-left-color: #f6a623;
      }
      .line-number {
        color: #666;
        border-right-color: #333;
      }
      .diff-header {
        border-bottom-color: #333;
      }
    }
  </style>
</head>
<body>
  <div class="diff-container">
    <div class="diff-header"><p>$title</p><small>$subtitle</small></div>
''');

  // Count number of lines and operations
  int lineNumber = 1;
  int additions = 0;
  int deletions = 0;

  // Group diffs by lines
  final lineGroups = <List<dmp.Diff>>[];
  List<dmp.Diff> currentLine = [];

  for (final diff in diffs) {
    final lines = diff.text.split('\n');

    for (int i = 0; i < lines.length; i++) {
      // If not the first line in split result, we need to start a new line group
      if (i > 0) {
        lineGroups.add([...currentLine]);
        currentLine = [];
      }

      // Add the current line segment with appropriate operation
      if (lines[i].isNotEmpty || i < lines.length - 1) {
        final textContent = i == lines.length - 1 ? lines[i] : '${lines[i]}\n';
        currentLine.add(dmp.Diff(diff.operation, textContent));

        // Count operations
        if (diff.operation == dmp.DIFF_INSERT) additions++;
        if (diff.operation == dmp.DIFF_DELETE) deletions++;
      }
    }
  }

  // Add the last line if it exists
  if (currentLine.isNotEmpty) {
    lineGroups.add(currentLine);
  }

  // Update stats display with real numbers
  buffer.write('''
    <div class="diff-stats">
      <div><span class="diff-stats-icon" style="background-color: #34d058;"></span>$additions Additions</div>
      <div><span class="diff-stats-icon" style="background-color: #d73a49;"></span>$deletions Deletions</div>
    </div>
  ''');

  // Process each line group with line numbers
  for (final lineGroup in lineGroups) {
    bool isAddition =
        lineGroup.any((diff) => diff.operation == dmp.DIFF_INSERT);
    bool isDeletion =
        lineGroup.any((diff) => diff.operation == dmp.DIFF_DELETE);

    String lineClass = "";
    if (isAddition && !isDeletion) lineClass = "addition";
    if (isDeletion && !isAddition) lineClass = "deletion";
    if (isDeletion && isAddition) lineClass = "change";

    buffer.write('<div class="diff-line $lineClass">');
    buffer.write('<span class="line-number">${lineNumber++}</span>');

    // Process each diff segment in the line
    for (final diff in lineGroup) {
      final htmlEscapedText = htmlEscape(diff.text);

      switch (diff.operation) {
        case dmp.DIFF_INSERT:
          buffer.write('<span class="diff-addition">$htmlEscapedText</span>');
          break;
        case dmp.DIFF_DELETE:
          buffer.write('<span class="diff-deletion">$htmlEscapedText</span>');
          break;
        case dmp.DIFF_EQUAL:
          buffer.write(htmlEscapedText);
          break;
      }
    }

    buffer.write('</div>');
  }

  // Add HTML footer
  buffer.write('''
  </div>
</body>
</html>
''');

  return buffer.toString();
}