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
This commit is contained in:
2025-07-08 15:48:59 -06:00
parent a039053e9f
commit 5da517315f
4 changed files with 212 additions and 126 deletions

View File

@@ -111,10 +111,12 @@ int init_ncurses_diff_viewer(NCursesDiffViewer *viewer) {
// Initialize grep search
init_grep_search(viewer);
viewer->commit_scroll_offset = 0;
viewer->selected_file = 0;
viewer->file_scroll_offset = 0;
viewer->file_cursor_line = 0;
viewer->selected_stash = 0;
viewer->stash_scroll_offset = 0;
viewer->selected_branch = 0;
viewer->current_mode = NCURSES_MODE_FILE_LIST;
viewer->sync_status = SYNC_STATUS_IDLE;
@@ -1815,13 +1817,17 @@ int amend_commit(NCursesDiffViewer *viewer) {
// Helper function to check if a character is safe for PAT/username input
static int is_safe_input_char(int ch) {
// Allow most printable ASCII but exclude problematic characters
if (ch < 32 || ch > 126) return 0; // Non-printable
if (ch == '?' || ch == 127) return 0; // Question mark and DEL
if (ch == '\x1b') return 0; // ESC
return 1; // Allow everything else
if (ch < 32 || ch > 126)
return 0; // Non-printable
if (ch == '?' || ch == 127)
return 0; // Question mark and DEL
if (ch == '\x1b')
return 0; // ESC
return 1; // Allow everything else
}
int get_single_input(const char *title, const char *prompt, char *input, int input_len, int is_password) {
int get_single_input(const char *title, const char *prompt, char *input,
int input_len, int is_password) {
if (!input)
return 0;
@@ -1832,11 +1838,14 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
fclose(debug_file);
}
// Make dialog much wider for PAT input - 80% of screen width, minimum 80 characters
// Make dialog much wider for PAT input - 80% of screen width, minimum 80
// characters
int dialog_width = (COLS * 8) / 10;
if (dialog_width < 80) dialog_width = 80;
if (dialog_width > 120) dialog_width = 120;
if (dialog_width < 80)
dialog_width = 80;
if (dialog_width > 120)
dialog_width = 120;
int dialog_height = 4; // Keep it simple and compact
int start_x = (COLS - dialog_width) / 2;
int start_y = (LINES - dialog_height) / 2;
@@ -1852,7 +1861,7 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
char temp_input[512] = ""; // Larger buffer for long PATs
int input_pos = 0;
// Enable keypad for special keys and disable timeout for paste
keypad(input_win, TRUE);
nodelay(input_win, FALSE);
@@ -1869,15 +1878,15 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
} else {
mvwprintw(input_win, 1, 2, "%s:", prompt);
}
// Clear the input area
for (int x = 2; x < dialog_width - 2; x++) {
mvwaddch(input_win, 2, x, ' ');
}
// Input area - much simpler approach
int max_display = dialog_width - 6; // Leave room for borders and padding
if (is_password) {
// For passwords, show simple asterisks without complex scrolling
mvwprintw(input_win, 2, 2, "");
@@ -1896,7 +1905,8 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
// Simple cursor positioning
int cursor_x = 2 + ((input_pos > max_display) ? max_display : input_pos);
if (cursor_x >= dialog_width - 1) cursor_x = dialog_width - 2;
if (cursor_x >= dialog_width - 1)
cursor_x = dialog_width - 2;
wmove(input_win, 2, cursor_x);
wrefresh(input_win);
@@ -1905,7 +1915,8 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
// Debug log key presses
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Key pressed: %d (0x%x) '%c'\n", ch, ch, (ch >= 32 && ch <= 126) ? ch : '?');
fprintf(debug_file, "Key pressed: %d (0x%x) '%c'\n", ch, ch,
(ch >= 32 && ch <= 126) ? ch : '?');
fclose(debug_file);
}
@@ -1925,15 +1936,17 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
case KEY_ENTER:
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Enter key detected (key: %d), input length: %d\n", ch, input_pos);
fprintf(debug_file, "Enter key detected (key: %d), input length: %d\n",
ch, input_pos);
fclose(debug_file);
}
if (input_pos > 0) {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
if (is_password) {
fprintf(debug_file, "Input completed, length: %d characters\n", input_pos);
fprintf(debug_file, "Input completed, length: %d characters\n",
input_pos);
} else {
fprintf(debug_file, "Input completed: '%s'\n", temp_input);
}
@@ -1961,28 +1974,31 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
case 200: // Bracketed paste start
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Paste detected (key: %d), starting paste mode...\n", ch);
fprintf(debug_file,
"Paste detected (key: %d), starting paste mode...\n", ch);
fclose(debug_file);
}
// Clear current input and start fresh for paste
input_pos = 0;
temp_input[0] = '\0';
// Set non-blocking and read all available characters
nodelay(input_win, TRUE);
usleep(50000); // Longer delay to let entire paste buffer fill
int paste_count = 0;
char paste_buffer[512] = {0};
int paste_pos = 0;
// Read all available characters
while (paste_pos < 511) {
int paste_ch = wgetch(input_win);
if (paste_ch == ERR) break; // No more characters
if (paste_ch == 201) break; // Bracketed paste end
if (paste_ch == ERR)
break; // No more characters
if (paste_ch == 201)
break; // Bracketed paste end
// Only collect printable ASCII characters (exclude backspaces!)
if (paste_ch >= 32 && paste_ch <= 126) {
paste_buffer[paste_pos] = paste_ch;
@@ -1991,27 +2007,26 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
}
// Skip backspaces (263), control chars, etc.
}
nodelay(input_win, FALSE); // Back to blocking mode
paste_buffer[paste_pos] = '\0';
// Clean up the pasted content - remove spaces and special chars
for (int i = 0; i < paste_pos && input_pos < 511; i++) {
char c = paste_buffer[i];
// Only keep alphanumeric and safe special characters for PAT
if ((c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
c == '_' || c == '-') {
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || c == '_' || c == '-') {
temp_input[input_pos] = c;
input_pos++;
}
}
temp_input[input_pos] = '\0';
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Paste cleanup: raw chars=%d, final chars=%d\n", paste_count, input_pos);
fprintf(debug_file, "Paste cleanup: raw chars=%d, final chars=%d\n",
paste_count, input_pos);
if (is_password) {
fprintf(debug_file, "Final PAT length: %d\n", input_pos);
} else {
@@ -2022,7 +2037,7 @@ int get_single_input(const char *title, const char *prompt, char *input, int inp
break;
default:
// Accept basic ASCII characters only (no spaces for passwords)
// Accept basic ASCII characters only (no spaces for passwords)
if (ch >= 32 && ch <= 126 && input_pos < 511) {
if (is_password && ch == ' ') {
// Skip spaces in password input
@@ -2049,7 +2064,8 @@ int get_github_credentials(char *username, int username_len, char *token,
}
// Get username first
if (!get_single_input("GitHub Authentication", "Username", username, username_len, 0)) {
if (!get_single_input("GitHub Authentication", "Username", username,
username_len, 0)) {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Username input cancelled\n");
@@ -2133,7 +2149,7 @@ int execute_git_with_auth(const char *base_cmd, const char *username,
// Create authenticated command using environment variables - SAFE APPROACH
char auth_cmd[4096];
char auth_url[2048];
if (strstr(remote_url, "https://github.com/")) {
char *repo_part = remote_url + strlen("https://github.com/");
snprintf(auth_url, sizeof(auth_url), "https://%s:%s@github.com/%s",
@@ -2151,15 +2167,15 @@ int execute_git_with_auth(const char *base_cmd, const char *username,
} else {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "ERROR: Unsupported remote URL format: %s\n", remote_url);
fprintf(debug_file, "ERROR: Unsupported remote URL format: %s\n",
remote_url);
fclose(debug_file);
}
return 1;
}
// Create a simple git push command with authenticated URL
snprintf(auth_cmd, sizeof(auth_cmd),
"git push %s", auth_url);
snprintf(auth_cmd, sizeof(auth_cmd), "git push %s", auth_url);
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
@@ -2170,10 +2186,11 @@ int execute_git_with_auth(const char *base_cmd, const char *username,
// Execute with authentication - this does NOT modify the repository
int result = system(auth_cmd);
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "SAFE: No repository modification needed - git -c used\n");
fprintf(debug_file,
"SAFE: No repository modification needed - git -c used\n");
fprintf(debug_file, "Git command result: %d\n", result);
if (result == 0) {
fprintf(debug_file, "SUCCESS: Git push with authentication succeeded\n");
@@ -2288,9 +2305,13 @@ int push_commit(NCursesDiffViewer *viewer, int commit_index) {
// Try push without credentials first - force git to fail without prompting
// Use multiple methods to ensure git doesn't prompt for credentials
result = system(is_diverged
? "GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 SSH_ASKPASS=/bin/false git push --force-with-lease origin </dev/null >/dev/null 2>/dev/null"
: "GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 SSH_ASKPASS=/bin/false git push origin </dev/null >/dev/null 2>/dev/null");
result =
system(is_diverged ? "GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 "
"SSH_ASKPASS=/bin/false git push --force-with-lease "
"origin </dev/null >/dev/null 2>/dev/null"
: "GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 "
"SSH_ASKPASS=/bin/false git push origin </dev/null "
">/dev/null 2>/dev/null");
// If push failed, immediately try with authentication
if (result != 0) {
@@ -2308,14 +2329,14 @@ int push_commit(NCursesDiffViewer *viewer, int commit_index) {
endwin();
clear();
refresh();
// Reinitialize ncurses for clean credential dialog
initscr();
noecho();
cbreak();
keypad(stdscr, TRUE);
start_color();
// Initialize color pairs for the dialog
init_pair(1, COLOR_WHITE, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
@@ -2323,7 +2344,7 @@ int push_commit(NCursesDiffViewer *viewer, int commit_index) {
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_pair(5, COLOR_RED, COLOR_BLACK);
init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
clear();
refresh();
@@ -2337,10 +2358,11 @@ int push_commit(NCursesDiffViewer *viewer, int commit_index) {
sizeof(token))) {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Credentials obtained successfully, attempting authenticated push\n");
fprintf(debug_file, "Credentials obtained successfully, attempting "
"authenticated push\n");
fclose(debug_file);
}
// Try authenticated push
result = execute_git_with_auth(push_cmd, username, token);
@@ -2616,17 +2638,18 @@ void render_commit_list_window(NCursesDiffViewer *viewer) {
for (int i = 0; i < max_commits_visible; i++) {
int y = i + 1;
int commit_index = i + viewer->commit_scroll_offset;
// Skip if no more commits
if (i >= viewer->commit_count)
if (commit_index >= viewer->commit_count)
continue;
// Check if this commit line should be highlighted
int is_selected_commit = (i == viewer->selected_commit &&
int is_selected_commit = (commit_index == viewer->selected_commit &&
viewer->current_mode == NCURSES_MODE_COMMIT_LIST);
// Check if this commit is currently being viewed
int is_being_viewed = (i == viewer->selected_commit &&
int is_being_viewed = (commit_index == viewer->selected_commit &&
viewer->current_mode == NCURSES_MODE_COMMIT_VIEW);
// Apply line highlight if selected
@@ -2657,15 +2680,16 @@ void render_commit_list_window(NCursesDiffViewer *viewer) {
// Color commit hash based on push status: yellow for pushed, red for
// unpushed
if (viewer->commits[i].is_pushed) {
if (viewer->commits[commit_index].is_pushed) {
wattron(viewer->commit_list_win,
COLOR_PAIR(4)); // Yellow for pushed commits
} else {
wattron(viewer->commit_list_win,
COLOR_PAIR(2)); // Red for unpushed commits
}
mvwprintw(viewer->commit_list_win, y, 2, "%s", viewer->commits[i].hash);
if (viewer->commits[i].is_pushed) {
mvwprintw(viewer->commit_list_win, y, 2, "%s",
viewer->commits[commit_index].hash);
if (viewer->commits[commit_index].is_pushed) {
wattroff(viewer->commit_list_win, COLOR_PAIR(4));
} else {
wattroff(viewer->commit_list_win, COLOR_PAIR(2));
@@ -2674,7 +2698,7 @@ void render_commit_list_window(NCursesDiffViewer *viewer) {
// Show author initials - use cyan for a subtle contrast
wattron(viewer->commit_list_win, COLOR_PAIR(3)); // Cyan for author initials
mvwprintw(viewer->commit_list_win, y, 10, "%s",
viewer->commits[i].author_initials);
viewer->commits[commit_index].author_initials);
wattroff(viewer->commit_list_win, COLOR_PAIR(3));
if (is_selected_commit) {
@@ -2684,12 +2708,13 @@ void render_commit_list_window(NCursesDiffViewer *viewer) {
// 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);
if ((int)strlen(viewer->commits[commit_index].title) > max_title_len) {
strncpy(truncated_title, viewer->commits[commit_index].title,
max_title_len - 2);
truncated_title[max_title_len - 2] = '\0';
strcat(truncated_title, "..");
} else {
strcpy(truncated_title, viewer->commits[i].title);
strcpy(truncated_title, viewer->commits[commit_index].title);
}
mvwprintw(viewer->commit_list_win, y, 13, "%s", truncated_title);
@@ -2792,8 +2817,8 @@ void render_file_content_window(NCursesDiffViewer *viewer) {
// Render the line with wrapping
int rows_used = render_wrapped_line(
viewer->file_content_win, line->line, y, 1, width - 2, line_height,
color_pair, is_cursor_line);
viewer->file_content_win, line->line, y, 1, width - 2,
line_height, color_pair, is_cursor_line);
display_count += rows_used;
}
} else {
@@ -2882,9 +2907,9 @@ void render_file_content_window(NCursesDiffViewer *viewer) {
unstaged_display_count += rows_used;
} else {
// Regular line rendering
int rows_used =
render_wrapped_line(viewer->file_content_win, line->line, y, 1, width - 2,
line_height, color_pair, is_cursor_line);
int rows_used = render_wrapped_line(viewer->file_content_win, line->line,
y, 1, width - 2, line_height,
color_pair, is_cursor_line);
unstaged_display_count += rows_used;
}
}
@@ -3880,6 +3905,16 @@ int handle_ncurses_diff_input(NCursesDiffViewer *viewer, int key) {
case 'k':
if (viewer->selected_commit > 0) {
viewer->selected_commit--;
// Calculate the scroll boundaries
int max_commits_visible = viewer->commit_panel_height - 2;
int scroll_threshhold = 2;
if (viewer->selected_commit <
viewer->commit_scroll_offset + scroll_threshhold) {
if (viewer->commit_scroll_offset > 0) {
viewer->commit_scroll_offset--;
}
}
// Auto-preview the selected commit
if (viewer->commit_count > 0) {
load_commit_for_viewing(
@@ -3892,6 +3927,17 @@ int handle_ncurses_diff_input(NCursesDiffViewer *viewer, int key) {
case 'j':
if (viewer->selected_commit < viewer->commit_count - 1) {
viewer->selected_commit++;
int max_commits_visible = viewer->commit_panel_height - 2;
int scroll_threshhold = 2;
if (viewer->selected_commit >= viewer->commit_scroll_offset +
max_commits_visible -
scroll_threshhold) {
if (viewer->commit_scroll_offset <
viewer->commit_count - max_commits_visible) {
viewer->commit_scroll_offset++;
}
}
// Auto-preview the selected commit
if (viewer->commit_count > 0) {
load_commit_for_viewing(
@@ -3984,6 +4030,19 @@ int handle_ncurses_diff_input(NCursesDiffViewer *viewer, int key) {
case 'k':
if (viewer->selected_stash > 0) {
viewer->selected_stash--;
// Calculate scroll boundaries
int max_stashes_visible = viewer->stash_panel_height - 2;
int scroll_threshold = 2;
// Scroll up if cursor is getting too close to top
if (viewer->selected_stash <
viewer->stash_scroll_offset + scroll_threshold) {
if (viewer->stash_scroll_offset > 0) {
viewer->stash_scroll_offset--;
}
}
// Auto-preview the selected stash
if (viewer->stash_count > 0) {
load_stash_for_viewing(viewer, viewer->selected_stash);
@@ -3995,6 +4054,21 @@ int handle_ncurses_diff_input(NCursesDiffViewer *viewer, int key) {
case 'j':
if (viewer->selected_stash < viewer->stash_count - 1) {
viewer->selected_stash++;
// Calculate scroll boundaries
int max_stashes_visible = viewer->stash_panel_height - 2;
int scroll_threshold = 2;
// Scroll down if cursor is getting too close to bottom
if (viewer->selected_stash >= viewer->stash_scroll_offset +
max_stashes_visible -
scroll_threshold) {
if (viewer->stash_scroll_offset <
viewer->stash_count - max_stashes_visible) {
viewer->stash_scroll_offset++;
}
}
// Auto-preview the selected stash
if (viewer->stash_count > 0) {
load_stash_for_viewing(viewer, viewer->selected_stash);
@@ -5746,15 +5820,20 @@ void render_stash_list_window(NCursesDiffViewer *viewer) {
// Show "No stashes" message
mvwprintw(viewer->stash_list_win, 1, 2, "No stashes available");
} else {
for (int i = 0; i < max_stashes_visible && i < viewer->stash_count; i++) {
for (int i = 0; i < max_stashes_visible; i++) {
int y = i + 1;
int stash_index = i + viewer->stash_scroll_offset;
// Skip if no more stashes
if (stash_index >= viewer->stash_count)
continue;
// Check if this stash line should be highlighted
int is_selected_stash = (i == viewer->selected_stash &&
int is_selected_stash = (stash_index == viewer->selected_stash &&
viewer->current_mode == NCURSES_MODE_STASH_LIST);
// Check if this stash is currently being viewed
int is_being_viewed = (i == viewer->selected_stash &&
int is_being_viewed = (stash_index == viewer->selected_stash &&
viewer->current_mode == NCURSES_MODE_STASH_VIEW);
// Apply line highlight if selected
@@ -5779,15 +5858,16 @@ void render_stash_list_window(NCursesDiffViewer *viewer) {
}
// Show stash info (truncated to fit panel)
int max_stash_len = viewer->file_panel_width - 4;
int max_stash_len = viewer->file_panel_width - 6;
char truncated_stash[256];
if ((int)strlen(viewer->stashes[i].stash_info) > max_stash_len) {
strncpy(truncated_stash, viewer->stashes[i].stash_info,
if ((int)strlen(viewer->stashes[stash_index].stash_info) >
max_stash_len) {
strncpy(truncated_stash, viewer->stashes[stash_index].stash_info,
max_stash_len - 2);
truncated_stash[max_stash_len - 2] = '\0';
strcat(truncated_stash, "..");
} else {
strcpy(truncated_stash, viewer->stashes[i].stash_info);
strcpy(truncated_stash, viewer->stashes[stash_index].stash_info);
}
// Show stash info with yellow time part and white message part
@@ -5820,39 +5900,39 @@ void render_stash_list_window(NCursesDiffViewer *viewer) {
// Print time part in yellow
wattron(viewer->stash_list_win, COLOR_PAIR(4));
mvwprintw(viewer->stash_list_win, y, 2, "%.*s", time_len,
mvwprintw(viewer->stash_list_win, y, 4, "%.*s", time_len,
truncated_stash);
// Add extra space for single digit times
if (is_single_digit) {
mvwprintw(viewer->stash_list_win, y, 2 + time_len, " ");
mvwprintw(viewer->stash_list_win, y, 4 + time_len, " ");
time_len++;
}
// Print " On [branch]:" part in yellow
mvwprintw(viewer->stash_list_win, y, 2 + time_len, "%.*s",
mvwprintw(viewer->stash_list_win, y, 4 + time_len, "%.*s",
(int)(colon_pos - on_pos + 1), on_pos);
wattroff(viewer->stash_list_win, COLOR_PAIR(4));
// Print message part in normal color (white)
mvwprintw(viewer->stash_list_win, y,
2 + time_part_len + (is_single_digit ? 1 : 0), "%s",
4 + time_part_len + (is_single_digit ? 1 : 0), "%s",
colon_pos + 1);
} else {
// Fallback: print entire yellow part if no " On " found
wattron(viewer->stash_list_win, COLOR_PAIR(4));
mvwprintw(viewer->stash_list_win, y, 2, "%.*s", time_part_len,
mvwprintw(viewer->stash_list_win, y, 4, "%.*s", time_part_len,
truncated_stash);
wattroff(viewer->stash_list_win, COLOR_PAIR(4));
// Print message part in normal color (white)
mvwprintw(viewer->stash_list_win, y, 2 + time_part_len, "%s",
mvwprintw(viewer->stash_list_win, y, 4 + time_part_len, "%s",
colon_pos + 1);
}
} else {
// Fallback: print entire string in yellow if no colon found
wattron(viewer->stash_list_win, COLOR_PAIR(4));
mvwprintw(viewer->stash_list_win, y, 2, "%s", truncated_stash);
mvwprintw(viewer->stash_list_win, y, 4, "%s", truncated_stash);
wattroff(viewer->stash_list_win, COLOR_PAIR(4));
}
@@ -5914,14 +5994,14 @@ void render_branch_list_window(NCursesDiffViewer *viewer) {
// Create status indicator
if (viewer->branches[i].commits_ahead > 0 &&
viewer->branches[i].commits_behind > 0) {
snprintf(status_indicator, sizeof(status_indicator), " %d↑%d↓",
snprintf(status_indicator, sizeof(status_indicator), " %d↑%d↓",
viewer->branches[i].commits_ahead,
viewer->branches[i].commits_behind);
} else if (viewer->branches[i].commits_ahead > 0) {
snprintf(status_indicator, sizeof(status_indicator), " %d↑",
snprintf(status_indicator, sizeof(status_indicator), " %d↑",
viewer->branches[i].commits_ahead);
} else if (viewer->branches[i].commits_behind > 0) {
snprintf(status_indicator, sizeof(status_indicator), " %d↓",
snprintf(status_indicator, sizeof(status_indicator), " %d↓",
viewer->branches[i].commits_behind);
}

View File

@@ -99,11 +99,13 @@ typedef struct {
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;
@@ -155,57 +157,58 @@ typedef struct {
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
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
} 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
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
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
} 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
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;
@@ -407,13 +410,14 @@ 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);
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);
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
@@ -430,7 +434,8 @@ int load_file_preview(NCursesDiffViewer *viewer, const char *filename);
*/
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_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);
@@ -560,6 +565,7 @@ 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);
void extract_branch_from_stash(const char *stash_info, char *branch_name,
int max_len);
#endif // NCURSES_DIFF_VIEWER_H

Binary file not shown.

BIN
shell

Binary file not shown.