From 9d702b93d7de0edb09c5c6efd94fefee599acb7b Mon Sep 17 00:00:00 2001 From: marcusDenslow Date: Fri, 6 Jun 2025 20:38:26 +0200 Subject: [PATCH] just checking if the file clearing works THIS IS A TEST COMMIT --- line_reader.o | Bin 53128 -> 53128 bytes ncurses_diff_viewer.c | 3233 +++++++++++++++++++++-------------------- ncurses_diff_viewer.o | Bin 63016 -> 62920 bytes shell | Bin 425352 -> 425008 bytes 4 files changed, 1696 insertions(+), 1537 deletions(-) diff --git a/line_reader.o b/line_reader.o index 39249f515d53bf5e153c9b12bb0aa71249ecf795..f36bd1adc450a00c982a92f422791c160c4bb391 100644 GIT binary patch delta 4778 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 E1f-rhZ~y=R delta 4778 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 diff --git a/ncurses_diff_viewer.c b/ncurses_diff_viewer.c index 72157c5..eddb766 100644 --- a/ncurses_diff_viewer.c +++ b/ncurses_diff_viewer.c @@ -4,1718 +4,1877 @@ */ #include "ncurses_diff_viewer.h" +#include #include #include #include -#include #include #include -#include +#include /** * Initialize the ncurses diff viewer */ int init_ncurses_diff_viewer(NCursesDiffViewer *viewer) { - if (!viewer) return 0; - - memset(viewer, 0, sizeof(NCursesDiffViewer)); - viewer->selected_file = 0; - viewer->file_scroll_offset = 0; - viewer->current_mode = NCURSES_MODE_FILE_LIST; - viewer->sync_status = SYNC_STATUS_IDLE; - viewer->spinner_frame = 0; - viewer->last_sync_time = time(NULL); - viewer->animation_frame = 0; - viewer->text_char_count = 0; - - // Set locale for Unicode support - setlocale(LC_ALL, ""); - - // Initialize ncurses - initscr(); - cbreak(); - noecho(); - keypad(stdscr, TRUE); - nodelay(stdscr, TRUE); // Make getch() non-blocking - curs_set(0); // Hide cursor to prevent flickering - - // Enable colors - if (has_colors()) { - start_color(); - init_pair(1, COLOR_GREEN, COLOR_BLACK); // Additions - init_pair(2, COLOR_RED, COLOR_BLACK); // Deletions - init_pair(3, COLOR_CYAN, COLOR_BLACK); // Headers - init_pair(4, COLOR_YELLOW, COLOR_BLACK); // Selected - init_pair(5, COLOR_WHITE, COLOR_BLUE); // Highlighted selection - } - - getmaxyx(stdscr, viewer->terminal_height, viewer->terminal_width); - viewer->file_panel_width = viewer->terminal_width * 0.4; // 40% of screen width - viewer->status_bar_height = viewer->terminal_height * 0.05; // 5% of screen height - if (viewer->status_bar_height < 1) viewer->status_bar_height = 1; // Minimum 1 line - - int available_height = viewer->terminal_height - 1 - viewer->status_bar_height; // Subtract top bar and status bar - viewer->file_panel_height = available_height * 0.6; // 60% of available height - viewer->commit_panel_height = available_height - viewer->file_panel_height - 1; // Rest of height - - // Position status bar right after the main content - int status_bar_y = 1 + available_height; - - // Create four windows - viewer->file_list_win = newwin(viewer->file_panel_height, viewer->file_panel_width, 1, 0); - viewer->commit_list_win = newwin(viewer->commit_panel_height, viewer->file_panel_width, - 1 + viewer->file_panel_height + 1, 0); - viewer->file_content_win = newwin(available_height, - viewer->terminal_width - viewer->file_panel_width - 1, - 1, viewer->file_panel_width + 1); - viewer->status_bar_win = newwin(viewer->status_bar_height, viewer->terminal_width, - status_bar_y, 0); - - if (!viewer->file_list_win || !viewer->file_content_win || !viewer->commit_list_win || !viewer->status_bar_win) { - cleanup_ncurses_diff_viewer(viewer); - return 0; - } - - return 1; + if (!viewer) + return 0; + + memset(viewer, 0, sizeof(NCursesDiffViewer)); + viewer->selected_file = 0; + viewer->file_scroll_offset = 0; + viewer->current_mode = NCURSES_MODE_FILE_LIST; + viewer->sync_status = SYNC_STATUS_IDLE; + viewer->spinner_frame = 0; + viewer->last_sync_time = time(NULL); + viewer->animation_frame = 0; + viewer->text_char_count = 0; + + // Set locale for Unicode support + setlocale(LC_ALL, ""); + + // Initialize ncurses + initscr(); + cbreak(); + noecho(); + keypad(stdscr, TRUE); + nodelay(stdscr, TRUE); // Make getch() non-blocking + curs_set(0); // Hide cursor to prevent flickering + + // Enable colors + if (has_colors()) { + start_color(); + init_pair(1, COLOR_GREEN, COLOR_BLACK); // Additions + init_pair(2, COLOR_RED, COLOR_BLACK); // Deletions + init_pair(3, COLOR_CYAN, COLOR_BLACK); // Headers + init_pair(4, COLOR_YELLOW, COLOR_BLACK); // Selected + init_pair(5, COLOR_WHITE, COLOR_BLUE); // Highlighted selection + } + + getmaxyx(stdscr, viewer->terminal_height, viewer->terminal_width); + viewer->file_panel_width = + viewer->terminal_width * 0.4; // 40% of screen width + viewer->status_bar_height = + viewer->terminal_height * 0.05; // 5% of screen height + if (viewer->status_bar_height < 1) + viewer->status_bar_height = 1; // Minimum 1 line + + int available_height = + viewer->terminal_height - 1 - + viewer->status_bar_height; // Subtract top bar and status bar + viewer->file_panel_height = available_height * 0.6; // 60% of available height + viewer->commit_panel_height = + available_height - viewer->file_panel_height - 1; // Rest of height + + // Position status bar right after the main content + int status_bar_y = 1 + available_height; + + // Create four windows + viewer->file_list_win = + newwin(viewer->file_panel_height, viewer->file_panel_width, 1, 0); + viewer->commit_list_win = + newwin(viewer->commit_panel_height, viewer->file_panel_width, + 1 + viewer->file_panel_height + 1, 0); + viewer->file_content_win = newwin( + available_height, viewer->terminal_width - viewer->file_panel_width - 1, + 1, viewer->file_panel_width + 1); + viewer->status_bar_win = newwin(viewer->status_bar_height, + viewer->terminal_width, status_bar_y, 0); + + if (!viewer->file_list_win || !viewer->file_content_win || + !viewer->commit_list_win || !viewer->status_bar_win) { + cleanup_ncurses_diff_viewer(viewer); + return 0; + } + + return 1; } /** * Get list of changed files from git */ int get_ncurses_changed_files(NCursesDiffViewer *viewer) { - if (!viewer) return 0; - - FILE *fp = popen("git status --porcelain 2>/dev/null", "r"); - if (!fp) return 0; - - viewer->file_count = 0; - char line[512]; - - while (fgets(line, sizeof(line), fp) != NULL && viewer->file_count < MAX_FILES) { - // Remove newline - char *newline = strchr(line, '\n'); - if (newline) *newline = '\0'; - - if (strlen(line) < 3) continue; - - // Parse git status format: "XY filename" - char status = line[0]; - if (status == ' ') status = line[1]; // Check second column if first is space - - // Skip the status characters and space - char *filename = line + 3; - - // Store file info - viewer->files[viewer->file_count].status = status; - viewer->files[viewer->file_count].marked_for_commit = 0; // Not marked by default - strncpy(viewer->files[viewer->file_count].filename, filename, MAX_FILENAME_LEN - 1); - viewer->files[viewer->file_count].filename[MAX_FILENAME_LEN - 1] = '\0'; - - viewer->file_count++; - } - - pclose(fp); - return viewer->file_count; + if (!viewer) + return 0; + + FILE *fp = popen("git status --porcelain 2>/dev/null", "r"); + if (!fp) + return 0; + + viewer->file_count = 0; + char line[512]; + + while (fgets(line, sizeof(line), fp) != NULL && + viewer->file_count < MAX_FILES) { + // Remove newline + char *newline = strchr(line, '\n'); + if (newline) + *newline = '\0'; + + if (strlen(line) < 3) + continue; + + // Parse git status format: "XY filename" + char status = line[0]; + if (status == ' ') + status = line[1]; // Check second column if first is space + + // Skip the status characters and space + char *filename = line + 3; + + // Store file info + viewer->files[viewer->file_count].status = status; + viewer->files[viewer->file_count].marked_for_commit = + 0; // Not marked by default + strncpy(viewer->files[viewer->file_count].filename, filename, + MAX_FILENAME_LEN - 1); + viewer->files[viewer->file_count].filename[MAX_FILENAME_LEN - 1] = '\0'; + + viewer->file_count++; + } + + pclose(fp); + return viewer->file_count; } /** * Create temporary file with current working version */ int create_temp_file_with_changes(const char *filename, char *temp_path) { - snprintf(temp_path, 256, "/tmp/shell_diff_current_%d", getpid()); - - char cmd[1024]; - snprintf(cmd, sizeof(cmd), "cp \"%s\" \"%s\" 2>/dev/null", filename, temp_path); - - return (system(cmd) == 0); + snprintf(temp_path, 256, "/tmp/shell_diff_current_%d", getpid()); + + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "cp \"%s\" \"%s\" 2>/dev/null", filename, + temp_path); + + return (system(cmd) == 0); } /** * Create temporary file with git HEAD version */ int create_temp_file_git_version(const char *filename, char *temp_path) { - snprintf(temp_path, 256, "/tmp/shell_diff_git_%d", getpid()); - - char cmd[1024]; - snprintf(cmd, sizeof(cmd), "git show HEAD:\"%s\" > \"%s\" 2>/dev/null", filename, temp_path); - - return (system(cmd) == 0); + snprintf(temp_path, 256, "/tmp/shell_diff_git_%d", getpid()); + + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "git show HEAD:\"%s\" > \"%s\" 2>/dev/null", + filename, temp_path); + + return (system(cmd) == 0); } /** * Check if a file is a new untracked file */ int is_ncurses_new_file(const char *filename) { - char cmd[1024]; - snprintf(cmd, sizeof(cmd), "git ls-files --error-unmatch \"%s\" 2>/dev/null", filename); - - FILE *fp = popen(cmd, "r"); - if (!fp) return 1; // Assume new if we can't check - - char output[256]; - int is_tracked = (fgets(output, sizeof(output), fp) != NULL); - pclose(fp); - - return !is_tracked; // Return 1 if not tracked (new file) + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "git ls-files --error-unmatch \"%s\" 2>/dev/null", + filename); + + FILE *fp = popen(cmd, "r"); + if (!fp) + return 1; // Assume new if we can't check + + char output[256]; + int is_tracked = (fgets(output, sizeof(output), fp) != NULL); + pclose(fp); + + return !is_tracked; // Return 1 if not tracked (new file) } /** * Load diff context with highlighting (like lazygit) */ int load_full_file_with_diff(NCursesDiffViewer *viewer, const char *filename) { - if (!viewer || !filename) return 0; - - viewer->file_line_count = 0; - viewer->file_scroll_offset = 0; - - // Check if this is a new file - if (is_ncurses_new_file(filename)) { - // For new files, show first 50 lines as additions - FILE *fp = fopen(filename, "r"); - if (!fp) return 0; - - char line[1024]; - int line_count = 0; - while (fgets(line, sizeof(line), fp) != NULL && - viewer->file_line_count < MAX_FULL_FILE_LINES && - line_count < 50) { - // Remove newline - char *newline_pos = strchr(line, '\n'); - if (newline_pos) *newline_pos = '\0'; - - NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; - snprintf(file_line->line, sizeof(file_line->line), "+%s", line); - file_line->type = '+'; - file_line->is_diff_line = 1; - - viewer->file_line_count++; - line_count++; - } - - fclose(fp); - return viewer->file_line_count; + if (!viewer || !filename) + return 0; + + viewer->file_line_count = 0; + viewer->file_scroll_offset = 0; + + // Check if this is a new file + if (is_ncurses_new_file(filename)) { + // For new files, show first 50 lines as additions + FILE *fp = fopen(filename, "r"); + if (!fp) + return 0; + + char line[1024]; + int line_count = 0; + while (fgets(line, sizeof(line), fp) != NULL && + viewer->file_line_count < MAX_FULL_FILE_LINES && line_count < 50) { + // Remove newline + char *newline_pos = strchr(line, '\n'); + if (newline_pos) + *newline_pos = '\0'; + + NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; + snprintf(file_line->line, sizeof(file_line->line), "+%s", line); + file_line->type = '+'; + file_line->is_diff_line = 1; + + viewer->file_line_count++; + line_count++; } - - // Use git diff to get only the changed context (like lazygit) - char cmd[1024]; - snprintf(cmd, sizeof(cmd), "git diff HEAD \"%s\" 2>/dev/null", filename); - - FILE *diff_fp = popen(cmd, "r"); - if (!diff_fp) return 0; - - char diff_line[1024]; - int found_changes = 0; - - while (fgets(diff_line, sizeof(diff_line), diff_fp) != NULL && - viewer->file_line_count < MAX_FULL_FILE_LINES) { - // Remove newline - char *newline_pos = strchr(diff_line, '\n'); - if (newline_pos) *newline_pos = '\0'; - - // Skip file headers - if (strncmp(diff_line, "diff --git", 10) == 0 || - strncmp(diff_line, "index ", 6) == 0 || - strncmp(diff_line, "--- ", 4) == 0 || - strncmp(diff_line, "+++ ", 4) == 0) { - continue; - } - - // Process hunk headers and content - if (diff_line[0] == '@' && diff_line[1] == '@') { - // Hunk header - NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; - strncpy(file_line->line, diff_line, sizeof(file_line->line) - 1); - file_line->line[sizeof(file_line->line) - 1] = '\0'; - file_line->type = '@'; - file_line->is_diff_line = 0; - viewer->file_line_count++; - found_changes = 1; - } else if (diff_line[0] == '+') { - // Added line - NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; - strncpy(file_line->line, diff_line, sizeof(file_line->line) - 1); - file_line->line[sizeof(file_line->line) - 1] = '\0'; - file_line->type = '+'; - file_line->is_diff_line = 1; - viewer->file_line_count++; - found_changes = 1; - } else if (diff_line[0] == '-') { - // Removed line - NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; - strncpy(file_line->line, diff_line, sizeof(file_line->line) - 1); - file_line->line[sizeof(file_line->line) - 1] = '\0'; - file_line->type = '-'; - file_line->is_diff_line = 1; - viewer->file_line_count++; - found_changes = 1; - } else if (diff_line[0] == ' ') { - // Context line (unchanged) - NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; - strncpy(file_line->line, diff_line, sizeof(file_line->line) - 1); - file_line->line[sizeof(file_line->line) - 1] = '\0'; - file_line->type = ' '; - file_line->is_diff_line = 0; - viewer->file_line_count++; - } - } - - pclose(diff_fp); - - // If no changes were found, show a message - if (!found_changes) { - NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; - strncpy(file_line->line, "No changes in this file", sizeof(file_line->line) - 1); - file_line->line[sizeof(file_line->line) - 1] = '\0'; - file_line->type = ' '; - file_line->is_diff_line = 0; - viewer->file_line_count++; - } - + + fclose(fp); return viewer->file_line_count; + } + + // Use git diff to get only the changed context (like lazygit) + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "git diff HEAD \"%s\" 2>/dev/null", filename); + + FILE *diff_fp = popen(cmd, "r"); + if (!diff_fp) + return 0; + + char diff_line[1024]; + int found_changes = 0; + + while (fgets(diff_line, sizeof(diff_line), diff_fp) != NULL && + viewer->file_line_count < MAX_FULL_FILE_LINES) { + // Remove newline + char *newline_pos = strchr(diff_line, '\n'); + if (newline_pos) + *newline_pos = '\0'; + + // Skip file headers + if (strncmp(diff_line, "diff --git", 10) == 0 || + strncmp(diff_line, "index ", 6) == 0 || + strncmp(diff_line, "--- ", 4) == 0 || + strncmp(diff_line, "+++ ", 4) == 0) { + continue; + } + + // Process hunk headers and content + if (diff_line[0] == '@' && diff_line[1] == '@') { + // Hunk header + NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; + strncpy(file_line->line, diff_line, sizeof(file_line->line) - 1); + file_line->line[sizeof(file_line->line) - 1] = '\0'; + file_line->type = '@'; + file_line->is_diff_line = 0; + viewer->file_line_count++; + found_changes = 1; + } else if (diff_line[0] == '+') { + // Added line + NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; + strncpy(file_line->line, diff_line, sizeof(file_line->line) - 1); + file_line->line[sizeof(file_line->line) - 1] = '\0'; + file_line->type = '+'; + file_line->is_diff_line = 1; + viewer->file_line_count++; + found_changes = 1; + } else if (diff_line[0] == '-') { + // Removed line + NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; + strncpy(file_line->line, diff_line, sizeof(file_line->line) - 1); + file_line->line[sizeof(file_line->line) - 1] = '\0'; + file_line->type = '-'; + file_line->is_diff_line = 1; + viewer->file_line_count++; + found_changes = 1; + } else if (diff_line[0] == ' ') { + // Context line (unchanged) + NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; + strncpy(file_line->line, diff_line, sizeof(file_line->line) - 1); + file_line->line[sizeof(file_line->line) - 1] = '\0'; + file_line->type = ' '; + file_line->is_diff_line = 0; + viewer->file_line_count++; + } + } + + pclose(diff_fp); + + // If no changes were found, show a message + if (!found_changes) { + NCursesFileLine *file_line = &viewer->file_lines[viewer->file_line_count]; + strncpy(file_line->line, "No changes in this file", + sizeof(file_line->line) - 1); + file_line->line[sizeof(file_line->line) - 1] = '\0'; + file_line->type = ' '; + file_line->is_diff_line = 0; + viewer->file_line_count++; + } + + return viewer->file_line_count; } /** * Draw a box with rounded corners */ void draw_rounded_box(WINDOW *win) { - if (!win) return; - - int height, width; - getmaxyx(win, height, width); - - // Draw horizontal lines - for (int x = 1; x < width - 1; x++) { - mvwaddch(win, 0, x, ACS_HLINE); - mvwaddch(win, height - 1, x, ACS_HLINE); - } - - // Draw vertical lines - for (int y = 1; y < height - 1; y++) { - mvwaddch(win, y, 0, ACS_VLINE); - mvwaddch(win, y, width - 1, ACS_VLINE); - } - - // Draw rounded corners - mvwaddch(win, 0, 0, ACS_ULCORNER); - mvwaddch(win, 0, width - 1, ACS_URCORNER); - mvwaddch(win, height - 1, 0, ACS_LLCORNER); - mvwaddch(win, height - 1, width - 1, ACS_LRCORNER); + if (!win) + return; + + int height, width; + getmaxyx(win, height, width); + + // Draw horizontal lines + for (int x = 1; x < width - 1; x++) { + mvwaddch(win, 0, x, ACS_HLINE); + mvwaddch(win, height - 1, x, ACS_HLINE); + } + + // Draw vertical lines + for (int y = 1; y < height - 1; y++) { + mvwaddch(win, y, 0, ACS_VLINE); + mvwaddch(win, y, width - 1, ACS_VLINE); + } + + // Draw rounded corners + mvwaddch(win, 0, 0, ACS_ULCORNER); + mvwaddch(win, 0, width - 1, ACS_URCORNER); + mvwaddch(win, height - 1, 0, ACS_LLCORNER); + mvwaddch(win, height - 1, width - 1, ACS_LRCORNER); } /** * Get commit history */ int get_commit_history(NCursesDiffViewer *viewer) { - if (!viewer) return 0; - - FILE *fp = popen("git log --oneline -20 --format=\"%h|%an|%s\" 2>/dev/null", "r"); - if (!fp) return 0; - - viewer->commit_count = 0; - char line[512]; - - // Get list of unpushed commits first - char unpushed_hashes[MAX_COMMITS][16]; - int unpushed_count = 0; - - FILE *unpushed_fp = popen("git log origin/HEAD..HEAD --format=\"%h\" 2>/dev/null", "r"); + if (!viewer) + return 0; + + FILE *fp = + popen("git log --oneline -20 --format=\"%h|%an|%s\" 2>/dev/null", "r"); + if (!fp) + return 0; + + viewer->commit_count = 0; + char line[512]; + + // Get list of unpushed commits first + char unpushed_hashes[MAX_COMMITS][16]; + int unpushed_count = 0; + + FILE *unpushed_fp = + popen("git log origin/HEAD..HEAD --format=\"%h\" 2>/dev/null", "r"); + if (unpushed_fp) { + while (fgets(line, sizeof(line), unpushed_fp) != NULL && + unpushed_count < MAX_COMMITS) { + char *newline = strchr(line, '\n'); + if (newline) + *newline = '\0'; + strncpy(unpushed_hashes[unpushed_count], line, + sizeof(unpushed_hashes[unpushed_count]) - 1); + unpushed_hashes[unpushed_count] + [sizeof(unpushed_hashes[unpushed_count]) - 1] = '\0'; + unpushed_count++; + } + pclose(unpushed_fp); + } + + // If origin/HEAD doesn't exist, try origin/main and origin/master + if (unpushed_count == 0) { + unpushed_fp = + popen("git log origin/main..HEAD --format=\"%h\" 2>/dev/null", "r"); if (unpushed_fp) { - while (fgets(line, sizeof(line), unpushed_fp) != NULL && unpushed_count < MAX_COMMITS) { - char *newline = strchr(line, '\n'); - if (newline) *newline = '\0'; - strncpy(unpushed_hashes[unpushed_count], line, sizeof(unpushed_hashes[unpushed_count]) - 1); - unpushed_hashes[unpushed_count][sizeof(unpushed_hashes[unpushed_count]) - 1] = '\0'; - unpushed_count++; - } - pclose(unpushed_fp); - } - - // If origin/HEAD doesn't exist, try origin/main and origin/master - if (unpushed_count == 0) { - unpushed_fp = popen("git log origin/main..HEAD --format=\"%h\" 2>/dev/null", "r"); - if (unpushed_fp) { - while (fgets(line, sizeof(line), unpushed_fp) != NULL && unpushed_count < MAX_COMMITS) { - char *newline = strchr(line, '\n'); - if (newline) *newline = '\0'; - strncpy(unpushed_hashes[unpushed_count], line, sizeof(unpushed_hashes[unpushed_count]) - 1); - unpushed_hashes[unpushed_count][sizeof(unpushed_hashes[unpushed_count]) - 1] = '\0'; - unpushed_count++; - } - pclose(unpushed_fp); - } - } - - if (unpushed_count == 0) { - unpushed_fp = popen("git log origin/master..HEAD --format=\"%h\" 2>/dev/null", "r"); - if (unpushed_fp) { - while (fgets(line, sizeof(line), unpushed_fp) != NULL && unpushed_count < MAX_COMMITS) { - char *newline = strchr(line, '\n'); - if (newline) *newline = '\0'; - strncpy(unpushed_hashes[unpushed_count], line, sizeof(unpushed_hashes[unpushed_count]) - 1); - unpushed_hashes[unpushed_count][sizeof(unpushed_hashes[unpushed_count]) - 1] = '\0'; - unpushed_count++; - } - pclose(unpushed_fp); - } - } - - while (fgets(line, sizeof(line), fp) != NULL && viewer->commit_count < MAX_COMMITS) { - // Remove newline + while (fgets(line, sizeof(line), unpushed_fp) != NULL && + unpushed_count < MAX_COMMITS) { char *newline = strchr(line, '\n'); - if (newline) *newline = '\0'; - - // Parse format: hash|author|title - char *hash = strtok(line, "|"); - char *author = strtok(NULL, "|"); - char *title = strtok(NULL, "|"); - - if (hash && author && title) { - // Store commit hash (first 7 chars) - strncpy(viewer->commits[viewer->commit_count].hash, hash, sizeof(viewer->commits[viewer->commit_count].hash) - 1); - viewer->commits[viewer->commit_count].hash[sizeof(viewer->commits[viewer->commit_count].hash) - 1] = '\0'; - - // Store first two letters of author name - viewer->commits[viewer->commit_count].author_initials[0] = author[0] ? author[0] : '?'; - viewer->commits[viewer->commit_count].author_initials[1] = author[1] ? author[1] : '?'; - viewer->commits[viewer->commit_count].author_initials[2] = '\0'; - - // Store title - strncpy(viewer->commits[viewer->commit_count].title, title, MAX_COMMIT_TITLE_LEN - 1); - viewer->commits[viewer->commit_count].title[MAX_COMMIT_TITLE_LEN - 1] = '\0'; - - // Check if this commit is in the unpushed list - viewer->commits[viewer->commit_count].is_pushed = 1; // Default to pushed - for (int i = 0; i < unpushed_count; i++) { - if (strcmp(viewer->commits[viewer->commit_count].hash, unpushed_hashes[i]) == 0) { - viewer->commits[viewer->commit_count].is_pushed = 0; - break; - } - } - - viewer->commit_count++; - } + if (newline) + *newline = '\0'; + strncpy(unpushed_hashes[unpushed_count], line, + sizeof(unpushed_hashes[unpushed_count]) - 1); + unpushed_hashes[unpushed_count] + [sizeof(unpushed_hashes[unpushed_count]) - 1] = '\0'; + unpushed_count++; + } + pclose(unpushed_fp); } - - pclose(fp); - return viewer->commit_count; + } + + if (unpushed_count == 0) { + unpushed_fp = + popen("git log origin/master..HEAD --format=\"%h\" 2>/dev/null", "r"); + if (unpushed_fp) { + while (fgets(line, sizeof(line), unpushed_fp) != NULL && + unpushed_count < MAX_COMMITS) { + char *newline = strchr(line, '\n'); + if (newline) + *newline = '\0'; + strncpy(unpushed_hashes[unpushed_count], line, + sizeof(unpushed_hashes[unpushed_count]) - 1); + unpushed_hashes[unpushed_count] + [sizeof(unpushed_hashes[unpushed_count]) - 1] = '\0'; + unpushed_count++; + } + pclose(unpushed_fp); + } + } + + while (fgets(line, sizeof(line), fp) != NULL && + viewer->commit_count < MAX_COMMITS) { + // Remove newline + char *newline = strchr(line, '\n'); + if (newline) + *newline = '\0'; + + // Parse format: hash|author|title + char *hash = strtok(line, "|"); + char *author = strtok(NULL, "|"); + char *title = strtok(NULL, "|"); + + if (hash && author && title) { + // Store commit hash (first 7 chars) + strncpy(viewer->commits[viewer->commit_count].hash, hash, + sizeof(viewer->commits[viewer->commit_count].hash) - 1); + viewer->commits[viewer->commit_count] + .hash[sizeof(viewer->commits[viewer->commit_count].hash) - 1] = '\0'; + + // Store first two letters of author name + viewer->commits[viewer->commit_count].author_initials[0] = + author[0] ? author[0] : '?'; + viewer->commits[viewer->commit_count].author_initials[1] = + author[1] ? author[1] : '?'; + viewer->commits[viewer->commit_count].author_initials[2] = '\0'; + + // Store title + strncpy(viewer->commits[viewer->commit_count].title, title, + MAX_COMMIT_TITLE_LEN - 1); + viewer->commits[viewer->commit_count].title[MAX_COMMIT_TITLE_LEN - 1] = + '\0'; + + // Check if this commit is in the unpushed list + viewer->commits[viewer->commit_count].is_pushed = 1; // Default to pushed + for (int i = 0; i < unpushed_count; i++) { + if (strcmp(viewer->commits[viewer->commit_count].hash, + unpushed_hashes[i]) == 0) { + viewer->commits[viewer->commit_count].is_pushed = 0; + break; + } + } + + viewer->commit_count++; + } + } + + pclose(fp); + return viewer->commit_count; } /** * Toggle file marking for commit */ void toggle_file_mark(NCursesDiffViewer *viewer, int file_index) { - if (!viewer || file_index < 0 || file_index >= viewer->file_count) return; - - viewer->files[file_index].marked_for_commit = !viewer->files[file_index].marked_for_commit; + if (!viewer || file_index < 0 || file_index >= viewer->file_count) + return; + + viewer->files[file_index].marked_for_commit = + !viewer->files[file_index].marked_for_commit; } /** * Mark all files for commit (or unmark if all are already marked) */ void mark_all_files(NCursesDiffViewer *viewer) { - if (!viewer) return; - - // Check if all files are already marked - int all_marked = 1; - for (int i = 0; i < viewer->file_count; i++) { - if (!viewer->files[i].marked_for_commit) { - all_marked = 0; - break; - } - } - - // If all are marked, unmark all. Otherwise, mark all - for (int i = 0; i < viewer->file_count; i++) { - viewer->files[i].marked_for_commit = all_marked ? 0 : 1; + if (!viewer) + return; + + // Check if all files are already marked + int all_marked = 1; + for (int i = 0; i < viewer->file_count; i++) { + if (!viewer->files[i].marked_for_commit) { + all_marked = 0; + break; } + } + + // If all are marked, unmark all. Otherwise, mark all + for (int i = 0; i < viewer->file_count; i++) { + viewer->files[i].marked_for_commit = all_marked ? 0 : 1; + } } /** * Get commit title and message input from user */ -int get_commit_title_input(char *title, int max_len, char *message, int max_message_len) { - if (!title) return 0; - - // Save current screen - WINDOW *saved_screen = dupwin(stdscr); - - // Calculate window dimensions - int input_width = COLS * 0.8; // 80% of screen width - int title_height = 3; - int message_height = 15; // Much taller message box - int start_x = COLS / 2 - input_width / 2; - int title_start_y = LINES / 2 - (title_height + message_height + 2) / 2; - int message_start_y = title_start_y + title_height + 1; - - // Create title and message windows directly (no outer dialog) - WINDOW *title_win = newwin(title_height, input_width, title_start_y, start_x); - WINDOW *message_win = newwin(message_height, input_width, message_start_y, start_x); - - if (!title_win || !message_win) { - if (saved_screen) delwin(saved_screen); - if (title_win) delwin(title_win); - if (message_win) delwin(message_win); - return 0; +int get_commit_title_input(char *title, int max_len, char *message, + int max_message_len) { + if (!title) + return 0; + + // Save current screen + WINDOW *saved_screen = dupwin(stdscr); + + // Calculate window dimensions + int input_width = COLS * 0.8; // 80% of screen width + int title_height = 3; + int message_height = 15; // Much taller message box + int start_x = COLS / 2 - input_width / 2; + int title_start_y = LINES / 2 - (title_height + message_height + 2) / 2; + int message_start_y = title_start_y + title_height + 1; + + // Create title and message windows directly (no outer dialog) + WINDOW *title_win = newwin(title_height, input_width, title_start_y, start_x); + WINDOW *message_win = + newwin(message_height, input_width, message_start_y, start_x); + + if (!title_win || !message_win) { + if (saved_screen) + delwin(saved_screen); + if (title_win) + delwin(title_win); + if (message_win) + delwin(message_win); + return 0; + } + + // Initialize message buffer + if (message) { + message[0] = '\0'; + } + + // Variables for input handling + char local_message[2048] = ""; + int current_field = 0; // 0 = title, 1 = message + int title_scroll_offset = 0; // For horizontal scrolling + int message_scroll_offset = 0; // For vertical scrolling + int ch; + + // Function to redraw title window + void redraw_title() { + werase(title_win); + box(title_win, 0, 0); + + int visible_width = input_width - 4; + int title_len = strlen(title); + + // CRITICAL: Clear the content area with spaces FIRST + for (int x = 1; x <= visible_width; x++) { + mvwaddch(title_win, 1, x, ' '); } - - // Initialize message buffer - if (message) { - message[0] = '\0'; + + // Calculate what part of the title to show + int display_start = title_scroll_offset; + int display_end = display_start + visible_width; + if (display_end > title_len) + display_end = title_len; + + // Show the visible portion of the title ON TOP of cleared spaces + for (int i = display_start; i < display_end; i++) { + mvwaddch(title_win, 1, 1 + (i - display_start), title[i]); } - - // Variables for input handling - char local_message[2048] = ""; - int current_field = 0; // 0 = title, 1 = message - int title_scroll_offset = 0; // For horizontal scrolling - int message_scroll_offset = 0; // For vertical scrolling - int ch; - - // Function to redraw title window - void redraw_title() { - werase(title_win); - box(title_win, 0, 0); - - int visible_width = input_width - 4; - int title_len = strlen(title); - - // CRITICAL: Clear the content area with spaces FIRST - for (int x = 1; x <= visible_width; x++) { - mvwaddch(title_win, 1, x, ' '); - } - - // Calculate what part of the title to show - int display_start = title_scroll_offset; - int display_end = display_start + visible_width; - if (display_end > title_len) display_end = title_len; - - // Show the visible portion of the title ON TOP of cleared spaces - for (int i = display_start; i < display_end; i++) { - mvwaddch(title_win, 1, 1 + (i - display_start), title[i]); - } - - // Highlight header if active - if (current_field == 0) { - wattron(title_win, COLOR_PAIR(4)); - mvwprintw(title_win, 0, 2, " Title (Tab to switch, Enter to commit) "); - wattroff(title_win, COLOR_PAIR(4)); - } else { - mvwprintw(title_win, 0, 2, " Title (Tab to switch, Enter to commit) "); - } - wrefresh(title_win); - } - - // Function to redraw message window - SIMPLE like title field - void redraw_message() { - werase(message_win); - box(message_win, 0, 0); - - int visible_height = message_height - 2; - int message_visible_width = input_width - 3; - - // Clear content area - for (int y = 1; y <= visible_height; y++) { - for (int x = 1; x <= message_visible_width; x++) { - mvwaddch(message_win, y, x, ' '); - } - } - - // SIMPLE: Just display the raw string, character by character - int y = 1, x = 1; - for (int i = 0; local_message[i] && y <= visible_height; i++) { - if (local_message[i] == '\n') { - y++; - x = 1; - } else { - if (x <= message_visible_width) { - mvwaddch(message_win, y, x, local_message[i]); - x++; - if (x > message_visible_width) { - y++; - x = 1; - } - } - } - } - - // Highlight header if active - if (current_field == 1) { - wattron(message_win, COLOR_PAIR(4)); - mvwprintw(message_win, 0, 2, " Message (Tab to switch, Enter for newline) "); - wattroff(message_win, COLOR_PAIR(4)); - } else { - mvwprintw(message_win, 0, 2, " Message (Tab to switch, Enter for newline) "); - } - wrefresh(message_win); - } - - // Initial draw - redraw_title(); - redraw_message(); - - // Position initial cursor + + // Highlight header if active if (current_field == 0) { - int cursor_pos = strlen(title) - title_scroll_offset; - int visible_width = input_width - 4; - if (cursor_pos > visible_width - 1) cursor_pos = visible_width - 1; - if (cursor_pos < 0) cursor_pos = 0; - wmove(title_win, 1, 1 + cursor_pos); - wrefresh(title_win); + wattron(title_win, COLOR_PAIR(4)); + mvwprintw(title_win, 0, 2, " Title (Tab to switch, Enter to commit) "); + wattroff(title_win, COLOR_PAIR(4)); + } else { + mvwprintw(title_win, 0, 2, " Title (Tab to switch, Enter to commit) "); } - - curs_set(1); - noecho(); - - // Main input loop - while (1) { - ch = getch(); - int redraw_needed = 0; - - if (ch == 27) { - // ESC to cancel - don't commit - if (message && max_message_len > 0) { - message[0] = '\0'; // Clear message - } - title[0] = '\0'; // Clear title - break; + wrefresh(title_win); + } + + // Function to redraw message window - SIMPLE like title field + void redraw_message() { + werase(message_win); + box(message_win, 0, 0); + + int visible_height = message_height - 2; + int message_visible_width = input_width - 3; + + // Clear content area + for (int y = 1; y <= visible_height; y++) { + for (int x = 1; x <= message_visible_width; x++) { + mvwaddch(message_win, y, x, ' '); + } + } + + // SIMPLE: Just display the raw string, character by character + int y = 1, x = 1; + for (int i = 0; local_message[i] && y <= visible_height; i++) { + if (local_message[i] == '\n') { + y++; + x = 1; + } else { + if (x <= message_visible_width) { + mvwaddch(message_win, y, x, local_message[i]); + x++; + if (x > message_visible_width) { + y++; + x = 1; + } } - - if (ch == '\t') { - // Switch between fields - current_field = !current_field; - redraw_needed = 1; - } else if (ch == '\n' || ch == '\r') { - if (current_field == 0) { - // Enter in title field - commit if title has content - if (strlen(title) > 0) { - break; - } - } else { - // Enter in message field - add newline - int len = strlen(local_message); - if (len < 2047) { - local_message[len] = '\n'; - local_message[len + 1] = '\0'; - - // Auto-scroll down if needed - int visible_height = message_height - 2; - int current_lines = 1; - for (int i = 0; local_message[i]; i++) { - if (local_message[i] == '\n') current_lines++; - } - if (current_lines > visible_height + message_scroll_offset) { - message_scroll_offset += 2; - } - redraw_message(); - } - } - } else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - // Handle backspace - if (current_field == 0) { - int len = strlen(title); - if (len > 0) { - title[len - 1] = '\0'; - - // Adjust scroll if needed - int visible_width = input_width - 4; - if (len - 1 <= title_scroll_offset) { - title_scroll_offset = (len - 1) - (visible_width - 5); - if (title_scroll_offset < 0) title_scroll_offset = 0; - } - redraw_title(); - } - } else { - int len = strlen(local_message); - if (len > 0) { - local_message[len - 1] = '\0'; - redraw_message(); - } - } - } else if (ch >= 32 && ch <= 126) { - // Regular character input - if (current_field == 0) { - int len = strlen(title); - if (len < max_len - 1) { - title[len] = ch; - title[len + 1] = '\0'; - - // Auto-scroll horizontally if needed - int visible_width = input_width - 4; - if (len + 1 > title_scroll_offset + visible_width - 5) { - title_scroll_offset = (len + 1) - (visible_width - 5); - } - redraw_title(); - } - } else { - int len = strlen(local_message); - if (len < 2047) { - local_message[len] = ch; - local_message[len + 1] = '\0'; - redraw_message(); - } - } + } + } + + // Highlight header if active + if (current_field == 1) { + wattron(message_win, COLOR_PAIR(4)); + mvwprintw(message_win, 0, 2, + " Message (Tab to switch, Enter for newline) "); + wattroff(message_win, COLOR_PAIR(4)); + } else { + mvwprintw(message_win, 0, 2, + " Message (Tab to switch, Enter for newline) "); + } + wrefresh(message_win); + } + + // Initial draw + redraw_title(); + redraw_message(); + + // Position initial cursor + if (current_field == 0) { + int cursor_pos = strlen(title) - title_scroll_offset; + int visible_width = input_width - 4; + if (cursor_pos > visible_width - 1) + cursor_pos = visible_width - 1; + if (cursor_pos < 0) + cursor_pos = 0; + wmove(title_win, 1, 1 + cursor_pos); + wrefresh(title_win); + } + + curs_set(1); + noecho(); + + // Main input loop + while (1) { + ch = getch(); + int redraw_needed = 0; + + if (ch == 27) { + // ESC to cancel - don't commit + if (message && max_message_len > 0) { + message[0] = '\0'; // Clear message + } + title[0] = '\0'; // Clear title + break; + } + + if (ch == '\t') { + // Switch between fields + current_field = !current_field; + redraw_needed = 1; + } else if (ch == '\n' || ch == '\r') { + if (current_field == 0) { + // Enter in title field - commit if title has content + if (strlen(title) > 0) { + break; } - - // Redraw if field switched - if (redraw_needed) { - redraw_title(); - redraw_message(); + } else { + // Enter in message field - add newline + int len = strlen(local_message); + if (len < 2047) { + local_message[len] = '\n'; + local_message[len + 1] = '\0'; + + // Auto-scroll down if needed + int visible_height = message_height - 2; + int current_lines = 1; + for (int i = 0; local_message[i]; i++) { + if (local_message[i] == '\n') + current_lines++; + } + if (current_lines > visible_height + message_scroll_offset) { + message_scroll_offset += 2; + } + redraw_message(); } - - // Position cursor in active field - if (current_field == 0) { - int cursor_pos = strlen(title) - title_scroll_offset; - int visible_width = input_width - 4; - if (cursor_pos > visible_width - 1) cursor_pos = visible_width - 1; - if (cursor_pos < 0) cursor_pos = 0; - wmove(title_win, 1, 1 + cursor_pos); - wrefresh(title_win); + } + } else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { + // Handle backspace + if (current_field == 0) { + int len = strlen(title); + if (len > 0) { + title[len - 1] = '\0'; + + // Adjust scroll if needed + int visible_width = input_width - 4; + if (len - 1 <= title_scroll_offset) { + title_scroll_offset = (len - 1) - (visible_width - 5); + if (title_scroll_offset < 0) + title_scroll_offset = 0; + } + redraw_title(); + } + } else { + int len = strlen(local_message); + if (len > 0) { + local_message[len - 1] = '\0'; + redraw_message(); + } + } + } else if (ch >= 32 && ch <= 126) { + // Regular character input + if (current_field == 0) { + int len = strlen(title); + if (len < max_len - 1) { + title[len] = ch; + title[len + 1] = '\0'; + + // Auto-scroll horizontally if needed + int visible_width = input_width - 4; + if (len + 1 > title_scroll_offset + visible_width - 5) { + title_scroll_offset = (len + 1) - (visible_width - 5); + } + redraw_title(); + } + } else { + int len = strlen(local_message); + if (len < 2047) { + local_message[len] = ch; + local_message[len + 1] = '\0'; + redraw_message(); + } + } + } + + // Redraw if field switched + if (redraw_needed) { + redraw_title(); + redraw_message(); + } + + // Position cursor in active field + if (current_field == 0) { + int cursor_pos = strlen(title) - title_scroll_offset; + int visible_width = input_width - 4; + if (cursor_pos > visible_width - 1) + cursor_pos = visible_width - 1; + if (cursor_pos < 0) + cursor_pos = 0; + wmove(title_win, 1, 1 + cursor_pos); + wrefresh(title_win); + } else { + // Position cursor in message field - EXACTLY like title field + int message_len = strlen(local_message); + int message_visible_width = input_width - 3; + + // Simple cursor positioning - just count where we are + int y = 1, x = 1; + for (int i = 0; i < message_len; i++) { + if (local_message[i] == '\n') { + y++; + x = 1; } else { - // Position cursor in message field - EXACTLY like title field - int message_len = strlen(local_message); - int message_visible_width = input_width - 3; - - // Simple cursor positioning - just count where we are - int y = 1, x = 1; - for (int i = 0; i < message_len; i++) { - if (local_message[i] == '\n') { - y++; - x = 1; - } else { - x++; - if (x > message_visible_width) { - y++; - x = 1; - } - } - } - - // Position cursor at end of text - wmove(message_win, y, x); - wrefresh(message_win); + x++; + if (x > message_visible_width) { + y++; + x = 1; + } } + } + + // Position cursor at end of text + wmove(message_win, y, x); + wrefresh(message_win); } - - // Copy local message to output parameter only if not canceled - if (message && max_message_len > 0 && strlen(title) > 0) { - strncpy(message, local_message, max_message_len - 1); - message[max_message_len - 1] = '\0'; - } - - // Restore settings - curs_set(0); // Hide cursor - - // Clean up windows - delwin(title_win); - delwin(message_win); - - // Restore the screen - if (saved_screen) { - overwrite(saved_screen, stdscr); - delwin(saved_screen); - } - - // Force a complete redraw - clear(); - refresh(); - - return strlen(title) > 0 ? 1 : 0; + } + + // Copy local message to output parameter only if not canceled + if (message && max_message_len > 0 && strlen(title) > 0) { + strncpy(message, local_message, max_message_len - 1); + message[max_message_len - 1] = '\0'; + } + + // Restore settings + curs_set(0); // Hide cursor + + // Clean up windows + delwin(title_win); + delwin(message_win); + + // Restore the screen + if (saved_screen) { + overwrite(saved_screen, stdscr); + delwin(saved_screen); + } + + // Force a complete redraw + clear(); + refresh(); + + return strlen(title) > 0 ? 1 : 0; } /** * Commit marked files with title and message */ -int commit_marked_files(NCursesDiffViewer *viewer, const char *commit_title, const char *commit_message) { - if (!viewer || !commit_title || strlen(commit_title) == 0) return 0; - - // First, add marked files to git - for (int i = 0; i < viewer->file_count; i++) { - if (viewer->files[i].marked_for_commit) { - char cmd[1024]; - snprintf(cmd, sizeof(cmd), "git add \"%s\" 2>/dev/null >/dev/null", viewer->files[i].filename); - system(cmd); - } - } - - // Commit with the provided title and message - char commit_cmd[2048]; - if (commit_message && strlen(commit_message) > 0) { - snprintf(commit_cmd, sizeof(commit_cmd), "git commit -m \"%s\" -m \"%s\" 2>/dev/null >/dev/null", commit_title, commit_message); - } else { - snprintf(commit_cmd, sizeof(commit_cmd), "git commit -m \"%s\" 2>/dev/null >/dev/null", commit_title); - } - int result = system(commit_cmd); - - if (result == 0) { - // Refresh file list and commit history - get_ncurses_changed_files(viewer); - get_commit_history(viewer); - - // Reset selection if no files remain - if (viewer->file_count == 0) { - viewer->selected_file = 0; - viewer->file_line_count = 0; - viewer->file_scroll_offset = 0; - } else if (viewer->selected_file >= viewer->file_count) { - viewer->selected_file = viewer->file_count - 1; - } - - return 1; - } - +int commit_marked_files(NCursesDiffViewer *viewer, const char *commit_title, + const char *commit_message) { + if (!viewer || !commit_title || strlen(commit_title) == 0) return 0; + + // First, add marked files to git + for (int i = 0; i < viewer->file_count; i++) { + if (viewer->files[i].marked_for_commit) { + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "git add \"%s\" 2>/dev/null >/dev/null", + viewer->files[i].filename); + system(cmd); + } + } + + // Commit with the provided title and message + char commit_cmd[2048]; + if (commit_message && strlen(commit_message) > 0) { + snprintf(commit_cmd, sizeof(commit_cmd), + "git commit -m \"%s\" -m \"%s\" 2>/dev/null >/dev/null", + commit_title, commit_message); + } else { + snprintf(commit_cmd, sizeof(commit_cmd), + "git commit -m \"%s\" 2>/dev/null >/dev/null", commit_title); + } + int result = system(commit_cmd); + + if (result == 0) { + get_ncurses_changed_files(viewer); + get_commit_history(viewer); + + if (viewer->file_count == 0) { + viewer->selected_file = 0; + viewer->file_line_count = 0; + viewer->file_scroll_offset = 0; + } else if (viewer->selected_file >= viewer->file_count) { + viewer->selected_file = viewer->file_count - 1; + } + return 1; + } + + return 0; } /** * Push specific commit */ int push_commit(NCursesDiffViewer *viewer, int commit_index) { - if (!viewer || commit_index < 0 || commit_index >= viewer->commit_count) return 0; - - // Start pushing animation immediately - viewer->sync_status = SYNC_STATUS_PUSHING_APPEARING; - viewer->animation_frame = 0; - viewer->text_char_count = 0; - - // Render immediately to show the animation start - render_status_bar(viewer); - - // Do the actual push work - int result = system("git push origin 2>/dev/null >/dev/null"); - - if (result == 0) { - // Refresh commit history to get proper push status - get_commit_history(viewer); - return 1; - } - + if (!viewer || commit_index < 0 || commit_index >= viewer->commit_count) return 0; + + // Start pushing animation immediately + viewer->sync_status = SYNC_STATUS_PUSHING_APPEARING; + viewer->animation_frame = 0; + viewer->text_char_count = 0; + + // Render immediately to show the animation start + render_status_bar(viewer); + + // Do the actual push work + int result = system("git push origin 2>/dev/null >/dev/null"); + + if (result == 0) { + // Refresh commit history to get proper push status + get_commit_history(viewer); + return 1; + } + + return 0; } /** * Pull commits from remote */ int pull_commits(NCursesDiffViewer *viewer) { - if (!viewer) return 0; - - // Start pulling animation immediately - viewer->sync_status = SYNC_STATUS_PULLING_APPEARING; - viewer->animation_frame = 0; - viewer->text_char_count = 0; - - // Render immediately to show the animation start - render_status_bar(viewer); - - // Do the actual pull work - int result = system("git pull origin 2>/dev/null >/dev/null"); - - if (result == 0) { - // Refresh everything after pull - get_ncurses_changed_files(viewer); - get_commit_history(viewer); - - // Reset selection if no files remain - if (viewer->file_count == 0) { - viewer->selected_file = 0; - viewer->file_line_count = 0; - viewer->file_scroll_offset = 0; - } else if (viewer->selected_file >= viewer->file_count) { - viewer->selected_file = viewer->file_count - 1; - } - - // Reload current file if any - if (viewer->file_count > 0 && viewer->selected_file < viewer->file_count) { - load_full_file_with_diff(viewer, viewer->files[viewer->selected_file].filename); - } - - return 1; - } - + if (!viewer) return 0; + + // Start pulling animation immediately + viewer->sync_status = SYNC_STATUS_PULLING_APPEARING; + viewer->animation_frame = 0; + viewer->text_char_count = 0; + + // Render immediately to show the animation start + render_status_bar(viewer); + + // Do the actual pull work + int result = system("git pull origin 2>/dev/null >/dev/null"); + + if (result == 0) { + // Refresh everything after pull + get_ncurses_changed_files(viewer); + get_commit_history(viewer); + + // Reset selection if no files remain + if (viewer->file_count == 0) { + viewer->selected_file = 0; + viewer->file_line_count = 0; + viewer->file_scroll_offset = 0; + } else if (viewer->selected_file >= viewer->file_count) { + viewer->selected_file = viewer->file_count - 1; + } + + // Reload current file if any + if (viewer->file_count > 0 && viewer->selected_file < viewer->file_count) { + load_full_file_with_diff(viewer, + viewer->files[viewer->selected_file].filename); + } + + return 1; + } + + return 0; } /** * Render the file list window */ void render_file_list_window(NCursesDiffViewer *viewer) { - if (!viewer || !viewer->file_list_win) return; - - // Draw rounded border and title - draw_rounded_box(viewer->file_list_win); - mvwprintw(viewer->file_list_win, 0, 2, " 1. Files "); - - int max_files_visible = viewer->file_panel_height - 2; - - for (int i = 0; i < max_files_visible; i++) { - int y = i + 1; - - - // Skip if no more files - if (i >= viewer->file_count) continue; - - // Show selection indicator - if (i == viewer->selected_file) { - if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { - wattron(viewer->file_list_win, COLOR_PAIR(5)); - mvwprintw(viewer->file_list_win, y, 1, ">"); - } else { - wattron(viewer->file_list_win, COLOR_PAIR(1)); - mvwprintw(viewer->file_list_win, y, 1, "*"); - } - } else { - mvwprintw(viewer->file_list_win, y, 1, " "); - } - - // Status indicator - char status = viewer->files[i].status; - if (status == 'M') { - wattron(viewer->file_list_win, COLOR_PAIR(4)); - mvwprintw(viewer->file_list_win, y, 2, "M"); - wattroff(viewer->file_list_win, COLOR_PAIR(4)); - } else if (status == 'A') { - wattron(viewer->file_list_win, COLOR_PAIR(1)); - mvwprintw(viewer->file_list_win, y, 2, "A"); - wattroff(viewer->file_list_win, COLOR_PAIR(1)); - } else if (status == 'D') { - wattron(viewer->file_list_win, COLOR_PAIR(2)); - mvwprintw(viewer->file_list_win, y, 2, "D"); - wattroff(viewer->file_list_win, COLOR_PAIR(2)); - } else { - mvwprintw(viewer->file_list_win, y, 2, "%c", status); - } - - // Filename (truncated to fit panel with "..") - int max_name_len = viewer->file_panel_width - 6; // Leave space for border - char truncated_name[256]; - if ((int)strlen(viewer->files[i].filename) > max_name_len) { - strncpy(truncated_name, viewer->files[i].filename, max_name_len - 2); - truncated_name[max_name_len - 2] = '\0'; - strcat(truncated_name, ".."); - } else { - strcpy(truncated_name, viewer->files[i].filename); - } - - // Color filename green if marked for commit - if (viewer->files[i].marked_for_commit) { - wattron(viewer->file_list_win, COLOR_PAIR(1)); - } - mvwprintw(viewer->file_list_win, y, 4, "%s", truncated_name); - if (viewer->files[i].marked_for_commit) { - wattroff(viewer->file_list_win, COLOR_PAIR(1)); - } - - if (i == viewer->selected_file) { - if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { - wattroff(viewer->file_list_win, COLOR_PAIR(5)); - } else { - wattroff(viewer->file_list_win, COLOR_PAIR(1)); - } - } + if (!viewer || !viewer->file_list_win) + return; + + // Draw rounded border and title + draw_rounded_box(viewer->file_list_win); + mvwprintw(viewer->file_list_win, 0, 2, " 1. Files "); + + int max_files_visible = viewer->file_panel_height - 2; + + for (int i = 0; i < max_files_visible; i++) { + int y = i + 1; + + // Skip if no more files + if (i >= viewer->file_count) + continue; + + // Show selection indicator + if (i == viewer->selected_file) { + if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { + wattron(viewer->file_list_win, COLOR_PAIR(5)); + mvwprintw(viewer->file_list_win, y, 1, ">"); + } else { + wattron(viewer->file_list_win, COLOR_PAIR(1)); + mvwprintw(viewer->file_list_win, y, 1, "*"); + } + } else { + mvwprintw(viewer->file_list_win, y, 1, " "); } - - wrefresh(viewer->file_list_win); + + // Status indicator + char status = viewer->files[i].status; + if (status == 'M') { + wattron(viewer->file_list_win, COLOR_PAIR(4)); + mvwprintw(viewer->file_list_win, y, 2, "M"); + wattroff(viewer->file_list_win, COLOR_PAIR(4)); + } else if (status == 'A') { + wattron(viewer->file_list_win, COLOR_PAIR(1)); + mvwprintw(viewer->file_list_win, y, 2, "A"); + wattroff(viewer->file_list_win, COLOR_PAIR(1)); + } else if (status == 'D') { + wattron(viewer->file_list_win, COLOR_PAIR(2)); + mvwprintw(viewer->file_list_win, y, 2, "D"); + wattroff(viewer->file_list_win, COLOR_PAIR(2)); + } else { + mvwprintw(viewer->file_list_win, y, 2, "%c", status); + } + + // Filename (truncated to fit panel with "..") + int max_name_len = viewer->file_panel_width - 6; // Leave space for border + char truncated_name[256]; + if ((int)strlen(viewer->files[i].filename) > max_name_len) { + strncpy(truncated_name, viewer->files[i].filename, max_name_len - 2); + truncated_name[max_name_len - 2] = '\0'; + strcat(truncated_name, ".."); + } else { + strcpy(truncated_name, viewer->files[i].filename); + } + + // Color filename green if marked for commit + if (viewer->files[i].marked_for_commit) { + wattron(viewer->file_list_win, COLOR_PAIR(1)); + } + mvwprintw(viewer->file_list_win, y, 4, "%s", truncated_name); + if (viewer->files[i].marked_for_commit) { + wattroff(viewer->file_list_win, COLOR_PAIR(1)); + } + + if (i == viewer->selected_file) { + if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { + wattroff(viewer->file_list_win, COLOR_PAIR(5)); + } else { + wattroff(viewer->file_list_win, COLOR_PAIR(1)); + } + } + } + + wrefresh(viewer->file_list_win); } /** * Render the commit list window */ void render_commit_list_window(NCursesDiffViewer *viewer) { - if (!viewer || !viewer->commit_list_win) return; - - // Draw rounded border and title - draw_rounded_box(viewer->commit_list_win); - mvwprintw(viewer->commit_list_win, 0, 2, " 3. Commits "); - - int max_commits_visible = viewer->commit_panel_height - 2; - - for (int i = 0; i < max_commits_visible; i++) { - int y = i + 1; - - - // Skip if no more commits - if (i >= viewer->commit_count) continue; - - // Show selection indicator for commit list mode - if (i == viewer->selected_commit && viewer->current_mode == NCURSES_MODE_COMMIT_LIST) { - wattron(viewer->commit_list_win, COLOR_PAIR(5)); - mvwprintw(viewer->commit_list_win, y, 1, ">"); - wattroff(viewer->commit_list_win, COLOR_PAIR(5)); - } else { - mvwprintw(viewer->commit_list_win, y, 1, " "); - } - - // Show commit hash with color based on push status - if (viewer->commits[i].is_pushed) { - wattron(viewer->commit_list_win, COLOR_PAIR(1)); // Green for pushed - } else { - wattron(viewer->commit_list_win, COLOR_PAIR(2)); // Red for unpushed - } - mvwprintw(viewer->commit_list_win, y, 2, "%s", viewer->commits[i].hash); - if (viewer->commits[i].is_pushed) { - wattroff(viewer->commit_list_win, COLOR_PAIR(1)); - } else { - wattroff(viewer->commit_list_win, COLOR_PAIR(2)); - } - - // Show author initials with same color as hash - if (viewer->commits[i].is_pushed) { - wattron(viewer->commit_list_win, COLOR_PAIR(1)); // Green for pushed - } else { - wattron(viewer->commit_list_win, COLOR_PAIR(2)); // Red for unpushed - } - mvwprintw(viewer->commit_list_win, y, 10, "%s", viewer->commits[i].author_initials); - if (viewer->commits[i].is_pushed) { - wattroff(viewer->commit_list_win, COLOR_PAIR(1)); - } else { - wattroff(viewer->commit_list_win, COLOR_PAIR(2)); - } - - // Show commit title (always white, truncated to fit with "..") - int max_title_len = viewer->file_panel_width - 15; // Leave space for border - char truncated_title[256]; - if ((int)strlen(viewer->commits[i].title) > max_title_len) { - strncpy(truncated_title, viewer->commits[i].title, max_title_len - 2); - truncated_title[max_title_len - 2] = '\0'; - strcat(truncated_title, ".."); - } else { - strcpy(truncated_title, viewer->commits[i].title); - } - - mvwprintw(viewer->commit_list_win, y, 13, "%s", truncated_title); + if (!viewer || !viewer->commit_list_win) + return; + + // Draw rounded border and title + draw_rounded_box(viewer->commit_list_win); + mvwprintw(viewer->commit_list_win, 0, 2, " 3. Commits "); + + int max_commits_visible = viewer->commit_panel_height - 2; + + for (int i = 0; i < max_commits_visible; i++) { + int y = i + 1; + + // Skip if no more commits + if (i >= viewer->commit_count) + continue; + + // Show selection indicator for commit list mode + if (i == viewer->selected_commit && + viewer->current_mode == NCURSES_MODE_COMMIT_LIST) { + wattron(viewer->commit_list_win, COLOR_PAIR(5)); + mvwprintw(viewer->commit_list_win, y, 1, ">"); + wattroff(viewer->commit_list_win, COLOR_PAIR(5)); + } else { + mvwprintw(viewer->commit_list_win, y, 1, " "); } - - wrefresh(viewer->commit_list_win); + + // Show commit hash with color based on push status + if (viewer->commits[i].is_pushed) { + wattron(viewer->commit_list_win, COLOR_PAIR(1)); // Green for pushed + } else { + wattron(viewer->commit_list_win, COLOR_PAIR(2)); // Red for unpushed + } + mvwprintw(viewer->commit_list_win, y, 2, "%s", viewer->commits[i].hash); + if (viewer->commits[i].is_pushed) { + wattroff(viewer->commit_list_win, COLOR_PAIR(1)); + } else { + wattroff(viewer->commit_list_win, COLOR_PAIR(2)); + } + + // Show author initials with same color as hash + if (viewer->commits[i].is_pushed) { + wattron(viewer->commit_list_win, COLOR_PAIR(1)); // Green for pushed + } else { + wattron(viewer->commit_list_win, COLOR_PAIR(2)); // Red for unpushed + } + mvwprintw(viewer->commit_list_win, y, 10, "%s", + viewer->commits[i].author_initials); + if (viewer->commits[i].is_pushed) { + wattroff(viewer->commit_list_win, COLOR_PAIR(1)); + } else { + wattroff(viewer->commit_list_win, COLOR_PAIR(2)); + } + + // Show commit title (always white, truncated to fit with "..") + int max_title_len = viewer->file_panel_width - 15; // Leave space for border + char truncated_title[256]; + if ((int)strlen(viewer->commits[i].title) > max_title_len) { + strncpy(truncated_title, viewer->commits[i].title, max_title_len - 2); + truncated_title[max_title_len - 2] = '\0'; + strcat(truncated_title, ".."); + } else { + strcpy(truncated_title, viewer->commits[i].title); + } + + mvwprintw(viewer->commit_list_win, y, 13, "%s", truncated_title); + } + + wrefresh(viewer->commit_list_win); } /** * Render the file content window */ void render_file_content_window(NCursesDiffViewer *viewer) { - if (!viewer || !viewer->file_content_win) return; - - // Clear the window content area (but preserve borders) - int height, width; - getmaxyx(viewer->file_content_win, height, width); - for (int y = 1; y < height - 1; y++) { - wmove(viewer->file_content_win, y, 1); - for (int x = 1; x < width - 1; x++) { - waddch(viewer->file_content_win, ' '); - } + if (!viewer || !viewer->file_content_win) + return; + + // Clear the window content area (but preserve borders) + int height, width; + getmaxyx(viewer->file_content_win, height, width); + for (int y = 1; y < height - 1; y++) { + wmove(viewer->file_content_win, y, 1); + for (int x = 1; x < width - 1; x++) { + waddch(viewer->file_content_win, ' '); } - - // Draw rounded border - draw_rounded_box(viewer->file_content_win); - - if (viewer->file_count > 0 && viewer->selected_file < viewer->file_count) { - // Show file content (preview in list mode, scrollable in view mode) - if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { - mvwprintw(viewer->file_content_win, 0, 2, " 2. %s (Preview) ", viewer->files[viewer->selected_file].filename); - } else { - mvwprintw(viewer->file_content_win, 0, 2, " 2. %s (Scrollable) ", viewer->files[viewer->selected_file].filename); - } - - int max_lines_visible = viewer->terminal_height - 4; - int content_width = viewer->terminal_width - viewer->file_panel_width - 3; - - - for (int i = 0; i < max_lines_visible && - (i + viewer->file_scroll_offset) < viewer->file_line_count; i++) { - - int line_idx = i + viewer->file_scroll_offset; - NCursesFileLine *line = &viewer->file_lines[line_idx]; - - int y = i + 1; - - // Apply color based on line type - if (line->type == '+') { - wattron(viewer->file_content_win, COLOR_PAIR(1)); // Green for additions - } else if (line->type == '-') { - wattron(viewer->file_content_win, COLOR_PAIR(2)); // Red for deletions - } else if (line->type == '@') { - wattron(viewer->file_content_win, COLOR_PAIR(3)); // Cyan for hunk headers - } - - // Truncate line to fit window - char display_line[1024]; - if ((int)strlen(line->line) > content_width - 2) { - strncpy(display_line, line->line, content_width - 5); - display_line[content_width - 5] = '\0'; - strcat(display_line, "..."); - } else { - strcpy(display_line, line->line); - } - - mvwprintw(viewer->file_content_win, y, 1, "%s", display_line); - - // Reset color - if (line->type == '+') { - wattroff(viewer->file_content_win, COLOR_PAIR(1)); - } else if (line->type == '-') { - wattroff(viewer->file_content_win, COLOR_PAIR(2)); - } else if (line->type == '@') { - wattroff(viewer->file_content_win, COLOR_PAIR(3)); - } - } - - // Show scroll indicator - if (viewer->file_line_count > max_lines_visible) { - int scroll_pos = (viewer->file_scroll_offset * max_lines_visible) / viewer->file_line_count; - mvwprintw(viewer->file_content_win, scroll_pos + 1, content_width + 1, "â–ˆ"); - } - - // Show current position info only in file view mode - if (viewer->current_mode == NCURSES_MODE_FILE_VIEW) { - mvwprintw(viewer->file_content_win, max_lines_visible + 1, 1, - "Line %d-%d of %d", - viewer->file_scroll_offset + 1, - viewer->file_scroll_offset + max_lines_visible < viewer->file_line_count ? - viewer->file_scroll_offset + max_lines_visible : viewer->file_line_count, - viewer->file_line_count); - } else { - mvwprintw(viewer->file_content_win, max_lines_visible + 1, 1, - "Press Enter to enable scrolling"); - } + } + + // Draw rounded border + draw_rounded_box(viewer->file_content_win); + + if (viewer->file_count > 0 && viewer->selected_file < viewer->file_count) { + // Show file content (preview in list mode, scrollable in view mode) + if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { + mvwprintw(viewer->file_content_win, 0, 2, " 2. %s (Preview) ", + viewer->files[viewer->selected_file].filename); + } else { + mvwprintw(viewer->file_content_win, 0, 2, " 2. %s (Scrollable) ", + viewer->files[viewer->selected_file].filename); } - - wrefresh(viewer->file_content_win); + + int max_lines_visible = viewer->terminal_height - 4; + int content_width = viewer->terminal_width - viewer->file_panel_width - 3; + + for (int i = 0; i < max_lines_visible && + (i + viewer->file_scroll_offset) < viewer->file_line_count; + i++) { + + int line_idx = i + viewer->file_scroll_offset; + NCursesFileLine *line = &viewer->file_lines[line_idx]; + + int y = i + 1; + + // Apply color based on line type + if (line->type == '+') { + wattron(viewer->file_content_win, COLOR_PAIR(1)); // Green for additions + } else if (line->type == '-') { + wattron(viewer->file_content_win, COLOR_PAIR(2)); // Red for deletions + } else if (line->type == '@') { + wattron(viewer->file_content_win, + COLOR_PAIR(3)); // Cyan for hunk headers + } + + // Truncate line to fit window + char display_line[1024]; + if ((int)strlen(line->line) > content_width - 2) { + strncpy(display_line, line->line, content_width - 5); + display_line[content_width - 5] = '\0'; + strcat(display_line, "..."); + } else { + strcpy(display_line, line->line); + } + + mvwprintw(viewer->file_content_win, y, 1, "%s", display_line); + + // Reset color + if (line->type == '+') { + wattroff(viewer->file_content_win, COLOR_PAIR(1)); + } else if (line->type == '-') { + wattroff(viewer->file_content_win, COLOR_PAIR(2)); + } else if (line->type == '@') { + wattroff(viewer->file_content_win, COLOR_PAIR(3)); + } + } + + // Show scroll indicator + if (viewer->file_line_count > max_lines_visible) { + int scroll_pos = (viewer->file_scroll_offset * max_lines_visible) / + viewer->file_line_count; + mvwprintw(viewer->file_content_win, scroll_pos + 1, content_width + 1, + "â–ˆ"); + } + + // Show current position info only in file view mode + if (viewer->current_mode == NCURSES_MODE_FILE_VIEW) { + mvwprintw(viewer->file_content_win, max_lines_visible + 1, 1, + "Line %d-%d of %d", viewer->file_scroll_offset + 1, + viewer->file_scroll_offset + max_lines_visible < + viewer->file_line_count + ? viewer->file_scroll_offset + max_lines_visible + : viewer->file_line_count, + viewer->file_line_count); + } else { + mvwprintw(viewer->file_content_win, max_lines_visible + 1, 1, + "Press Enter to enable scrolling"); + } + } + + wrefresh(viewer->file_content_win); } /** * Render the status bar */ void render_status_bar(NCursesDiffViewer *viewer) { - if (!viewer || !viewer->status_bar_win) return; - - // Clear status bar (no border) - werase(viewer->status_bar_win); - wbkgd(viewer->status_bar_win, COLOR_PAIR(3)); // Cyan background - - // Left side: Key bindings based on current mode - char keybindings[256] = ""; - if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { - strcpy(keybindings, "Stage: | Stage All: a | Commit: c"); - } else if (viewer->current_mode == NCURSES_MODE_COMMIT_LIST) { - strcpy(keybindings, "Push: P | Pull: p | Nav: j/k"); - } else if (viewer->current_mode == NCURSES_MODE_FILE_VIEW) { - strcpy(keybindings, "Scroll: j/k | Page: Ctrl+U/D | Back: Esc"); + if (!viewer || !viewer->status_bar_win) + return; + + // Clear status bar (no border) + werase(viewer->status_bar_win); + wbkgd(viewer->status_bar_win, COLOR_PAIR(3)); // Cyan background + + // Left side: Key bindings based on current mode + char keybindings[256] = ""; + if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { + strcpy(keybindings, "Stage: | Stage All: a | Commit: c"); + } else if (viewer->current_mode == NCURSES_MODE_COMMIT_LIST) { + strcpy(keybindings, "Push: P | Pull: p | Nav: j/k"); + } else if (viewer->current_mode == NCURSES_MODE_FILE_VIEW) { + strcpy(keybindings, "Scroll: j/k | Page: Ctrl+U/D | Back: Esc"); + } + + mvwprintw(viewer->status_bar_win, 0, 1, "%s", keybindings); + + // Right side: Sync status + char sync_text[64] = ""; + char *spinner_chars[] = {"|", "/", "-", "\\"}; + int spinner_idx = + viewer->spinner_frame % 4; // Change every frame (~50ms per character) + + if (viewer->sync_status == SYNC_STATUS_IDLE) { + // Show nothing when idle + strcpy(sync_text, ""); + } else if (viewer->sync_status >= SYNC_STATUS_SYNCING_APPEARING && + viewer->sync_status <= SYNC_STATUS_SYNCING_DISAPPEARING) { + // Show partial or full "Fetching" text with spinner + char full_text[] = "Fetching"; + int chars_to_show = viewer->text_char_count; + if (chars_to_show > 8) + chars_to_show = 8; // Max length of "Fetching" + if (chars_to_show < 0) + chars_to_show = 0; + + if (chars_to_show > 0) { + char partial_text[16]; + strncpy(partial_text, full_text, chars_to_show); + partial_text[chars_to_show] = '\0'; + + if (viewer->sync_status == SYNC_STATUS_SYNCING_VISIBLE) { + snprintf(sync_text, sizeof(sync_text), "%s %s", partial_text, + spinner_chars[spinner_idx]); + } else { + strcpy(sync_text, partial_text); + } + } else { + strcpy(sync_text, ""); } - - mvwprintw(viewer->status_bar_win, 0, 1, "%s", keybindings); - - // Right side: Sync status - char sync_text[64] = ""; - char *spinner_chars[] = {"|", "/", "-", "\\"}; - int spinner_idx = viewer->spinner_frame % 4; // Change every frame (~50ms per character) - - if (viewer->sync_status == SYNC_STATUS_IDLE) { - // Show nothing when idle - strcpy(sync_text, ""); - } else if (viewer->sync_status >= SYNC_STATUS_SYNCING_APPEARING && viewer->sync_status <= SYNC_STATUS_SYNCING_DISAPPEARING) { - // Show partial or full "Fetching" text with spinner - char full_text[] = "Fetching"; - int chars_to_show = viewer->text_char_count; - if (chars_to_show > 8) chars_to_show = 8; // Max length of "Fetching" - if (chars_to_show < 0) chars_to_show = 0; - - if (chars_to_show > 0) { - char partial_text[16]; - strncpy(partial_text, full_text, chars_to_show); - partial_text[chars_to_show] = '\0'; - - if (viewer->sync_status == SYNC_STATUS_SYNCING_VISIBLE) { - snprintf(sync_text, sizeof(sync_text), "%s %s", partial_text, spinner_chars[spinner_idx]); - } else { - strcpy(sync_text, partial_text); - } - } else { - strcpy(sync_text, ""); - } - } else if (viewer->sync_status >= SYNC_STATUS_PUSHING_APPEARING && viewer->sync_status <= SYNC_STATUS_PUSHING_DISAPPEARING) { - // Show partial or full "Pushing" text with spinner - char full_text[] = "Pushing"; - int chars_to_show = viewer->text_char_count; - if (chars_to_show > 7) chars_to_show = 7; // Max length of "Pushing" - if (chars_to_show < 0) chars_to_show = 0; - - if (chars_to_show > 0) { - char partial_text[16]; - strncpy(partial_text, full_text, chars_to_show); - partial_text[chars_to_show] = '\0'; - - if (viewer->sync_status == SYNC_STATUS_PUSHING_VISIBLE) { - snprintf(sync_text, sizeof(sync_text), "%s %s", partial_text, spinner_chars[spinner_idx]); - } else { - strcpy(sync_text, partial_text); - } - } else { - strcpy(sync_text, ""); - } - } else if (viewer->sync_status >= SYNC_STATUS_PULLING_APPEARING && viewer->sync_status <= SYNC_STATUS_PULLING_DISAPPEARING) { - // Show partial or full "Pulling" text with spinner - char full_text[] = "Pulling"; - int chars_to_show = viewer->text_char_count; - if (chars_to_show > 7) chars_to_show = 7; // Max length of "Pulling" - if (chars_to_show < 0) chars_to_show = 0; - - if (chars_to_show > 0) { - char partial_text[16]; - strncpy(partial_text, full_text, chars_to_show); - partial_text[chars_to_show] = '\0'; - - if (viewer->sync_status == SYNC_STATUS_PULLING_VISIBLE) { - snprintf(sync_text, sizeof(sync_text), "%s %s", partial_text, spinner_chars[spinner_idx]); - } else { - strcpy(sync_text, partial_text); - } - } else { - strcpy(sync_text, ""); - } - } else if (viewer->sync_status >= SYNC_STATUS_SYNCED_APPEARING && viewer->sync_status <= SYNC_STATUS_SYNCED_DISAPPEARING) { - // Show partial or full "Synced!" text (for fetching) - char full_text[] = "Synced!"; - int chars_to_show = viewer->text_char_count; - if (chars_to_show > 7) chars_to_show = 7; // Max length of "Synced!" - if (chars_to_show < 0) chars_to_show = 0; - - strncpy(sync_text, full_text, chars_to_show); - sync_text[chars_to_show] = '\0'; - } else if (viewer->sync_status >= SYNC_STATUS_PUSHED_APPEARING && viewer->sync_status <= SYNC_STATUS_PUSHED_DISAPPEARING) { - // Show partial or full "Pushed!" text - char full_text[] = "Pushed!"; - int chars_to_show = viewer->text_char_count; - if (chars_to_show > 7) chars_to_show = 7; // Max length of "Pushed!" - if (chars_to_show < 0) chars_to_show = 0; - - strncpy(sync_text, full_text, chars_to_show); - sync_text[chars_to_show] = '\0'; - } else if (viewer->sync_status >= SYNC_STATUS_PULLED_APPEARING && viewer->sync_status <= SYNC_STATUS_PULLED_DISAPPEARING) { - // Show partial or full "Pulled!" text - char full_text[] = "Pulled!"; - int chars_to_show = viewer->text_char_count; - if (chars_to_show > 7) chars_to_show = 7; // Max length of "Pulled!" - if (chars_to_show < 0) chars_to_show = 0; - - strncpy(sync_text, full_text, chars_to_show); - sync_text[chars_to_show] = '\0'; + } else if (viewer->sync_status >= SYNC_STATUS_PUSHING_APPEARING && + viewer->sync_status <= SYNC_STATUS_PUSHING_DISAPPEARING) { + // Show partial or full "Pushing" text with spinner + char full_text[] = "Pushing"; + int chars_to_show = viewer->text_char_count; + if (chars_to_show > 7) + chars_to_show = 7; // Max length of "Pushing" + if (chars_to_show < 0) + chars_to_show = 0; + + if (chars_to_show > 0) { + char partial_text[16]; + strncpy(partial_text, full_text, chars_to_show); + partial_text[chars_to_show] = '\0'; + + if (viewer->sync_status == SYNC_STATUS_PUSHING_VISIBLE) { + snprintf(sync_text, sizeof(sync_text), "%s %s", partial_text, + spinner_chars[spinner_idx]); + } else { + strcpy(sync_text, partial_text); + } + } else { + strcpy(sync_text, ""); } - - if (strlen(sync_text) > 0) { - int sync_text_pos = viewer->terminal_width - strlen(sync_text) - 1; // No border padding needed - - if (viewer->sync_status >= SYNC_STATUS_SYNCED_APPEARING && viewer->sync_status <= SYNC_STATUS_SYNCED_DISAPPEARING || - viewer->sync_status >= SYNC_STATUS_PUSHED_APPEARING && viewer->sync_status <= SYNC_STATUS_PUSHED_DISAPPEARING || - viewer->sync_status >= SYNC_STATUS_PULLED_APPEARING && viewer->sync_status <= SYNC_STATUS_PULLED_DISAPPEARING) { - wattron(viewer->status_bar_win, COLOR_PAIR(1)); // Green for success messages - mvwprintw(viewer->status_bar_win, 0, sync_text_pos, "%s", sync_text); - wattroff(viewer->status_bar_win, COLOR_PAIR(1)); - } else { - wattron(viewer->status_bar_win, COLOR_PAIR(4)); // Yellow for in-progress messages - mvwprintw(viewer->status_bar_win, 0, sync_text_pos, "%s", sync_text); - wattroff(viewer->status_bar_win, COLOR_PAIR(4)); - } + } else if (viewer->sync_status >= SYNC_STATUS_PULLING_APPEARING && + viewer->sync_status <= SYNC_STATUS_PULLING_DISAPPEARING) { + // Show partial or full "Pulling" text with spinner + char full_text[] = "Pulling"; + int chars_to_show = viewer->text_char_count; + if (chars_to_show > 7) + chars_to_show = 7; // Max length of "Pulling" + if (chars_to_show < 0) + chars_to_show = 0; + + if (chars_to_show > 0) { + char partial_text[16]; + strncpy(partial_text, full_text, chars_to_show); + partial_text[chars_to_show] = '\0'; + + if (viewer->sync_status == SYNC_STATUS_PULLING_VISIBLE) { + snprintf(sync_text, sizeof(sync_text), "%s %s", partial_text, + spinner_chars[spinner_idx]); + } else { + strcpy(sync_text, partial_text); + } + } else { + strcpy(sync_text, ""); } - - wrefresh(viewer->status_bar_win); - - // Ensure cursor stays hidden and positioned off-screen - move(viewer->terminal_height - 1, viewer->terminal_width - 1); - refresh(); + } else if (viewer->sync_status >= SYNC_STATUS_SYNCED_APPEARING && + viewer->sync_status <= SYNC_STATUS_SYNCED_DISAPPEARING) { + // Show partial or full "Synced!" text (for fetching) + char full_text[] = "Synced!"; + int chars_to_show = viewer->text_char_count; + if (chars_to_show > 7) + chars_to_show = 7; // Max length of "Synced!" + if (chars_to_show < 0) + chars_to_show = 0; + + strncpy(sync_text, full_text, chars_to_show); + sync_text[chars_to_show] = '\0'; + } else if (viewer->sync_status >= SYNC_STATUS_PUSHED_APPEARING && + viewer->sync_status <= SYNC_STATUS_PUSHED_DISAPPEARING) { + // Show partial or full "Pushed!" text + char full_text[] = "Pushed!"; + int chars_to_show = viewer->text_char_count; + if (chars_to_show > 7) + chars_to_show = 7; // Max length of "Pushed!" + if (chars_to_show < 0) + chars_to_show = 0; + + strncpy(sync_text, full_text, chars_to_show); + sync_text[chars_to_show] = '\0'; + } else if (viewer->sync_status >= SYNC_STATUS_PULLED_APPEARING && + viewer->sync_status <= SYNC_STATUS_PULLED_DISAPPEARING) { + // Show partial or full "Pulled!" text + char full_text[] = "Pulled!"; + int chars_to_show = viewer->text_char_count; + if (chars_to_show > 7) + chars_to_show = 7; // Max length of "Pulled!" + if (chars_to_show < 0) + chars_to_show = 0; + + strncpy(sync_text, full_text, chars_to_show); + sync_text[chars_to_show] = '\0'; + } + + if (strlen(sync_text) > 0) { + int sync_text_pos = viewer->terminal_width - strlen(sync_text) - + 1; // No border padding needed + + if (viewer->sync_status >= SYNC_STATUS_SYNCED_APPEARING && + viewer->sync_status <= SYNC_STATUS_SYNCED_DISAPPEARING || + viewer->sync_status >= SYNC_STATUS_PUSHED_APPEARING && + viewer->sync_status <= SYNC_STATUS_PUSHED_DISAPPEARING || + viewer->sync_status >= SYNC_STATUS_PULLED_APPEARING && + viewer->sync_status <= SYNC_STATUS_PULLED_DISAPPEARING) { + wattron(viewer->status_bar_win, + COLOR_PAIR(1)); // Green for success messages + mvwprintw(viewer->status_bar_win, 0, sync_text_pos, "%s", sync_text); + wattroff(viewer->status_bar_win, COLOR_PAIR(1)); + } else { + wattron(viewer->status_bar_win, + COLOR_PAIR(4)); // Yellow for in-progress messages + mvwprintw(viewer->status_bar_win, 0, sync_text_pos, "%s", sync_text); + wattroff(viewer->status_bar_win, COLOR_PAIR(4)); + } + } + + wrefresh(viewer->status_bar_win); + + // Ensure cursor stays hidden and positioned off-screen + move(viewer->terminal_height - 1, viewer->terminal_width - 1); + refresh(); } /** * Update sync status and check for new files */ void update_sync_status(NCursesDiffViewer *viewer) { - if (!viewer) return; - - time_t current_time = time(NULL); - - // Check if it's time to sync (every 30 seconds) - if (current_time - viewer->last_sync_time >= 30) { - viewer->sync_status = SYNC_STATUS_SYNCING_APPEARING; - viewer->last_sync_time = current_time; - viewer->animation_frame = 0; - viewer->text_char_count = 0; - - // Do the actual sync work immediately - get_ncurses_changed_files(viewer); - if (viewer->selected_file < viewer->file_count && viewer->file_count > 0) { - load_full_file_with_diff(viewer, viewer->files[viewer->selected_file].filename); - } - get_commit_history(viewer); + if (!viewer) + return; + + time_t current_time = time(NULL); + + // Check if it's time to sync (every 30 seconds) + if (current_time - viewer->last_sync_time >= 30) { + viewer->sync_status = SYNC_STATUS_SYNCING_APPEARING; + viewer->last_sync_time = current_time; + viewer->animation_frame = 0; + viewer->text_char_count = 0; + + // Do the actual sync work immediately + get_ncurses_changed_files(viewer); + if (viewer->selected_file < viewer->file_count && viewer->file_count > 0) { + load_full_file_with_diff(viewer, + viewer->files[viewer->selected_file].filename); } - - // Handle all animation states - if (viewer->sync_status != SYNC_STATUS_IDLE) { - viewer->animation_frame++; - - // Handle fetching animation (4 seconds total: appear + visible + disappear) - if (viewer->sync_status >= SYNC_STATUS_SYNCING_APPEARING && viewer->sync_status <= SYNC_STATUS_SYNCING_DISAPPEARING) { - if (viewer->sync_status == SYNC_STATUS_SYNCING_APPEARING) { - // Appearing: one character every 2 frames (0.1s) for "Fetching" (8 chars) - viewer->text_char_count = viewer->animation_frame / 2; - if (viewer->text_char_count >= 8) { - viewer->text_char_count = 8; - viewer->sync_status = SYNC_STATUS_SYNCING_VISIBLE; - viewer->animation_frame = 0; - } - } else if (viewer->sync_status == SYNC_STATUS_SYNCING_VISIBLE) { - // Visible with spinner for 2.4 seconds (48 frames) - if (viewer->animation_frame >= 48) { - viewer->sync_status = SYNC_STATUS_SYNCING_DISAPPEARING; - viewer->animation_frame = 0; - viewer->text_char_count = 8; - } - } else if (viewer->sync_status == SYNC_STATUS_SYNCING_DISAPPEARING) { - // Disappearing: remove one character every 2 frames (0.1s) - int chars_to_remove = viewer->animation_frame / 2; - viewer->text_char_count = 8 - chars_to_remove; - if (viewer->text_char_count <= 0) { - viewer->text_char_count = 0; - viewer->sync_status = SYNC_STATUS_SYNCED_APPEARING; - viewer->animation_frame = 0; - } - } + get_commit_history(viewer); + } + + // Handle all animation states + if (viewer->sync_status != SYNC_STATUS_IDLE) { + viewer->animation_frame++; + + // Handle fetching animation (4 seconds total: appear + visible + disappear) + if (viewer->sync_status >= SYNC_STATUS_SYNCING_APPEARING && + viewer->sync_status <= SYNC_STATUS_SYNCING_DISAPPEARING) { + if (viewer->sync_status == SYNC_STATUS_SYNCING_APPEARING) { + // Appearing: one character every 2 frames (0.1s) for "Fetching" (8 + // chars) + viewer->text_char_count = viewer->animation_frame / 2; + if (viewer->text_char_count >= 8) { + viewer->text_char_count = 8; + viewer->sync_status = SYNC_STATUS_SYNCING_VISIBLE; + viewer->animation_frame = 0; } - // Handle pushing animation (same 4 second pattern) - else if (viewer->sync_status >= SYNC_STATUS_PUSHING_APPEARING && viewer->sync_status <= SYNC_STATUS_PUSHING_DISAPPEARING) { - if (viewer->sync_status == SYNC_STATUS_PUSHING_APPEARING) { - // Appearing: one character every 2 frames (0.1s) for "Pushing" (7 chars) - viewer->text_char_count = viewer->animation_frame / 2; - if (viewer->text_char_count >= 7) { - viewer->text_char_count = 7; - viewer->sync_status = SYNC_STATUS_PUSHING_VISIBLE; - viewer->animation_frame = 0; - } - } else if (viewer->sync_status == SYNC_STATUS_PUSHING_VISIBLE) { - // Visible with spinner for 1.2 seconds (24 frames) - faster - if (viewer->animation_frame >= 24) { - viewer->sync_status = SYNC_STATUS_PUSHING_DISAPPEARING; - viewer->animation_frame = 0; - viewer->text_char_count = 7; - } - } else if (viewer->sync_status == SYNC_STATUS_PUSHING_DISAPPEARING) { - // Disappearing: remove one character every 2 frames (0.1s) - int chars_to_remove = viewer->animation_frame / 2; - viewer->text_char_count = 7 - chars_to_remove; - if (viewer->text_char_count <= 0) { - viewer->text_char_count = 0; - viewer->sync_status = SYNC_STATUS_PUSHED_APPEARING; - viewer->animation_frame = 0; - } - } + } else if (viewer->sync_status == SYNC_STATUS_SYNCING_VISIBLE) { + // Visible with spinner for 2.4 seconds (48 frames) + if (viewer->animation_frame >= 48) { + viewer->sync_status = SYNC_STATUS_SYNCING_DISAPPEARING; + viewer->animation_frame = 0; + viewer->text_char_count = 8; } - // Handle pulling animation (same pattern as pushing) - else if (viewer->sync_status >= SYNC_STATUS_PULLING_APPEARING && viewer->sync_status <= SYNC_STATUS_PULLING_DISAPPEARING) { - if (viewer->sync_status == SYNC_STATUS_PULLING_APPEARING) { - // Appearing: one character every 2 frames (0.1s) for "Pulling" (7 chars) - viewer->text_char_count = viewer->animation_frame / 2; - if (viewer->text_char_count >= 7) { - viewer->text_char_count = 7; - viewer->sync_status = SYNC_STATUS_PULLING_VISIBLE; - viewer->animation_frame = 0; - } - } else if (viewer->sync_status == SYNC_STATUS_PULLING_VISIBLE) { - // Visible with spinner for 1.2 seconds (24 frames) - faster - if (viewer->animation_frame >= 24) { - viewer->sync_status = SYNC_STATUS_PULLING_DISAPPEARING; - viewer->animation_frame = 0; - viewer->text_char_count = 7; - } - } else if (viewer->sync_status == SYNC_STATUS_PULLING_DISAPPEARING) { - // Disappearing: remove one character every 2 frames (0.1s) - int chars_to_remove = viewer->animation_frame / 2; - viewer->text_char_count = 7 - chars_to_remove; - if (viewer->text_char_count <= 0) { - viewer->text_char_count = 0; - viewer->sync_status = SYNC_STATUS_PULLED_APPEARING; - viewer->animation_frame = 0; - } - } - } - // Handle synced animation - else if (viewer->sync_status >= SYNC_STATUS_SYNCED_APPEARING && viewer->sync_status <= SYNC_STATUS_SYNCED_DISAPPEARING) { - if (viewer->sync_status == SYNC_STATUS_SYNCED_APPEARING) { - // Appearing: one character every 2 frames (0.1s) for "Synced!" (7 chars) - viewer->text_char_count = viewer->animation_frame / 2; - if (viewer->text_char_count >= 7) { - viewer->text_char_count = 7; - viewer->sync_status = SYNC_STATUS_SYNCED_VISIBLE; - viewer->animation_frame = 0; - } - } else if (viewer->sync_status == SYNC_STATUS_SYNCED_VISIBLE) { - // Visible for 3 seconds (60 frames) - if (viewer->animation_frame >= 60) { - viewer->sync_status = SYNC_STATUS_SYNCED_DISAPPEARING; - viewer->animation_frame = 0; - viewer->text_char_count = 7; - } - } else if (viewer->sync_status == SYNC_STATUS_SYNCED_DISAPPEARING) { - // Disappearing: remove one character every 2 frames (0.1s) - int chars_to_remove = viewer->animation_frame / 2; - viewer->text_char_count = 7 - chars_to_remove; - if (viewer->text_char_count <= 0) { - viewer->text_char_count = 0; - viewer->sync_status = SYNC_STATUS_IDLE; - } - } - } - // Handle pushed animation - else if (viewer->sync_status >= SYNC_STATUS_PUSHED_APPEARING && viewer->sync_status <= SYNC_STATUS_PUSHED_DISAPPEARING) { - if (viewer->sync_status == SYNC_STATUS_PUSHED_APPEARING) { - // Appearing: one character every 2 frames (0.1s) for "Pushed!" (7 chars) - viewer->text_char_count = viewer->animation_frame / 2; - if (viewer->text_char_count >= 7) { - viewer->text_char_count = 7; - viewer->sync_status = SYNC_STATUS_PUSHED_VISIBLE; - viewer->animation_frame = 0; - } - } else if (viewer->sync_status == SYNC_STATUS_PUSHED_VISIBLE) { - // Visible for 2 seconds (40 frames) - if (viewer->animation_frame >= 40) { - viewer->sync_status = SYNC_STATUS_PUSHED_DISAPPEARING; - viewer->animation_frame = 0; - viewer->text_char_count = 7; - } - } else if (viewer->sync_status == SYNC_STATUS_PUSHED_DISAPPEARING) { - // Disappearing: remove one character every 2 frames (0.1s) - int chars_to_remove = viewer->animation_frame / 2; - viewer->text_char_count = 7 - chars_to_remove; - if (viewer->text_char_count <= 0) { - viewer->text_char_count = 0; - viewer->sync_status = SYNC_STATUS_IDLE; - } - } - } - // Handle pulled animation - else if (viewer->sync_status >= SYNC_STATUS_PULLED_APPEARING && viewer->sync_status <= SYNC_STATUS_PULLED_DISAPPEARING) { - if (viewer->sync_status == SYNC_STATUS_PULLED_APPEARING) { - // Appearing: one character every 2 frames (0.1s) for "Pulled!" (7 chars) - viewer->text_char_count = viewer->animation_frame / 2; - if (viewer->text_char_count >= 7) { - viewer->text_char_count = 7; - viewer->sync_status = SYNC_STATUS_PULLED_VISIBLE; - viewer->animation_frame = 0; - } - } else if (viewer->sync_status == SYNC_STATUS_PULLED_VISIBLE) { - // Visible for 2 seconds (40 frames) - if (viewer->animation_frame >= 40) { - viewer->sync_status = SYNC_STATUS_PULLED_DISAPPEARING; - viewer->animation_frame = 0; - viewer->text_char_count = 7; - } - } else if (viewer->sync_status == SYNC_STATUS_PULLED_DISAPPEARING) { - // Disappearing: remove one character every 2 frames (0.1s) - int chars_to_remove = viewer->animation_frame / 2; - viewer->text_char_count = 7 - chars_to_remove; - if (viewer->text_char_count <= 0) { - viewer->text_char_count = 0; - viewer->sync_status = SYNC_STATUS_IDLE; - } - } + } else if (viewer->sync_status == SYNC_STATUS_SYNCING_DISAPPEARING) { + // Disappearing: remove one character every 2 frames (0.1s) + int chars_to_remove = viewer->animation_frame / 2; + viewer->text_char_count = 8 - chars_to_remove; + if (viewer->text_char_count <= 0) { + viewer->text_char_count = 0; + viewer->sync_status = SYNC_STATUS_SYNCED_APPEARING; + viewer->animation_frame = 0; } + } } - - // Always update spinner frame for spinner animation - viewer->spinner_frame++; - if (viewer->spinner_frame > 100) viewer->spinner_frame = 0; // Reset to prevent overflow + // Handle pushing animation (same 4 second pattern) + else if (viewer->sync_status >= SYNC_STATUS_PUSHING_APPEARING && + viewer->sync_status <= SYNC_STATUS_PUSHING_DISAPPEARING) { + if (viewer->sync_status == SYNC_STATUS_PUSHING_APPEARING) { + // Appearing: one character every 2 frames (0.1s) for "Pushing" (7 + // chars) + viewer->text_char_count = viewer->animation_frame / 2; + if (viewer->text_char_count >= 7) { + viewer->text_char_count = 7; + viewer->sync_status = SYNC_STATUS_PUSHING_VISIBLE; + viewer->animation_frame = 0; + } + } else if (viewer->sync_status == SYNC_STATUS_PUSHING_VISIBLE) { + // Visible with spinner for 1.2 seconds (24 frames) - faster + if (viewer->animation_frame >= 24) { + viewer->sync_status = SYNC_STATUS_PUSHING_DISAPPEARING; + viewer->animation_frame = 0; + viewer->text_char_count = 7; + } + } else if (viewer->sync_status == SYNC_STATUS_PUSHING_DISAPPEARING) { + // Disappearing: remove one character every 2 frames (0.1s) + int chars_to_remove = viewer->animation_frame / 2; + viewer->text_char_count = 7 - chars_to_remove; + if (viewer->text_char_count <= 0) { + viewer->text_char_count = 0; + viewer->sync_status = SYNC_STATUS_PUSHED_APPEARING; + viewer->animation_frame = 0; + } + } + } + // Handle pulling animation (same pattern as pushing) + else if (viewer->sync_status >= SYNC_STATUS_PULLING_APPEARING && + viewer->sync_status <= SYNC_STATUS_PULLING_DISAPPEARING) { + if (viewer->sync_status == SYNC_STATUS_PULLING_APPEARING) { + // Appearing: one character every 2 frames (0.1s) for "Pulling" (7 + // chars) + viewer->text_char_count = viewer->animation_frame / 2; + if (viewer->text_char_count >= 7) { + viewer->text_char_count = 7; + viewer->sync_status = SYNC_STATUS_PULLING_VISIBLE; + viewer->animation_frame = 0; + } + } else if (viewer->sync_status == SYNC_STATUS_PULLING_VISIBLE) { + // Visible with spinner for 1.2 seconds (24 frames) - faster + if (viewer->animation_frame >= 24) { + viewer->sync_status = SYNC_STATUS_PULLING_DISAPPEARING; + viewer->animation_frame = 0; + viewer->text_char_count = 7; + } + } else if (viewer->sync_status == SYNC_STATUS_PULLING_DISAPPEARING) { + // Disappearing: remove one character every 2 frames (0.1s) + int chars_to_remove = viewer->animation_frame / 2; + viewer->text_char_count = 7 - chars_to_remove; + if (viewer->text_char_count <= 0) { + viewer->text_char_count = 0; + viewer->sync_status = SYNC_STATUS_PULLED_APPEARING; + viewer->animation_frame = 0; + } + } + } + // Handle synced animation + else if (viewer->sync_status >= SYNC_STATUS_SYNCED_APPEARING && + viewer->sync_status <= SYNC_STATUS_SYNCED_DISAPPEARING) { + if (viewer->sync_status == SYNC_STATUS_SYNCED_APPEARING) { + // Appearing: one character every 2 frames (0.1s) for "Synced!" (7 + // chars) + viewer->text_char_count = viewer->animation_frame / 2; + if (viewer->text_char_count >= 7) { + viewer->text_char_count = 7; + viewer->sync_status = SYNC_STATUS_SYNCED_VISIBLE; + viewer->animation_frame = 0; + } + } else if (viewer->sync_status == SYNC_STATUS_SYNCED_VISIBLE) { + // Visible for 3 seconds (60 frames) + if (viewer->animation_frame >= 60) { + viewer->sync_status = SYNC_STATUS_SYNCED_DISAPPEARING; + viewer->animation_frame = 0; + viewer->text_char_count = 7; + } + } else if (viewer->sync_status == SYNC_STATUS_SYNCED_DISAPPEARING) { + // Disappearing: remove one character every 2 frames (0.1s) + int chars_to_remove = viewer->animation_frame / 2; + viewer->text_char_count = 7 - chars_to_remove; + if (viewer->text_char_count <= 0) { + viewer->text_char_count = 0; + viewer->sync_status = SYNC_STATUS_IDLE; + } + } + } + // Handle pushed animation + else if (viewer->sync_status >= SYNC_STATUS_PUSHED_APPEARING && + viewer->sync_status <= SYNC_STATUS_PUSHED_DISAPPEARING) { + if (viewer->sync_status == SYNC_STATUS_PUSHED_APPEARING) { + // Appearing: one character every 2 frames (0.1s) for "Pushed!" (7 + // chars) + viewer->text_char_count = viewer->animation_frame / 2; + if (viewer->text_char_count >= 7) { + viewer->text_char_count = 7; + viewer->sync_status = SYNC_STATUS_PUSHED_VISIBLE; + viewer->animation_frame = 0; + } + } else if (viewer->sync_status == SYNC_STATUS_PUSHED_VISIBLE) { + // Visible for 2 seconds (40 frames) + if (viewer->animation_frame >= 40) { + viewer->sync_status = SYNC_STATUS_PUSHED_DISAPPEARING; + viewer->animation_frame = 0; + viewer->text_char_count = 7; + } + } else if (viewer->sync_status == SYNC_STATUS_PUSHED_DISAPPEARING) { + // Disappearing: remove one character every 2 frames (0.1s) + int chars_to_remove = viewer->animation_frame / 2; + viewer->text_char_count = 7 - chars_to_remove; + if (viewer->text_char_count <= 0) { + viewer->text_char_count = 0; + viewer->sync_status = SYNC_STATUS_IDLE; + } + } + } + // Handle pulled animation + else if (viewer->sync_status >= SYNC_STATUS_PULLED_APPEARING && + viewer->sync_status <= SYNC_STATUS_PULLED_DISAPPEARING) { + if (viewer->sync_status == SYNC_STATUS_PULLED_APPEARING) { + // Appearing: one character every 2 frames (0.1s) for "Pulled!" (7 + // chars) + viewer->text_char_count = viewer->animation_frame / 2; + if (viewer->text_char_count >= 7) { + viewer->text_char_count = 7; + viewer->sync_status = SYNC_STATUS_PULLED_VISIBLE; + viewer->animation_frame = 0; + } + } else if (viewer->sync_status == SYNC_STATUS_PULLED_VISIBLE) { + // Visible for 2 seconds (40 frames) + if (viewer->animation_frame >= 40) { + viewer->sync_status = SYNC_STATUS_PULLED_DISAPPEARING; + viewer->animation_frame = 0; + viewer->text_char_count = 7; + } + } else if (viewer->sync_status == SYNC_STATUS_PULLED_DISAPPEARING) { + // Disappearing: remove one character every 2 frames (0.1s) + int chars_to_remove = viewer->animation_frame / 2; + viewer->text_char_count = 7 - chars_to_remove; + if (viewer->text_char_count <= 0) { + viewer->text_char_count = 0; + viewer->sync_status = SYNC_STATUS_IDLE; + } + } + } + } + + // Always update spinner frame for spinner animation + viewer->spinner_frame++; + if (viewer->spinner_frame > 100) + viewer->spinner_frame = 0; // Reset to prevent overflow } /** * Handle keyboard input for navigation */ int handle_ncurses_diff_input(NCursesDiffViewer *viewer, int key) { - if (!viewer) return 0; - - int max_lines_visible = viewer->terminal_height - 4; - - // Global quit commands - if (key == 'q' || key == 'Q') { - return 0; // Exit + if (!viewer) + return 0; + + int max_lines_visible = viewer->terminal_height - 4; + + // Global quit commands + if (key == 'q' || key == 'Q') { + return 0; // Exit + } + + // Global number key navigation + switch (key) { + case '1': + viewer->current_mode = NCURSES_MODE_FILE_LIST; + break; + case '2': + // Switch to file view mode and load selected file + if (viewer->file_count > 0 && viewer->selected_file < viewer->file_count) { + load_full_file_with_diff(viewer, + viewer->files[viewer->selected_file].filename); + viewer->current_mode = NCURSES_MODE_FILE_VIEW; } - - // Global number key navigation + break; + case '3': + viewer->current_mode = NCURSES_MODE_COMMIT_LIST; + break; + } + + if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { + // File list mode navigation switch (key) { - case '1': - viewer->current_mode = NCURSES_MODE_FILE_LIST; - break; - case '2': - // Switch to file view mode and load selected file - if (viewer->file_count > 0 && viewer->selected_file < viewer->file_count) { - load_full_file_with_diff(viewer, viewer->files[viewer->selected_file].filename); - viewer->current_mode = NCURSES_MODE_FILE_VIEW; - } - break; - case '3': - viewer->current_mode = NCURSES_MODE_COMMIT_LIST; - break; + case 27: // ESC + return 0; // Exit from file list mode + + case KEY_UP: + case 'k': + if (viewer->selected_file > 0) { + viewer->selected_file--; + // Auto-preview the selected file + if (viewer->file_count > 0) { + load_full_file_with_diff( + viewer, viewer->files[viewer->selected_file].filename); + } + } + break; + + case KEY_DOWN: + case 'j': + if (viewer->selected_file < viewer->file_count - 1) { + viewer->selected_file++; + // Auto-preview the selected file + if (viewer->file_count > 0) { + load_full_file_with_diff( + viewer, viewer->files[viewer->selected_file].filename); + } + } + break; + + case ' ': // Space - toggle file marking + if (viewer->file_count > 0 && + viewer->selected_file < viewer->file_count) { + toggle_file_mark(viewer, viewer->selected_file); + } + break; + + case 'a': + case 'A': // Mark all files + mark_all_files(viewer); + break; + + case 'c': + case 'C': // Commit marked files + { + char commit_title[MAX_COMMIT_TITLE_LEN]; + char commit_message[2048]; + if (get_commit_title_input(commit_title, MAX_COMMIT_TITLE_LEN, + commit_message, sizeof(commit_message))) { + commit_marked_files(viewer, commit_title, commit_message); + } + } break; + + case '\t': // Tab - switch to commit list mode + viewer->current_mode = NCURSES_MODE_COMMIT_LIST; + break; + + case '\n': + case '\r': + case KEY_ENTER: + // Enter file view mode and load the selected file + if (viewer->file_count > 0 && + viewer->selected_file < viewer->file_count) { + load_full_file_with_diff(viewer, + viewer->files[viewer->selected_file].filename); + viewer->current_mode = NCURSES_MODE_FILE_VIEW; + } + break; } - - if (viewer->current_mode == NCURSES_MODE_FILE_LIST) { - // File list mode navigation - switch (key) { - case 27: // ESC - return 0; // Exit from file list mode - - case KEY_UP: - case 'k': - if (viewer->selected_file > 0) { - viewer->selected_file--; - // Auto-preview the selected file - if (viewer->file_count > 0) { - load_full_file_with_diff(viewer, viewer->files[viewer->selected_file].filename); - } - } - break; - - case KEY_DOWN: - case 'j': - if (viewer->selected_file < viewer->file_count - 1) { - viewer->selected_file++; - // Auto-preview the selected file - if (viewer->file_count > 0) { - load_full_file_with_diff(viewer, viewer->files[viewer->selected_file].filename); - } - } - break; - - case ' ': // Space - toggle file marking - if (viewer->file_count > 0 && viewer->selected_file < viewer->file_count) { - toggle_file_mark(viewer, viewer->selected_file); - } - break; - - case 'a': - case 'A': // Mark all files - mark_all_files(viewer); - break; - - case 'c': - case 'C': // Commit marked files - { - char commit_title[MAX_COMMIT_TITLE_LEN]; - char commit_message[2048]; - if (get_commit_title_input(commit_title, MAX_COMMIT_TITLE_LEN, commit_message, sizeof(commit_message))) { - commit_marked_files(viewer, commit_title, commit_message); - } - } - break; - - case '\t': // Tab - switch to commit list mode - viewer->current_mode = NCURSES_MODE_COMMIT_LIST; - break; - - case '\n': - case '\r': - case KEY_ENTER: - // Enter file view mode and load the selected file - if (viewer->file_count > 0 && viewer->selected_file < viewer->file_count) { - load_full_file_with_diff(viewer, viewer->files[viewer->selected_file].filename); - viewer->current_mode = NCURSES_MODE_FILE_VIEW; - } - break; + } else if (viewer->current_mode == NCURSES_MODE_FILE_VIEW) { + // File view mode navigation + switch (key) { + case 27: // ESC + viewer->current_mode = NCURSES_MODE_FILE_LIST; // Return to file list mode + break; + + case KEY_UP: + case 'k': + // Scroll content up + if (viewer->file_scroll_offset > 0) { + viewer->file_scroll_offset--; + } + break; + + case KEY_DOWN: + case 'j': + // Scroll content down + if (viewer->file_line_count > max_lines_visible && + viewer->file_scroll_offset < + viewer->file_line_count - max_lines_visible) { + viewer->file_scroll_offset++; + } + break; + + case 21: // Ctrl+U + // Scroll up 30 lines + viewer->file_scroll_offset -= 30; + if (viewer->file_scroll_offset < 0) { + viewer->file_scroll_offset = 0; + } + break; + + case 4: // Ctrl+D + // Scroll down 30 lines + if (viewer->file_line_count > max_lines_visible) { + viewer->file_scroll_offset += 30; + if (viewer->file_scroll_offset > + viewer->file_line_count - max_lines_visible) { + viewer->file_scroll_offset = + viewer->file_line_count - max_lines_visible; } - } else if (viewer->current_mode == NCURSES_MODE_FILE_VIEW) { - // File view mode navigation - switch (key) { - case 27: // ESC - viewer->current_mode = NCURSES_MODE_FILE_LIST; // Return to file list mode - break; - - case KEY_UP: - case 'k': - // Scroll content up - if (viewer->file_scroll_offset > 0) { - viewer->file_scroll_offset--; - } - break; - - case KEY_DOWN: - case 'j': - // Scroll content down - if (viewer->file_line_count > max_lines_visible && - viewer->file_scroll_offset < viewer->file_line_count - max_lines_visible) { - viewer->file_scroll_offset++; - } - break; - - case 21: // Ctrl+U - // Scroll up 30 lines - viewer->file_scroll_offset -= 30; - if (viewer->file_scroll_offset < 0) { - viewer->file_scroll_offset = 0; - } - break; - - case 4: // Ctrl+D - // Scroll down 30 lines - if (viewer->file_line_count > max_lines_visible) { - viewer->file_scroll_offset += 30; - if (viewer->file_scroll_offset > viewer->file_line_count - max_lines_visible) { - viewer->file_scroll_offset = viewer->file_line_count - max_lines_visible; - } - } - break; - - case KEY_NPAGE: // Page Down - case ' ': - // Scroll content down by page - if (viewer->file_line_count > max_lines_visible) { - viewer->file_scroll_offset += max_lines_visible; - if (viewer->file_scroll_offset > viewer->file_line_count - max_lines_visible) { - viewer->file_scroll_offset = viewer->file_line_count - max_lines_visible; - } - } - break; - - case KEY_PPAGE: // Page Up - // Scroll content up by page - viewer->file_scroll_offset -= max_lines_visible; - if (viewer->file_scroll_offset < 0) { - viewer->file_scroll_offset = 0; - } - break; - } - } else if (viewer->current_mode == NCURSES_MODE_COMMIT_LIST) { - // Commit list mode navigation - switch (key) { - case 27: // ESC - case '\t': // Tab - return to file list mode - viewer->current_mode = NCURSES_MODE_FILE_LIST; - break; - - case KEY_UP: - case 'k': - if (viewer->selected_commit > 0) { - viewer->selected_commit--; - } - break; - - case KEY_DOWN: - case 'j': - if (viewer->selected_commit < viewer->commit_count - 1) { - viewer->selected_commit++; - } - break; - - case 'P': // Push commit - if (viewer->commit_count > 0 && viewer->selected_commit < viewer->commit_count) { - push_commit(viewer, viewer->selected_commit); - } - break; - - case 'p': // Pull commits - pull_commits(viewer); - break; + } + break; + + case KEY_NPAGE: // Page Down + case ' ': + // Scroll content down by page + if (viewer->file_line_count > max_lines_visible) { + viewer->file_scroll_offset += max_lines_visible; + if (viewer->file_scroll_offset > + viewer->file_line_count - max_lines_visible) { + viewer->file_scroll_offset = + viewer->file_line_count - max_lines_visible; } + } + break; + + case KEY_PPAGE: // Page Up + // Scroll content up by page + viewer->file_scroll_offset -= max_lines_visible; + if (viewer->file_scroll_offset < 0) { + viewer->file_scroll_offset = 0; + } + break; } - - return 1; // Continue + } else if (viewer->current_mode == NCURSES_MODE_COMMIT_LIST) { + // Commit list mode navigation + switch (key) { + case 27: // ESC + case '\t': // Tab - return to file list mode + viewer->current_mode = NCURSES_MODE_FILE_LIST; + break; + + case KEY_UP: + case 'k': + if (viewer->selected_commit > 0) { + viewer->selected_commit--; + } + break; + + case KEY_DOWN: + case 'j': + if (viewer->selected_commit < viewer->commit_count - 1) { + viewer->selected_commit++; + } + break; + + case 'P': // Push commit + if (viewer->commit_count > 0 && + viewer->selected_commit < viewer->commit_count) { + push_commit(viewer, viewer->selected_commit); + } + break; + + case 'p': // Pull commits + pull_commits(viewer); + break; + } + } + + return 1; // Continue } /** * Run the ncurses diff viewer */ int run_ncurses_diff_viewer(void) { - NCursesDiffViewer viewer; - - if (!init_ncurses_diff_viewer(&viewer)) { - printf("Failed to initialize ncurses diff viewer\n"); - return 1; + NCursesDiffViewer viewer; + + if (!init_ncurses_diff_viewer(&viewer)) { + printf("Failed to initialize ncurses diff viewer\n"); + return 1; + } + + // Get changed files (can be 0, that's okay) + get_ncurses_changed_files(&viewer); + + // Load commit history + get_commit_history(&viewer); + + // Load the first file by default + if (viewer.file_count > 0) { + load_full_file_with_diff(&viewer, viewer.files[0].filename); + } + + // Initial display + attron(COLOR_PAIR(3)); + if (viewer.current_mode == NCURSES_MODE_FILE_LIST) { + mvprintw(0, 0, + "Git Diff Viewer: 1=files 2=view 3=commits | j/k=nav Space=mark " + "A=all C=commit P=push | q=quit"); + } else if (viewer.current_mode == NCURSES_MODE_FILE_VIEW) { + mvprintw(0, 0, + "Git Diff Viewer: 1=files 2=view 3=commits | j/k=scroll " + "Ctrl+U/D=30lines | q=quit"); + } else { + mvprintw( + 0, 0, + "Git Diff Viewer: 1=files 2=view 3=commits | j/k=nav P=push | q=quit"); + } + attroff(COLOR_PAIR(3)); + refresh(); + + render_file_list_window(&viewer); + render_file_content_window(&viewer); + render_commit_list_window(&viewer); + render_status_bar(&viewer); + + // Main display loop + int running = 1; + NCursesViewMode last_mode = viewer.current_mode; + + while (running) { + // Only update title if mode changed + if (viewer.current_mode != last_mode) { + // Clear just the title line + move(0, 0); + clrtoeol(); + + attron(COLOR_PAIR(3)); + if (viewer.current_mode == NCURSES_MODE_FILE_LIST) { + mvprintw(0, 0, + "Git Diff Viewer: 1=files 2=view 3=commits | j/k=nav " + "Space=mark A=all C=commit P=push | q=quit"); + } else if (viewer.current_mode == NCURSES_MODE_FILE_VIEW) { + mvprintw(0, 0, + "Git Diff Viewer: 1=files 2=view 3=commits | j/k=scroll " + "Ctrl+U/D=30lines | q=quit"); + } else { + mvprintw(0, 0, + "Git Diff Viewer: 1=files 2=view 3=commits | j/k=nav P=push " + "p=pull | q=quit"); + } + attroff(COLOR_PAIR(3)); + refresh(); + last_mode = viewer.current_mode; } - - // Get changed files (can be 0, that's okay) - get_ncurses_changed_files(&viewer); - - // Load commit history - get_commit_history(&viewer); - - // Load the first file by default - if (viewer.file_count > 0) { - load_full_file_with_diff(&viewer, viewer.files[0].filename); - } - - // Initial display - attron(COLOR_PAIR(3)); - if (viewer.current_mode == NCURSES_MODE_FILE_LIST) { - mvprintw(0, 0, "Git Diff Viewer: 1=files 2=view 3=commits | j/k=nav Space=mark A=all C=commit P=push | q=quit"); - } else if (viewer.current_mode == NCURSES_MODE_FILE_VIEW) { - mvprintw(0, 0, "Git Diff Viewer: 1=files 2=view 3=commits | j/k=scroll Ctrl+U/D=30lines | q=quit"); - } else { - mvprintw(0, 0, "Git Diff Viewer: 1=files 2=view 3=commits | j/k=nav P=push | q=quit"); - } - attroff(COLOR_PAIR(3)); - refresh(); - + + // Update sync status and check for new files + update_sync_status(&viewer); + render_file_list_window(&viewer); render_file_content_window(&viewer); render_commit_list_window(&viewer); render_status_bar(&viewer); - - // Main display loop - int running = 1; - NCursesViewMode last_mode = viewer.current_mode; - - while (running) { - // Only update title if mode changed - if (viewer.current_mode != last_mode) { - // Clear just the title line - move(0, 0); - clrtoeol(); - - attron(COLOR_PAIR(3)); - if (viewer.current_mode == NCURSES_MODE_FILE_LIST) { - mvprintw(0, 0, "Git Diff Viewer: 1=files 2=view 3=commits | j/k=nav Space=mark A=all C=commit P=push | q=quit"); - } else if (viewer.current_mode == NCURSES_MODE_FILE_VIEW) { - mvprintw(0, 0, "Git Diff Viewer: 1=files 2=view 3=commits | j/k=scroll Ctrl+U/D=30lines | q=quit"); - } else { - mvprintw(0, 0, "Git Diff Viewer: 1=files 2=view 3=commits | j/k=nav P=push p=pull | q=quit"); - } - attroff(COLOR_PAIR(3)); - refresh(); - last_mode = viewer.current_mode; - } - - // Update sync status and check for new files - update_sync_status(&viewer); - - render_file_list_window(&viewer); - render_file_content_window(&viewer); - render_commit_list_window(&viewer); - render_status_bar(&viewer); - - // Keep cursor hidden - curs_set(0); - - int c = getch(); - if (c != ERR) { // Only process if a key was actually pressed - running = handle_ncurses_diff_input(&viewer, c); - } - - // Small delay to prevent excessive CPU usage and allow animations - usleep(50000); // 50ms delay + + // Keep cursor hidden + curs_set(0); + + int c = getch(); + if (c != ERR) { // Only process if a key was actually pressed + running = handle_ncurses_diff_input(&viewer, c); } - - cleanup_ncurses_diff_viewer(&viewer); - return 0; + + // Small delay to prevent excessive CPU usage and allow animations + usleep(50000); // 50ms delay + } + + cleanup_ncurses_diff_viewer(&viewer); + return 0; } /** * Clean up ncurses diff viewer resources */ void cleanup_ncurses_diff_viewer(NCursesDiffViewer *viewer) { - if (viewer) { - if (viewer->file_list_win) { - delwin(viewer->file_list_win); - } - if (viewer->file_content_win) { - delwin(viewer->file_content_win); - } - if (viewer->commit_list_win) { - delwin(viewer->commit_list_win); - } - if (viewer->status_bar_win) { - delwin(viewer->status_bar_win); - } + if (viewer) { + if (viewer->file_list_win) { + delwin(viewer->file_list_win); } - endwin(); -} \ No newline at end of file + if (viewer->file_content_win) { + delwin(viewer->file_content_win); + } + if (viewer->commit_list_win) { + delwin(viewer->commit_list_win); + } + if (viewer->status_bar_win) { + delwin(viewer->status_bar_win); + } + } + endwin(); +} diff --git a/ncurses_diff_viewer.o b/ncurses_diff_viewer.o index aef19a318680879c86abc6c14f5347c8295a5a01..dd10cb3be4c1ac596f827b4ffd4a5b670e449242 100644 GIT binary patch delta 7422 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 ZL6=g1cv#n^6z1yDAI}X0Skwva{{gPpm68Ae delta 7538 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 RF{IA^*th?&L#5#8{|8*}$s_;( diff --git a/shell b/shell index 3c4275bed7e5e28e581bab0db7f999cb17d72e49..b1a1add9d2693f6df93150520e21cd10cc0bba03 100755 GIT binary patch delta 12416 zcmc(F33yahp6@;P+<(7T8BanKr`)?W7ONww7{(qDb)--c{lP&zRFw6>si zKYwIDXZTm|+Ru6VCYE6zeT0{@arVE6JLlHKo7 zEL-fEVoB}#&ZB(K53St5bWPI|c>T*9tEL%u{_H0Q?(^xTXFCgDofc5AyaO{@} zquQe?jKU>h%2T>Qmmfj5LZyq4eU>n&yPd^egq@4Yr(~#QgVSL{muWrwF^HGC+j&}M zqTp%22o^Wj>e5_Bv~!OoiPpJ*eE@t|u4xN(X(1zeVUHEKO;hmZ z0L$$Dqm9yO_v*f~6yb<7%UA^%Ms(kJ?2YJ|*DUrNtkSFiK$y$&anI6yB9wrz*J4MZ z-$r}OXrpj^s~&LRT^9QZ5I5zl zu7mFf5ag!{AoCg0#h$$m_77ULA@OOHepeb~N zvtGd2-HG5j46=B5qQX71$I{HbtL^WPF*4a$`)|e=r782dl;sNRCHA{xjKb7gxkLXQ zhyHDL@>rvP(L)MdnM{cO@HO|pXV;F!{jKi(Q_lTu_G7qTef%2qUhZgm7@B@>tx_@Z zYE9p9DV=aAy=(swlu}G1*->~d6E+LlL&kw|$VgGjzTu#!*~`WOecUzZlkKMj`ZO0k z?x0uOJwRu-+Mi)5`i{EwmAY%MA@iU;Zam05?2_34tw8Duf&pQkF7K+Sham;rIBk@>qWmt;^@(pJ`w5F`b4#@_lfkn zUhgNA)1!;EwD%BCXW6$_C|hmN0mqMRzdKdad?M)2vI2GY21)8Ht5n-5lGIr?U6A5Y z6ruVoO}ofM){O=2+t~*hns!O|n%Fov>U+J`3?d7(Jv4UP~_Bvz{Q*wrHSAT~k~c%g+o4)rscEN47=xP=|c z*HX3q%v7O$yoEgvorc;kO*D$oNKZ~Q3eqMj`@wT9nl@-FxU-k+gh@vJ!u=_Wz?nz8 zHWWE7XaMU9gFp$*3E5bG`KF$Ge5gO<~Z1LGyga>DMJ1X(NWk0u#;^%XLj zd35u9;}ejrbo4s204ygs;ypdz_#xarGuSHY*S`5fZ3xfin&T0My6}@e3e>gadn4MW z;O($4oIT&z2c(DWRg>YjY4&52jqtE1{mQ8a<{KO_`mD1(Jl_z_r`_4c=NqE=w0E7J zY!oy0vi)yUjI+$&_5M^N&Je;so`z8M+oz`?DgAa*wGqYAQEk*<`Ka2cOY#2#!qh>G z;AA`mHGkLq>x~=rBL7R!;w6s*zfJRNz|+qP%@gpWgCGW)G)?N9i>=hEyF#%`po3J(OtQJ;x~G-^as~;$b%)4vL32@$g$bSQkh(Cp}uPc~-oy0&+eC z6hBr`2orc5gxfVO{Q>yZHh%w>!rO`AN#boUNk*>4pr>sNR_g9r$4)@K4#QMh9beBL zgI1k}DR6nTZ9Ur!8{V|rNT`s2!lm2U+CE{>D1Z z^SxeW&F`s2V>@BwVi)7sI>-CMN;2XnYyLw}O&CM&x#qX0-ejar8D%_vMA2C00Rnz( z`yR|!(lMiq3gwumTUbAsbDV4E&K7oYfY=A^CvP&syb@|2xXH-nRYsEC3rJ{=YmNOa z><49w|>Wq{!U4L2f32pB>Jcbso(KYP6n-K-H`kb+=-AA>pibK*PcbHi=VMd zyTs)y0&3KH&)+-8tKH-KwaeTw{i*frXC)Zbpq#3z@2qEO2%$p5=j7pf5KONr)U>B~ z{vjovamBD-y5c>>M=0?QtY^1Cyx3LZz2;o2aI}JW9Xw#~_$wo~ddF3Q915dbaNjS$ zkVoHM&(6b2N8EGBwH(Hb^Q+~`_#CY6u-VNAucVx>e9zZ!!Wm_HyZ_3n9I@vPd53gknUkV=?>}kZtaBjL)OVSN6$90RP6ux zD-5eRiya1$e{oSRG_v=?n)Zde|EQ69vHw?h|5+p34$-4SD76I%nZ zL%Ot~#3*BWhZna}IN z>3GI8+r<7E`o@l>YYvl76^`QNKYmE|q5RlS3 zqnN*rhnhMZ_2B5vIvgDbWE+k`@99njJfQ@BU-#QRbw)w`@7;s#P0}tOx%-YL80J%V zcd8MV`BYC;BEQqzhMFUX&_kVn5>GTC}{nQ;kLKf%YUml03v^EyM|W@S#sZEgA}=yzQXXXk1l-ic|`Vyh0GMV zm~GawIOJWz%r$aw(DL|GOMcdXJ$yt0XYI~H=k zxfm{*02h4>d~2%fqT4Y&*1{^?!pg5aqeRR1D%sffGZ^GkMPuxC8qepAssD%u(S5zD z#Gf^0qH=0yOt)?ZehBs-I38$g3S^@Sop(=L0>8xef_J*Tc?pW@&;80#@uq-i#pdEF3=jJAl^{kTXS4u1`@Bu{lWumIL$D0EEQCt4rnF3CsbbE`os|&ke6Y+Zm zYRF3;JAQk*$!ppJmKq_iy!@|O_SB_DzmXB(F+q)(L+(J6=DjbbsJ)Yk+j5Oaar;){*rTPB$ih(i_!Mh#n zooWu;F<8@HbcWh9&4JbEJ3ny;l4dQ-hMArdjw^p!N%)c@A>00p%j&sd%2TIWQmuCc z{v^k2Es}NY3TrLv8A!TCkXvV8OYXc&ZcF(Ua)R2H$zP)uy^2}@)RqZq6Q5MnCR@4^ zk8Qsfc_+hane)POV6D*nHb?XLS{V>4uOzPLkVB(ZkRt4-Z)`Alc3-Dde^ew7!T@!);GY`^sa{933hmjF}ADZJ>#8l7GpPtSO_s$q4A{3 zLN>ua-hX3A&+5w3=`qI4t`|7vG_p}$hwq_hQ*?i}{YfcR+E2C8V$0J_fo9?zlx@~g zelKP9Qf4n@_fl>z_0x2N#VBWYFY(jnP)DdE)65d16#he0;@48ZJnf zF-oqJkNOef7SC&=S8h@G*Ga3 zkn+S5p-Gz{nIl()*&sq%N){55rC*}a{Jt1v^MqQ9f`1u18wUCx!SeAr7fGgxih{}b z&jf}D13spcJm;yZhccwt6rqubpY&eJ>qQ8KC^t^Ye-u6|F_#c>mhTgadd^Y+*LZ}e zT<*;(EPAcSoT#=dOs%F8Zsk!0FjLE=m8Ih!fR zj%}s_i#+_>W-T$E5vEaJWsax_Ds!?r5IUJ^>qjXWE@LINo$}jp5TR;w6{VZa2#uDA z%u3{gOZz8E}axL&GV6@Ifqu5e{a=qUpGwiTg?7zEtwceeYks|Cg^P zT(~OhQ+H4r)Cxfa)9j!m;VpKOvV_1Vsq_?8#G#a^Y%A%;9!fFy(Uj+fX+1Lq%0X#v z!&6IBur-hp<|{PiAHG6}UyG1u9-=96DmC{i(m(*|*<#s8W8-8co4w?XQSm|=WG)v4 zC(&%9fS}k(g|ldas5iYdL~tKO*#aH`N*U#d3N-&4D-a*%7NF}yXzfG*Ogi%A4- zzI+V^&LfDdV3;!bh+#yRx+y^)okrbJvO1Y@+dqXO=xGBXvMRLX0m5m>G}Ni(X*+ergpo0F8eP2M(7lGn4w zee`(NsCsyi%x>}`wED=~?feb&k!Po9IOa~!ZP>jVrQsj67@+4Vqn9j^qi90X9w(t^ zAEoxnzfq!gkmKju$+sA(QbPz>-&P8V3o%NIQZh0VbQe+PR!Z-TQm~J5#9{^myAEXiD2KXJN5ZZ)bgZkA#$vxB$MI|_yLTEE>REEydMuiB= zb}DWs{~l0^oTU&CTD4b8m)9diLr3!QM!_h(R)sub{b~+54HN*aZpuJ&Om1~~4eg_e zCn>3l%32+E{tl#i`qdoryODVfSGXLqEHjnoRFTQc!{m+jk$FK3O#PKAezCwSKG>>P z{*A)q@JhBhndG54tl>PRVgNufjESTPT*N4ZYIul}ntLhOEZdkao0A}hSm#M6K_QWU zx#28%EdDUssMD-PSBEtPb3I0*Ylu(^5D`VNC7%L^A!ZO1t;8?%QPw#M#|T42Hs84c z2y|&Tva14dk+eai$S`p9(h>BBR~)XOmY zMa@2ac!+{>;QEG*UWAEIugstXSZ^yOVdj*B-Gej(b+V1hQ8NK6HVdh&4JjiO@E}=- zdCXbzB-<>JuC=^~a(FgduM9~5_GuWDg^;aENWsg?XXr%?@rvlkQI3zGkiM>gCZV)r z4q^P57N!YuV21O)!6e1sU=r(!O@iGjb}@l2l-divInpEuL2r%NPKTs~U5-A=l~!V* zXoxsfNQDre%s&XEw5z}cK!UXfz*>4qs%tLm25GO6u$O?A!eE20HrNPhFmXh9FY)p@ zdKIIg*O;MFTH#e?h1X$)Dp+Bd2;pn2@TBrXCuU4ERKx{Y116S@$X^`Sx6peB(NS-LvUPS#VLqa41~rk8Z8E@2~?_AWVp}8 z`bZf#1yLGj#c4PqaTq0S6*7bu6DCF!V81>%je_UNcM@Sr8G2EOQm%b0Hb0~s^nnQZ zlnCQ%D4~stt0*CYsk)E`wo$^B&?^#|iUTj~7}P5f?t-56XK4HR64WOVZ)`P%@e&=U zNe`;{JA%eE2=N}Lf>trq4Px8>NCbDf0*o!ShBM+)Umh`t|*Y>!UIe ztuH7)y$e5$GOZ`S(NQ9UO>+k&>iEBZgpmnpp)U(jA|8>;`V{w(>kHG7wByrJ{A&T! z$01vez&EqywON#gUW2@ErD;N$BjnqLVWJt-%f%dKVG_oI(ZYL_ilvz^%e1Sb8T=l7 z3Zn#`T8rmKq_bBvn&a3v&G34VYvZe}RRm z!hBNnr|i9ydz#WZ$?`-fyIp)5%IoVTI!!2p17e~D$yc$Na$;aHxAV|>N_I1AXrO_MXhMt%J1NvIX3%1!LoXF$e&YAzRSU1^loAt4;H9kH7Vfu=~bULmpSBz%(#-^dYI#`J%Mugli@|HRj2Yrz!%34LWO z{&)0s`MPYaZ?H3_d|~7q*U10lOt0c;AwHyY|4U4>%;8tXlJaTRWdSN(_$M(Whb|vK zx#1$uuX6s$L;fZ3%CPuMnb;!&JEsmM`X?JMB46G{zDyg1(E^PW-_Z(DBQb4mQ}241 z%%jA2Qa^n}8hSHkC4A+Ie{FD!%W0Oy<=`gTAUrwouxH6)|Yyy6l3dhN`FY) z@BaXKk2ypg4728&P#nT|Pgaz#k@g@eLhZ0|r$#8VySL~b54ThI0NVK6^0N?-zpi-qD7wD%C;`09kw z7=c?uS-6yfCvnLuE>TJ!(G%h-sNoYg%#ngG3FfOPvA4u9GUHn|-ew`-METiqAO&}m z*-73y3L=^Q{1fkb`FYy#(6)Nh=&CCA45?+?>{V+$W7!yc$6C*1Uwf|l-%6@~ZhyGe zGe-a5J-cX~XOjL-j(w|GUn{V8iZxznzc1DcbM2z_o=LuY@A1h0=(C2|gPJ}0{|?&8 B!yf~%Rexi^n{Z=N^r7jFV4yaNOc2ni@)g`0qwDlZiiD+yQ$93?3D zsLciSfYO_Qk5bF4iVX#{I9j=Y<70*r9JQhnVXU38KxgdN&gb{vd!K`p(dqnt-`8*M zPuXX$z4m&qwb$OT=gX}1f6D6Ur%!45*?{Fns6JTn?hIb>?8@D_>775^^5LJasDHM7 zud!~g!SKJ{z1J|(*0P{|be&PohTH#34DZ)wLZcE8NY*t zCQ}~)eBcrMf!|a$XlzSrqBdJgcu%5a3v^KREo-?`yV|AN$le8$DeiO$W&Tj@o-Rpg z8Y{-`2qlaJSdZQaj@cP7s$EgZ3vVc-T%{Xyc@DZ2DP4r@(}Y3Y=`{8;*tvuflnfCz zFby_znKrVYf_RxbouOs;1yB2DVDX)LU7CwWckHqx(FPZ=2Y}DYHEpggEySZ|cUiuh zwR!IPA*>I8P&c@M?EuUNpwTWH&P%Uq(G&Wm3P+r}kyV0WtDZ0#a~?hQON%`Tt1MRl zAY9AxuJzy;k=*@NSDe55)U)zc34rehH36K)l}t+_T#X zZ-!;rRX}^tK0BQEvtHB#&UWzq1cLlr0VLmg82cq%`kY5k?zX7*cIBT0HE8TkNcwkr zP{b6Eo(99W0eDpp37SGTq{Y)%yTcEzLm*2Z!>@2p?Xom;_nr0+NAL_b%Kq^PUYa_` zkg{B4J>ULd1TPHSY&i7acjzy$Q%3T#qWcxPGMNy4-DUe9u_Gg~f1|tqq_h7~`yuSF zdFL|plZK<|A!z!8l}bhbrJ8==QabKX`i=buP)hak6i4A}nXp-kJ!ljdhj^+|_B97R z&AxFI&_`c}UTr@v&}&@uxPv~)J_2-hv;75zqLpgb*Xp)iMyA~!H5z2rxn$mO$gH<- z8_i4DZo6YN&t?bhpN{7F?AP|O(Y!DFy?q{g*e7<{7z|1F$T7TK|K2A*=6GVDV*0Y6jdUT!^`UvsV%VsT7wpy(Njvw1{M?ljOM9}xLe6@Fq zB-P8tsA;t%)ypOeQbrU-s4-L1&NGp9BSCus`y^e{F335i-(hdS&3|yG5$4Ya8!H?n z?JvgioY~)HgM@@ebkkxf@pswPYTmI}ihL6*Q6fLQIQ)pvbu+YF#AJ#Z(YF_~o#3_j z;=Gyt7>5THd}`KtdL{c-X`96k<9TK;ei_LvVy}qKB0#J+`lqdU)6{ zMd0Zb>|v;%$z(Y*qK8(ngLzs&>&r|P+HbF5KY~s}>=(xIBIdPEjN|#C@ydSi+zL$_ zumRlJUfVmK=QX^TIv1RcX!mNK0=hqUj-5lRQ?zeB50Ke7P4^7|&Lmp35PTe<~8;H>- zo#~<3TpT`K&NM!oi^Hd@``rn=n6aPR|543PGhg?|6M38=gnxP!Le*!#dliz>XD8M0 zD2A>YUWeiH8eX63dmX~mLyY7J_(;}#-Loh0sd|y`1!(bt$ARCh`843XhIv@vz10VZ zuUyT;r2oWY|7IEwr~kw=-1G^LEYXsV124TCUBE0mc{(rXdz~jkfGFDa`d6#jD&+JE zk8}}59~F?hiZtymPntb@I?qer?2!eYqFb;8I@?P%Z3Ed4PUrc>8|Vtb1YWwmT17k(YbfBW|eZyTB?iMORB1-TZ3 zp5ypHrSA4Nb{y(W;t5Ktw^y-;pw&#CAaIRn$11iHHk>(&Css|9?lPqiPL$qS56 zjU;>DOrB$$H3D`IAi*zH|6)Y8x>9l ztzritd6z5s@l~Ezoo!Db)y0qBqHQze@rD|;%JUD-@}2JT_qC^7WlpYQuas!o-(#+- z>ier$2qE;Qk>upzDiBPoE7Y{Bjl6?OJbuxzU%BF4Z46W5?OVmJhj`aqBHk~ZZIzBz z5N`&|yY)JrQ#11tK@JvZ+Cn4gbucudZ>?fyV5JrAI^R=GC&4lgLk zHA;m9dNjhSQE2-s0rconE&X3X*sv!8Yp7wAI9LfEK_|l)IvMOle<3y{U$W_UNW&i8 zj(&&dTRplgE%0P>LKV`zN3Z@|7g8Q-P7qb9M{iK`ea&nWV&k-);8rb3_cPUt7}58e zJyrXZ5uX5G@q;zq(;a7>Z1&vlEKgOd8o&AW1!>v`df;DGSwtDs(v!hsuU&IJFF>16 zB=Ufwdzy>Qgg4-iy=<`134z*_&{ihqRRRMS!KQ?zP--ums@C^3v2!4_kNImQ?d{F1 z6-jr1IVBzxOW1E=;e!fdL{B%d0Ol`!jbRmMvO^&9vWs%IiG5U{X}@&mpEt2Sm>+WI zUo^435aLzEREnL#UIoT$t{8io*>Z^ek4)N7V)SFfhilsLuh|youo`gvH5>M|tOaa1 zww-Vp9ctn?FVTA3>6cBVby)rFIWS4$n1wfSjF!N;+6=z$w|pJ_EHOy5G)=V9!{5R7ZHx zb+dGNEE&;9n?2L_sK9y-f{7p72xmt0EFmbm*=F`f%@EoDEk>bQy^adY=7a zgqIqN^fdc}2+uQa)3faV9pNR$GC)e}d9l%kkGgs+t-;dLdMrHv$VMy$pU|BOcw7nm zq@HRYspt8P+uVgM&C)JUx$~}O80J}bcCrbUc~;LPgl#Q zB(Ur~Xw_*?Y2Z15PS(#Y0cQ{$PoY;`-T-qwjqA-OAXq!xrhh5!{l#$GRw0g!1_UExmt#O{-*L`m1ef{{@e`eJt0%f$sq&Q4L>PN5bBukV1wh$<2@zHQ#T4LS0OS!oEO~-xg_5T~qPmE(EDqeT(n(y|#CaQLsrIpXa8W*7^gG~NrLKz>fJ6jVc}7_IrOisTygo`c zw&epf@&R!$KI0rbUp58)2?xY8lT;=CqA3HFQ;R3Kbu;l9us`N_prbi43svY%cXdVL zo0z`koouha0Y!CJvT{_sIZ+(sKTC3mtVnznXwN$>*ZO9U^@Nh^9VM2Q_z6Vl4k{PC z-JDp4+Ops20#2fI_bJ+;keiM>e%sXC$F%#-=fOU5^T&Di#Q8jX=rVyP zsQuQVwy#<1b5~3eds@-bHn~U_^6VdAmm={WdG_x4P%43Ct9IlN1j$?oT8|3Te!NK$ zdCwujwusk3Y0F1Tl`Z3~(w2{1v{nYfd6Q$xldXxj4#Yjj>2OcACfxU>_CszdC7x?2H8?KP7AG*j|%iIjf+l1t8wvzm7YNuUl zZRLMKt!=_V4gAp!DRR~&ck|9v{lW{~?;qm-9MrFUrTg%4zS*lk^;-8$zu{|i{exe1e|n1N7G_pz zM$(uD-rV6Nr;4!IY9tDr}3w5 zuAwqdCyk6zo>@hGduViw^6{12LuQPw6o|D{bdoA#R9!{h7+q11^;)Xxp-L-8Ga^(7 z@K(y*NMV7jmj+wMg$9kINiiB1qk_SdCzi~r-AZm9I4Qfy+(82b zJVFIx*g;og&?7WX3_Iv%3`T_N#ju0C^^|hJOTHDv>S;`comfo`CAoTWkjBNS)a;=v z;^c)Ix#nW3jZ?C@i^6g88B6VzS5o%0j1CI)5<5b*<_)j_^@1w%w0O)jn;SL;f+x6uVd%-{y)O< z8P}epiBcy-WT0Mt>xYSq;X4Q(tv*88QfzMvjeyw5n!+B0M34&N6!?P(gA(%wG7k{# z7F0Z^Damt&sD-@ca&A^(&?6pGgd+2pi{z9_#o#f=P`&`yQii4{a<+()=F7eI@7Gao zW|@m5gIXd99J0&S)7YeBk8ztBA!ei(P=$=*0xKIKo5R$Zk0NU4yVYes|=_nEY z7|p3i*e{}qaY{2gC?p7$?4W^lG)X{EMF7zUDhIb99s5$Ds3JGYDq@woRpd$$K07H@ zV7Jg141AH2C`Qv8r~;}DTSHUK&hwS3+_mIix0rl8rP4_Hz%Armba6+AkSrqW1!DdaQB5on^uBeF+pI@bFqe~8 zFAWhLpa$cB)gn#;5@!sYNT$auq)aIayqM#eYtBWGcXW1A=1v-51~3UwemhJRpqdjj z6=%v0>JKxWpsbCOhIxX*o>uqM^eiV!`fES=4k!g;!=!lntoo z7Vu(}6Q%Sn@*}Y4Qtk!{?}$>cS4B(*r5vH+E(*mct)0U2C{2(yhfruS^*c{}&(oj| z%G%mXd7YFNqXFfVbB<~RPOX$eU#5J~B1;WBN28`w<_Riqr?llb5v=+724rK{@yk%plz^kZV0nVIwJWsgQk- z2no|VvDz$ng(B)yk4q-jMJ~mQDbzvP2XOQiP?-8gkOJ~Yd&zfJ^f$QK3onTTWAGAuijs`@J0U*hVh3CRH|3iX z6qY$;@fpfQ7W}pv}Bz^ei%t7Kqc8|w@#C3 ztieeudB$k8!0eEw)-kIiZ-<4d+)HJ!Emgybh$lo!JJDIh-ltR)6YWFTcy2Wiyytf! zAuB^s3Tza4m|iZ9`BWzxW!6Vz-OxS1SF+S9+3vlVn-5T^g#tM9WywfE&Rca9tfSBd z1edUj$8_kVd(5b)CvZf#PF8o_Sl&Y7Btl@x9!u0#18M2=7^(Zw2bgtwNzhz^1TH}W z@1$Fe=UIyh6ZER07E?tHhQvl5ldTz9wMgY8^caLgUsSPk^xbL5kPhsUqMUNgij$GE zI1GxvrxPU=At!4=41K9SIz$1{yO`!Q@*YJ~aE5%S({*U$g47THXPf0z5vL%^c^!?y zQWd2fL+fXW-fz4py^fUVkc4=PWV%4g>oKDUgv{~`L$e}!V+y`~NzPM6jI3ZxN`!?f3T_h^vR|sDNlM2OHC$Oo zvtr~wM=8)uHmsv7kSCB@bj&ya<+*W!XrozA#d>*&ym3m}Ak6Bor_8g$9GMURjo<-F ziBoEw*^A}NlL%PB$l*Eg&?|Lg*Cc} zdT9Z{J71awLFum((^^RD#VlX1a3z*lXq80dDx|t^Mk)ppEH)4p(@O%b$*gOn%|^jy z0y+>z8+xhHMoOcJC9hdb{frrU6;oM#i77@w?IA|BG{h?~L=_A%Rfy`zirU8S_!OWNBR{dVNSWa*eY4VVq(85c0Spwu`=S4r(w|z=^F_B8BAV^kbA0 zr(yWEC}%nOU)LvP5$P2fRBk+{0=5T!sxTk3zR^)4i4)ANl&l}rgTlz(I!f1X3=*zt zxV5J1)$S+h4QZ61k4~dRJrYDJje;}9U3Hq=hC3KeBJ{e-Lb9gFy5L9K?n6zqM3H43 zR(zomIZFMd$uGz(tfzY!9*?UTr5ec*G5H>{*lz^ncN0z`aj3goI#lIyxQH|< zzexH&GqK>*DP~IYfY3#$9L+#Kje+`0sAF&>H?X_BSAI zzr~d9_LM(RxP_2w#qBiWG*t@uPtz1JpaT~J4wxk(KxM-gQ5kC?SLP*u0-o^2_4A2>*VgAVxu)&M~SJa<5TONHNNY z5_*_XhjD8J|Qi5}bY*HZR-+;4XjQ&@O{{Nq|Vr&_gaK2XQEbhmD zOPz&FZWETyj*taY{|~Ue$kmds0e@B7Jag!OimSW}GN2m%GN9nrIuTDP#z(8q897zX zf8i=hG)CfLgZMroqFg)&;bve3j6A%|W;4qa7&tU*gBUk=4HU$J<`LC?EUJuBc| z7A4w-ax*L>3qri-w9l11Y&o9t~?L zQ}5NS5QTBG#nW3m4Z!2!1;cR4qG>)+_-#LuD3Dd{nmZ{BPY|+fEJQA!P`z_=P|YQF?+}%AxZG3ajY>(dpymR`whIwBp(|?=s$)ENnnL}lEMKR3M-sP zXF}JYV_rm6?c$?y4W%CYnykRVCH@FmwR^QkCVWDSOgmu`dcY8*% z3VZ6^o(TzCa@4<5iM?)bxZ5*AAM=TQ^ls01y&~66Scx&c(5@9@L9zXy7^`R6N5y#T LPCKE^Q}%xV;=k5^