// retoor <retoor@molodetz.nl>
#include "r_diff.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define COLOR_RED "\x1b[31m"
#define COLOR_GREEN "\x1b[32m"
#define COLOR_CYAN "\x1b[36m"
#define COLOR_RESET "\x1b[0m"
#define COLOR_DIM "\x1b[2m"
#define COLOR_BG_RED "\x1b[41;37m"
#define COLOR_BG_GREEN "\x1b[42;30m"
typedef struct {
char **lines;
size_t count;
} line_set_t;
static line_set_t split_lines(const char *str) {
line_set_t set = {NULL, 0};
if (!str || !*str) return set;
char *copy = strdup(str);
char *p = copy;
char *line_start = copy;
while (*p) {
if (*p == '\n') {
*p = '\0';
set.lines = realloc(set.lines, sizeof(char *) * (set.count + 1));
set.lines[set.count++] = strdup(line_start);
line_start = p + 1;
}
p++;
}
// Handle last line if no trailing newline
if (*line_start) {
set.lines = realloc(set.lines, sizeof(char *) * (set.count + 1));
set.lines[set.count++] = strdup(line_start);
}
free(copy);
return set;
}
static void free_line_set(line_set_t set) {
for (size_t i = 0; i < set.count; i++) free(set.lines[i]);
free(set.lines);
}
static void print_truncated(const char *str, int width, const char *color) {
if (width <= 0) return;
int len = (int)strlen(str);
if (color) printf("%s", color);
if (len > width && width > 3) {
char *temp = strndup(str, (size_t)width - 3);
printf("%s...", temp);
free(temp);
} else {
printf("%-*.*s", width, width, str);
}
if (color) printf("%s", COLOR_RESET);
}
void r_diff_print(const char *path, const char *old_content, const char *new_content) {
line_set_t old_set = split_lines(old_content);
line_set_t new_set = split_lines(new_content);
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
int term_width = w.ws_col > 0 ? w.ws_col : 120;
if (term_width < 40) term_width = 40; // Minimum usable width
int col_width = (term_width - 15) / 2;
if (col_width < 5) col_width = 5;
printf("\n%s %s CHANGES: %s %s\n", COLOR_CYAN, COLOR_DIM, path, COLOR_RESET);
printf("%4s %-*s | %4s %-*s\n", "LINE", col_width, " OLD", "LINE", col_width, " NEW");
printf("%.*s\n", term_width, "--------------------------------------------------------------------------------------------------------------------------------------------");
size_t o = 0, n = 0;
while (o < old_set.count || n < new_set.count) {
bool match = false;
if (o < old_set.count && n < new_set.count) {
if (strcmp(old_set.lines[o], new_set.lines[n]) == 0) {
printf("%4zu ", o + 1);
print_truncated(old_set.lines[o], col_width - 2, NULL);
printf(" | %4zu ", n + 1);
print_truncated(new_set.lines[n], col_width - 2, NULL);
printf("\n");
o++; n++;
match = true;
}
}
if (!match) {
bool found_o_later = false;
if (o < old_set.count) {
for (size_t i = n + 1; i < new_set.count && i < n + 5; i++) {
if (strcmp(old_set.lines[o], new_set.lines[i]) == 0) {
found_o_later = true;
break;
}
}
}
if (found_o_later) {
// Line added on NEW side
printf("%4s ", "");
print_truncated("", col_width - 2, NULL);
printf(" | %4zu %s+%s ", n + 1, COLOR_GREEN, COLOR_RESET);
print_truncated(new_set.lines[n], col_width - 2, COLOR_GREEN);
printf("\n");
n++;
} else if (o < old_set.count) {
// Line removed on OLD side
printf("%4zu %s-%s ", o + 1, COLOR_RED, COLOR_RESET);
print_truncated(old_set.lines[o], col_width - 2, COLOR_RED);
printf(" | %4s ", "");
print_truncated("", col_width - 2, NULL);
printf("\n");
o++;
} else if (n < new_set.count) {
// Line added on NEW side
printf("%4s ", "");
print_truncated("", col_width - 2, NULL);
printf(" | %4zu %s+%s ", n + 1, COLOR_GREEN, COLOR_RESET);
print_truncated(new_set.lines[n], col_width - 2, COLOR_GREEN);
printf("\n");
n++;
}
}
fflush(stdout);
usleep(30000); // 30ms delay for streaming effect
}
printf("\n");
fflush(stdout);
free_line_set(old_set);
free_line_set(new_set);
}