when grepping for commits, searching for author initials is now possible, and will be prioritized first. this means typing ma for marcus for example will give you all commits from marcus. if no hits are found on initials it will apply that grep to the name/titles. also some highlighing has been added to commit grep updated stash grep to include possiblility to grep for time and branches. this means you can filter for branches and times
564 lines
16 KiB
C
564 lines
16 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;
|
|
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);
|
|
|
|
/**
|
|
* 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
|