nnn

mirror of https://github.com/jarun/nnn
Log | Files | Refs | README | LICENSE

commit 8d72835640d8e3402958d503f2d844cb59590780
parent 2ec3ad4302dd0c08a4788953d5896a3975072fc8
Author: Arun Prakash Jana <engineerarun@gmail.com>
Date:   Sun, 16 Nov 2025 11:48:18 +0530

Show window for file stats

Diffstat:
Msrc/.clang-tidy | 2+-
Msrc/nnn.c | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/nnn.h | 1+
3 files changed, 252 insertions(+), 7 deletions(-)

diff --git a/src/.clang-tidy b/src/.clang-tidy @@ -42,5 +42,5 @@ CheckOptions: - key: fuchsia-restrict-system-includes.Includes value: '*,-stdint.h,-stdbool.h' - key: readability-function-size.StatementThreshold - value: '950' + value: '960' ... diff --git a/src/nnn.c b/src/nnn.c @@ -4783,9 +4783,9 @@ static bool get_output(char *file, char *arg1, char *arg2, int fdout, bool page) } /* - * Follows the stat(1) output closely + * Helper function to generate stats content for a file */ -static bool show_stats(char *fpath) +static bool generate_file_stats(char *filepath, char **content_out, size_t *content_len_out) { static char * const cmds[] = { #ifdef FILE_MIME_OPTS @@ -4805,12 +4805,249 @@ static bool show_stats(char *fpath) return FALSE; while (r) - get_output(cmds[--r], fpath, NULL, fd, FALSE); + get_output(cmds[--r], filepath, NULL, fd, FALSE); close(fd); - spawn(pager, g_tmpfpath, NULL, NULL, F_CLI | F_TTY); + /* Read the temp file content */ + FILE *fp = fopen(g_tmpfpath, "r"); + if (!fp) { + unlink(g_tmpfpath); + return FALSE; + } + + /* Allocate buffer for content */ + char *content = NULL; + size_t content_size = 0; + size_t content_len = 0; + size_t line_len = 0; + char line[4096]; + + /* Read all content from file */ + while (fgets(line, sizeof(line), fp)) { + line_len = xstrlen(line); + if (content_len + line_len + 1 > content_size) { + size_t new_size = content_size ? content_size * 2 : 8192; + char *new_content = realloc(content, new_size); + if (!new_content) { + free(content); + fclose(fp); + unlink(g_tmpfpath); + return FALSE; + } + content = new_content; + content_size = new_size; + } + + if (content) { + xstrsncpy(content + content_len, line, content_size - content_len); + content_len += line_len; + } + } + fclose(fp); unlink(g_tmpfpath); + + if (!content || content_len == 0) { + free(content); + return FALSE; + } + + *content_out = content; + *content_len_out = content_len; + return TRUE; +} + +/* + * Follows the stat(1) output closely + * Displays output in a floating ncurses window + */ +static bool show_stats(char *fpath, enum action *action) +{ + char *content = NULL; + size_t content_len = 0; + + if (!generate_file_stats(fpath, &content, &content_len)) + return FALSE; + + /* Calculate window dimensions */ + int win_height = MIN(20, xlines - 4); + int win_width = (xcols * 3) / 4; /* 75% of terminal width */ + + /* Ensure minimum window size */ + if (win_height < 5) + win_height = 5; + if (win_width < 20) + win_width = 20; + + int start_y = (xlines - win_height) / 2; + int start_x = (xcols - win_width) / 2; + + /* Create floating window */ + WINDOW *win = newwin(win_height, win_width, start_y, start_x); + if (!win) { + free(content); + return FALSE; + } + + keypad(win, TRUE); /* Enable special keys */ + + /* Count total lines once */ + int line_count = 0; + for (size_t i = 0; i < content_len; i++) { + if (content[i] == '\n') + line_count++; + } + if (content_len > 0 && content[content_len - 1] != '\n') + line_count++; + + /* Display content with scrolling */ + int max_lines = win_height - 3; /* Account for border and help line */ + int max_display_width = win_width - 2; /* Account for border */ + int scroll_offset = 0; + int hscroll_offset = 0; /* Horizontal scroll offset */ + int max_line_width = 0; /* Maximum line width in content */ + int ch; + bool done = FALSE; + + /* Calculate maximum line width */ + char *line_ptr = content; + while (line_ptr < content + content_len) { + char *next = strchr(line_ptr, '\n'); + size_t line_len = next ? (size_t)(next - line_ptr) : content_len - (line_ptr - content); + if (line_len > (size_t)max_line_width) + max_line_width = (int)line_len; + if (next) + line_ptr = next + 1; + else + break; + } + + while (!done) { + werase(win); + box(win, 0, 0); + mvwaddstr(win, 0, 2, " File Statistics "); + + /* Skip to scroll offset */ + char *line_start = content; + for (int i = 0; i < scroll_offset && line_start < content + content_len; i++) { + char *next = strchr(line_start, '\n'); + if (next) + line_start = next + 1; + else + break; + } + + /* Display visible lines */ + char *current = line_start; + int display_line = 1; + while (display_line <= max_lines && current < content + content_len) { + char *next = strchr(current, '\n'); + size_t line_len = next ? (size_t)(next - current) : content_len - (current - content); + + /* Apply horizontal scrolling */ + char *display_start = current; + size_t display_len = line_len; + + if (hscroll_offset > 0 && (size_t)hscroll_offset < line_len) { + display_start = current + hscroll_offset; + display_len = line_len - hscroll_offset; + } + + /* Truncate if still too long for display */ + if (display_len > (size_t)max_display_width) { + mvwaddnstr(win, display_line, 1, display_start, max_display_width); + } else { + mvwaddnstr(win, display_line, 1, display_start, display_len); + } + + if (next) { + current = next + 1; + display_line++; + } else { + break; + } + } + + /* Show vertical scroll indicators if needed */ + if (line_count > max_lines) { + if (scroll_offset > 0) + mvwaddch(win, 1, win_width - 2, '^'); + if (scroll_offset + max_lines < line_count) + mvwaddch(win, max_lines, win_width - 2, 'v'); + } + + /* Show horizontal scroll indicators if needed */ + if (max_line_width > max_display_width) { + if (hscroll_offset > 0) + mvwaddch(win, max_lines + 1, 1, '<'); + if (hscroll_offset + max_display_width < max_line_width) + mvwaddch(win, max_lines + 1, win_width - 2, '>'); + } + + /* Show help hint */ + mvwaddstr(win, win_height - 1, 1, "q/ESC: close n/p: next/prev arrows: scroll"); + + wrefresh(win); + + /* Get user input from window */ + ch = wgetch(win); + switch (ch) { + case 'q': + case 'Q': + case ESC: + done = TRUE; + break; + case 'n': + *action = SEL_NEXT; + done = TRUE; + break; + case 'p': + *action = SEL_PREV; + done = TRUE; + break; + case KEY_UP: + case 'k': + case 'K': + if (scroll_offset > 0) + scroll_offset--; + break; + case KEY_DOWN: + case 'j': + case 'J': + if (scroll_offset + max_lines < line_count) + scroll_offset++; + break; + case KEY_PPAGE: + scroll_offset = MAX(0, scroll_offset - max_lines); + break; + case KEY_NPAGE: + scroll_offset = MIN(line_count - max_lines, scroll_offset + max_lines); + break; + case KEY_HOME: + scroll_offset = 0; + break; + case KEY_END: + scroll_offset = MAX(0, line_count - max_lines); + break; + case KEY_LEFT: + case 'h': + case 'H': + if (hscroll_offset > 0) + hscroll_offset = MAX(0, hscroll_offset - 10); + break; + case KEY_RIGHT: + case 'l': + case 'L': + if (max_line_width > max_display_width) + hscroll_offset = MIN(max_line_width - max_display_width, + hscroll_offset + 10); + break; + } + } + + delwin(win); + free(content); + refresh(); /* Refresh main screen */ return TRUE; } @@ -7629,17 +7866,24 @@ nochange: case SEL_STATS: // fallthrough case SEL_CHMODX: if (ndents) { + enum action action = SEL_MAX; tmp = (listpath && xstrcmp(path, listpath) == 0) ? listroot : path; mkpath(tmp, pdents[cur].name, newpath); - if ((sel == SEL_STATS && !show_stats(newpath)) + if ((sel == SEL_STATS && !show_stats(newpath, &action)) || (lstat(newpath, &sb) == -1) || (sel == SEL_CHMODX && !xchmod(newpath, &sb.st_mode))) { printwarn(&presel); goto nochange; } - if (sel == SEL_CHMODX) + if (sel == SEL_STATS) { + if (action == SEL_NEXT || action == SEL_PREV) { + g_state.move = 1; + handle_screen_move(action); + presel = 'f'; // Show stats for the next file + } + } else if (sel == SEL_CHMODX) pdents[cur].mode = sb.st_mode; } break; diff --git a/src/nnn.h b/src/nnn.h @@ -120,6 +120,7 @@ enum action { #ifndef NOMOUSE SEL_CLICK, #endif + SEL_MAX, }; /* Associate a pressed key to an action */