Files
FERRUM/ncurses_diff_viewer.h
marcusDenslow 5da517315f added scrolling in the commits pane
added scrolling in the commits pane so that you can view older commits without going into them. also tried adding this to the stashes pane but because of an older bug it broke and is not working right. its added to the things to fix list
2025-07-08 15:48:59 -06:00

572 lines
15 KiB
C

/**
* 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 <ncurses.h>
#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;
int commit_scroll_offset;
NCursesStash stashes[MAX_STASHES];
NCursesBranches branches[MAX_BRANCHES];
int stash_count;
int branch_count;
int selected_stash;
int stash_scroll_offset;
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_single_input(const char *title, const char *prompt, char *input,
int input_len, int is_password);
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);
/**
* Extract branch name from stash info
*/
void extract_branch_from_stash(const char *stash_info, char *branch_name,
int max_len);
#endif // NCURSES_DIFF_VIEWER_H