/** * ncurses_diff_viewer.h * NCurses-based interactive diff viewer for git changes */ #ifndef NCURSES_DIFF_VIEWER_H #define NCURSES_DIFF_VIEWER_H #include "common.h" #include #define MAX_FILES 100 #define MAX_FILENAME_LEN 256 #define MAX_FULL_FILE_LINES 2000 #define MAX_COMMITS 50 #define MAX_COMMIT_TITLE_LEN 256 #define MAX_AUTHOR_INITIALS 3 #define MAX_STASHES 20 #define MAX_BRANCHES 5 #define MAX_BRANCHNAME_LEN 256 typedef struct { char stash_info[512]; } NCursesStash; typedef struct { char filename[MAX_FILENAME_LEN]; char status; // 'M' = modified, 'A' = added, 'D' = deleted int marked_for_commit; // 1 if marked for commit, 0 otherwise int has_staged_changes; } NCursesChangedFile; typedef struct { char name[MAX_BRANCHNAME_LEN]; int status; int commits_ahead; int commits_behind; } NCursesBranches; typedef struct { char line[1024]; char type; // '+' = addition, '-' = deletion, ' ' = context, '@' = hunk header int is_diff_line; // 1 if this is a diff line, 0 if original file line int hunk_id; int is_staged; int line_number_old; int line_number_new; int is_context; } NCursesFileLine; typedef struct { char hash[16]; // Short commit hash char author_initials[MAX_AUTHOR_INITIALS]; char title[MAX_COMMIT_TITLE_LEN]; int is_pushed; // 1 if pushed to remote, 0 if local only } NCursesCommit; typedef enum { NCURSES_MODE_FILE_LIST, NCURSES_MODE_FILE_VIEW, NCURSES_MODE_COMMIT_LIST, NCURSES_MODE_COMMIT_VIEW, NCURSES_MODE_STASH_LIST, NCURSES_MODE_STASH_VIEW, NCURSES_MODE_BRANCH_LIST, NCURSES_MODE_BRANCH_VIEW } NCursesViewMode; typedef enum { SYNC_STATUS_IDLE, SYNC_STATUS_SYNCING_APPEARING, SYNC_STATUS_SYNCING_VISIBLE, SYNC_STATUS_SYNCING_DISAPPEARING, SYNC_STATUS_PUSHING_APPEARING, SYNC_STATUS_PUSHING_VISIBLE, SYNC_STATUS_PUSHING_DISAPPEARING, SYNC_STATUS_PULLING_APPEARING, SYNC_STATUS_PULLING_VISIBLE, SYNC_STATUS_PULLING_DISAPPEARING, SYNC_STATUS_SYNCED_APPEARING, SYNC_STATUS_SYNCED_VISIBLE, SYNC_STATUS_SYNCED_DISAPPEARING, SYNC_STATUS_PUSHED_APPEARING, SYNC_STATUS_PUSHED_VISIBLE, SYNC_STATUS_PUSHED_DISAPPEARING, SYNC_STATUS_PULLED_APPEARING, SYNC_STATUS_PULLED_VISIBLE, SYNC_STATUS_PULLED_DISAPPEARING } SyncStatus; typedef struct { NCursesChangedFile files[MAX_FILES]; int file_count; int selected_file; NCursesFileLine file_lines[MAX_FULL_FILE_LINES]; int file_line_count; int file_scroll_offset; int file_cursor_line; NCursesCommit commits[MAX_COMMITS]; int commit_count; int selected_commit; NCursesStash stashes[MAX_STASHES]; NCursesBranches branches[MAX_BRANCHES]; int stash_count; int branch_count; int selected_stash; int selected_branch; WINDOW *file_list_win; WINDOW *file_content_win; WINDOW *commit_list_win; WINDOW *stash_list_win; WINDOW *branch_list_win; WINDOW *status_bar_win; int terminal_width; int terminal_height; int file_panel_width; int file_panel_height; int commit_panel_height; int stash_panel_height; int branch_panel_height; int status_bar_height; NCursesViewMode current_mode; SyncStatus sync_status; int spinner_frame; time_t last_sync_time; int animation_frame; int text_char_count; int pushing_branch_index; int pulling_branch_index; SyncStatus branch_push_status; SyncStatus branch_pull_status; int branch_animation_frame; int branch_text_char_count; int critical_operation_in_progress; // Prevent fetching during critical ops // Background fetch management pid_t fetch_pid; // Process ID of background fetch int fetch_in_progress; // Flag to track if fetch is running // Branch-specific commits for hover functionality char branch_commits[MAX_COMMITS][2048]; // Larger buffer for formatted commits int branch_commit_count; char current_branch_for_commits[MAX_BRANCHNAME_LEN]; int branch_commits_scroll_offset; int branch_commits_cursor_line; int split_view_mode; // 0 = normal view, 1 = split staged/unstaged int staged_scroll_offset; // Scroll position for staged pane int active_pane; // 0 = unstaged, 1 = staged char current_file_path[512]; // Path of currently viewed file int total_hunks; // Total number of hunks in current file NCursesFileLine staged_lines[MAX_FULL_FILE_LINES]; // Separate storage for staged content int staged_line_count; // Number of lines in staged view int staged_cursor_line; // Fuzzy search state int fuzzy_search_active; // 1 if fuzzy search is active char fuzzy_search_query[256]; // Current search query int fuzzy_search_query_len; // Length of current query // Scored search results struct { int file_index; int score; } fuzzy_scored_files[MAX_FILES]; // Scored and sorted results int fuzzy_filtered_count; // Number of filtered files int fuzzy_selected_index; // Currently selected in fuzzy list int fuzzy_scroll_offset; // Scroll position in fuzzy list WINDOW *fuzzy_input_win; // Input window for search WINDOW *fuzzy_list_win; // Results list window // State tracking to prevent unnecessary redraws int fuzzy_needs_full_redraw; // 1 if full redraw needed (borders, input, list) int fuzzy_needs_input_redraw; // 1 if input field needs redraw int fuzzy_needs_list_redraw; // 1 if file list needs redraw char fuzzy_last_query[256]; // Last rendered query int fuzzy_last_selected; // Last rendered selection int fuzzy_last_scroll; // Last rendered scroll position int fuzzy_last_filtered_count; // Last rendered result count // Generic grep search state (for commits, stashes, branches) int grep_search_active; // 1 if grep search is active NCursesViewMode grep_search_mode; // Which mode grep is searching in char grep_search_query[256]; // Current search query int grep_search_query_len; // Length of current query // Scored grep search results struct { int item_index; int score; } grep_scored_items[MAX_COMMITS]; // Scored and sorted results (reuse MAX_COMMITS) int grep_filtered_count; // Number of filtered items int grep_selected_index; // Currently selected in grep list int grep_scroll_offset; // Scroll position in grep list WINDOW *grep_input_win; // Input window for search WINDOW *grep_list_win; // Results list window // State tracking for grep search rendering int grep_needs_full_redraw; // 1 if full redraw needed int grep_needs_input_redraw; // 1 if input field needs redraw int grep_needs_list_redraw; // 1 if item list needs redraw char grep_last_query[256]; // Last rendered query int grep_last_selected; // Last rendered selection int grep_last_scroll; // Last rendered scroll position int grep_last_filtered_count; // Last rendered result count } NCursesDiffViewer; /** * Initialize the ncurses diff viewer */ int init_ncurses_diff_viewer(NCursesDiffViewer *viewer); /** * Get list of changed files from git */ int get_ncurses_changed_files(NCursesDiffViewer *viewer); /** * Load full file content with diff highlighting */ int load_full_file_with_diff(NCursesDiffViewer *viewer, const char *filename); /** * Render the file list window */ void render_file_list_window(NCursesDiffViewer *viewer); /** * Render the file content window */ void render_file_content_window(NCursesDiffViewer *viewer); /** * Handle keyboard input for navigation */ int handle_ncurses_diff_input(NCursesDiffViewer *viewer, int key); /** * Run the ncurses diff viewer */ int run_ncurses_diff_viewer(void); /** * Clean up ncurses diff viewer resources */ void cleanup_ncurses_diff_viewer(NCursesDiffViewer *viewer); /** * Create temporary file with current working version */ int create_temp_file_with_changes(const char *filename, char *temp_path); /** * Create temporary file with git HEAD version */ int create_temp_file_git_version(const char *filename, char *temp_path); /** * Get commit history */ int get_commit_history(NCursesDiffViewer *viewer); /** * Toggle file marking for commit */ void toggle_file_mark(NCursesDiffViewer *viewer, int file_index); /** * Mark all files for commit */ void mark_all_files(NCursesDiffViewer *viewer); /** * Commit marked files with title and message */ int commit_marked_files(NCursesDiffViewer *viewer, const char *commit_title, const char *commit_message); /** * Push specific commit */ int push_commit(NCursesDiffViewer *viewer, int commit_index); /** * Pull commits from remote */ int pull_commits(NCursesDiffViewer *viewer); /** * Render the commit list window */ void render_commit_list_window(NCursesDiffViewer *viewer); /** * Get commit title and message input from user */ int get_commit_title_input(char *title, int max_len, char *message, int max_message_len); /** * Draw a box with rounded corners */ void draw_rounded_box(WINDOW *win); /** * Render the status bar */ void render_status_bar(NCursesDiffViewer *viewer); void render_branch_list_window(NCursesDiffViewer *viewer); /** * Update sync status and check for new files */ void update_sync_status(NCursesDiffViewer *viewer); int get_ncurses_git_stashes(NCursesDiffViewer *viewer); int get_ncurses_git_branches(NCursesDiffViewer *viewer); typedef enum { DELETE_LOCAL = 0, DELETE_REMOTE = 1, DELETE_BOTH = 2, DELETE_CANCEL = 3 } DeleteBranchOption; int get_branch_name_input(char *branch_name, int max_len); int create_git_branch(const char *branch_name); int get_rename_branch_input(const char *current_name, char *new_name, int max_len); int rename_git_branch(const char *old_name, const char *new_name); int show_delete_branch_dialog(const char *branch_name); void show_error_popup(const char *error_message); int get_git_remotes(char remotes[][256], int max_remotes); int show_upstream_selection_dialog(const char *branch_name, char *upstream_result, int max_len); int get_current_branch_name(char *branch_name, int max_len); int branch_has_upstream(const char *branch_name); int delete_git_branch(const char *branch_name, DeleteBranchOption option); int create_ncurses_git_stash(NCursesDiffViewer *viewer); int get_stash_name_input(char *stash_name, int max_len); void render_stash_list_window(NCursesDiffViewer *viewer); /** * Load commit details for viewing */ int load_commit_for_viewing(NCursesDiffViewer *viewer, const char *commit_hash); /** * Load stash details for viewing */ int load_stash_for_viewing(NCursesDiffViewer *viewer, int stash_index); /** * Load commits for a specific branch for the hover preview */ int load_branch_commits(NCursesDiffViewer *viewer, const char *branch_name); /** * Parse branch commits into navigable lines for branch view mode */ int parse_branch_commits_to_lines(NCursesDiffViewer *viewer); /** * Start background fetch process */ void start_background_fetch(NCursesDiffViewer *viewer); /** * Check if background fetch is complete and update UI accordingly */ void check_background_fetch(NCursesDiffViewer *viewer); /** * Move cursor up/down while skipping empty lines */ void move_cursor_smart(NCursesDiffViewer *viewer, int direction); /** * Move cursor up/down in unstaged pane with proper scrolling */ void move_cursor_smart_unstaged(NCursesDiffViewer *viewer, int direction); /** * Move cursor up/down in staged pane with proper scrolling */ void move_cursor_smart_staged(NCursesDiffViewer *viewer, int direction); /** * Helper function to wrap a long line into multiple display lines */ int wrap_line_to_width(const char *input_line, char wrapped_lines[][1024], int max_lines, int width); /** * Render a single line with proper wrapping and coloring */ int render_wrapped_line(WINDOW *win, const char *line, int start_y, int start_x, int width, int max_rows, int color_pair, int reverse); /** * Calculate how many display lines a wrapped line will take */ int calculate_wrapped_line_height(const char *line, int width); /** * Load file preview showing the top of the file content */ int load_file_preview(NCursesDiffViewer *viewer, const char *filename); /** * Update preview based on current mode and selection */ void update_preview_for_current_selection(NCursesDiffViewer *viewer); int get_github_credentials(char *username, int username_len, char *token, int token_len); int execute_git_with_auth(const char *base_cmd, const char *username, const char *token); int stage_hunk_by_line(NCursesDiffViewer *viewer, int line_index); int load_file_with_staging_info(NCursesDiffViewer *viewer, const char *filename); void rebuild_staged_view(NCursesDiffViewer *viewer); int apply_staged_changes(NCursesDiffViewer *viewer); int reset_staged_changes(NCursesDiffViewer *viewer); int unstage_line_from_git(NCursesDiffViewer *viewer, int staged_line_index); int commit_staged_changes_only(NCursesDiffViewer *viewer, const char *commit_title, const char *commit_message); void rebuild_staged_view_from_git(NCursesDiffViewer *viewer); /** * Initialize fuzzy search state */ void init_fuzzy_search(NCursesDiffViewer *viewer); /** * Cleanup fuzzy search windows */ void cleanup_fuzzy_search(NCursesDiffViewer *viewer); /** * Enter fuzzy search mode */ void enter_fuzzy_search_mode(NCursesDiffViewer *viewer); /** * Exit fuzzy search mode */ void exit_fuzzy_search_mode(NCursesDiffViewer *viewer); /** * Update fuzzy search filter based on current query */ void update_fuzzy_filter(NCursesDiffViewer *viewer); /** * Render fuzzy search UI */ void render_fuzzy_search(NCursesDiffViewer *viewer); /** * Render fuzzy search input field only */ void render_fuzzy_input(NCursesDiffViewer *viewer); /** * Render fuzzy search file list content only */ void render_fuzzy_list_content(NCursesDiffViewer *viewer); /** * Create fuzzy search windows with borders (one-time setup) */ void create_fuzzy_windows_with_borders(NCursesDiffViewer *viewer); /** * Handle fuzzy search input */ int handle_fuzzy_search_input(NCursesDiffViewer *viewer, int key); /** * Select file from fuzzy search results */ void select_fuzzy_file(NCursesDiffViewer *viewer); /** * Initialize grep search state */ void init_grep_search(NCursesDiffViewer *viewer); /** * Cleanup grep search windows */ void cleanup_grep_search(NCursesDiffViewer *viewer); /** * Enter grep search mode for current view mode */ void enter_grep_search_mode(NCursesDiffViewer *viewer); /** * Exit grep search mode */ void exit_grep_search_mode(NCursesDiffViewer *viewer); /** * Update grep search filter based on current query and mode */ void update_grep_filter(NCursesDiffViewer *viewer); /** * Render grep search UI */ void render_grep_search(NCursesDiffViewer *viewer); /** * Handle grep search input */ int handle_grep_search_input(NCursesDiffViewer *viewer, int key); /** * Select item from grep search results */ void select_grep_item(NCursesDiffViewer *viewer); /** * Calculate grep match score for text content */ int calculate_grep_score(const char *pattern, const char *text); #endif // NCURSES_DIFF_VIEWER_H