X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/03a93dbb1eb5bcd84333f58ae577b9cdab62936e..cfc6fa71c81e66c653fe83763d262c948b87731c:/tig.c diff --git a/tig.c b/tig.c index aa62a8f..cbfa854 100644 --- a/tig.c +++ b/tig.c @@ -15,21 +15,22 @@ * tig [options] [--] [git log options] * tig [options] log [git log options] * tig [options] diff [git diff options] - * tig [options] < [git log or git diff output] + * tig [options] show [git show options] + * tig [options] < [git command output] * * DESCRIPTION * ----------- * Browse changes in a git repository. **/ -#ifndef DEBUG -#define NDEBUG -#endif - #ifndef VERSION #define VERSION "tig-0.1" #endif +#ifndef DEBUG +#define NDEBUG +#endif + #include #include #include @@ -38,13 +39,14 @@ #include #include #include +#include #include #include static void die(const char *err, ...); static void report(const char *msg, ...); -static void set_nonblocking_input(int boolean); +static void set_nonblocking_input(bool loading); #define ABS(x) ((x) >= 0 ? (x) : -(x)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -67,7 +69,7 @@ static void set_nonblocking_input(int boolean); #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3) -/* Some ascii-shorthands that fit into the ncurses namespace. */ +/* Some ascii-shorthands fitted into the ncurses namespace. */ #define KEY_TAB '\t' #define KEY_RETURN '\r' #define KEY_ESC 27 @@ -82,11 +84,13 @@ enum request { REQ_VIEW_DIFF, REQ_VIEW_LOG, REQ_VIEW_HELP, + REQ_VIEW_PAGER, REQ_ENTER, REQ_QUIT, REQ_PROMPT, REQ_SCREEN_REDRAW, + REQ_SCREEN_RESIZE, REQ_SCREEN_UPDATE, REQ_SHOW_VERSION, REQ_STOP_LOADING, @@ -179,12 +183,10 @@ static int opt_line_number = FALSE; static int opt_num_interval = NUMBER_INTERVAL; static enum request opt_request = REQ_VIEW_MAIN; static char opt_cmd[SIZEOF_CMD] = ""; - -char ref_head[SIZEOF_REF] = "HEAD"; -char ref_commit[SIZEOF_REF] = "HEAD"; +static FILE *opt_pipe = NULL; /* Returns the index of log or diff command or -1 to exit. */ -static int +static bool parse_options(int argc, char *argv[]) { int i; @@ -193,22 +195,8 @@ parse_options(int argc, char *argv[]) char *opt = argv[i]; /** - * log [options]:: - * git log options. - * - * diff [options]:: - * git diff options. - **/ - if (!strcmp(opt, "log") || - !strcmp(opt, "diff")) { - opt_request = opt[0] == 'l' - ? REQ_VIEW_LOG : REQ_VIEW_DIFF; - return i; - } - - /** * -l:: - * Start up in log view. + * Start up in log view using the internal log command. **/ if (!strcmp(opt, "-l")) { opt_request = REQ_VIEW_LOG; @@ -217,7 +205,7 @@ parse_options(int argc, char *argv[]) /** * -d:: - * Start up in diff view. + * Start up in diff view using the internal diff command. **/ if (!strcmp(opt, "-d")) { opt_request = REQ_VIEW_DIFF; @@ -230,7 +218,7 @@ parse_options(int argc, char *argv[]) * Optionally, with interval different than each line. **/ if (!strncmp(opt, "-n", 2) || - !strncmp(opt, "--line-number", 13)) { + !strncmp(opt, "--line-number", 13)) { char *num = opt; if (opt[1] == 'n') { @@ -252,34 +240,108 @@ parse_options(int argc, char *argv[]) * Show version and exit. **/ if (!strcmp(opt, "-v") || - !strcmp(opt, "--version")) { + !strcmp(opt, "--version")) { printf("tig version %s\n", VERSION); - return -1; + return FALSE; } /** * \--:: - * End of tig options. Useful when specifying commands + * End of tig(1) options. Useful when specifying commands * for the main view. Example: * - * $ tig -- --pretty=raw tag-1.0..HEAD + * $ tig -- --since=1.month **/ - if (!strcmp(opt, "--")) - return i + 1; + if (!strcmp(opt, "--")) { + i++; + break; + } - /* Make stuff like: - * + /** + * log [options]:: + * Open log view using the given git log options. + * + * diff [options]:: + * Open diff view using the given git diff options. + * + * show [options]:: + * Open diff view using the given git show options. + **/ + if (!strcmp(opt, "log") || + !strcmp(opt, "diff") || + !strcmp(opt, "show")) { + opt_request = opt[0] == 'l' + ? REQ_VIEW_LOG : REQ_VIEW_DIFF; + break; + } + + /* Make stuff like: + * * $ tig tag-1.0..HEAD * - * work. - */ + * work. */ if (opt[0] && opt[0] != '-') - return i; + break; die("unknown command '%s'", opt); } - return i; + if (!isatty(STDIN_FILENO)) { + /** + * Pager mode + * ~~~~~~~~~~ + * If stdin is a pipe, any log or diff options will be ignored and the + * pager view will be opened loading data from stdin. The pager mode + * can be used for colorizing output from various git commands. + * + * Example on how to colorize the output of git-show(1): + * + * $ git show | tig + **/ + opt_request = REQ_VIEW_PAGER; + opt_pipe = stdin; + + } else if (i < argc) { + size_t buf_size; + + /** + * Git command options + * ~~~~~~~~~~~~~~~~~~~ + * All git command options specified on the command line will + * be passed to the given command and all will be shell quoted + * before used. + * + * NOTE: It is possible to specify options even for the main + * view. If doing this you should not touch the `--pretty` + * option. + * + * Example on how to open the log view and show both author and + * committer information: + * + * $ tig log --pretty=fuller + **/ + + if (opt_request == REQ_VIEW_MAIN) + /* XXX: This is vulnerable to the user overriding + * options required for the main view parser. */ + string_copy(opt_cmd, "git log --stat --pretty=raw"); + else + string_copy(opt_cmd, "git"); + buf_size = strlen(opt_cmd); + + while (buf_size < sizeof(opt_cmd) && i < argc) { + opt_cmd[buf_size++] = ' '; + buf_size = sq_quote(opt_cmd, buf_size, argv[i++]); + } + + if (buf_size >= sizeof(opt_cmd)) + die("command too long"); + + opt_cmd[buf_size] = 0; + + } + + return TRUE; } @@ -305,6 +367,8 @@ struct keymap keymap[] = { * Switch to log view. * m:: * Switch to main view. + * p:: + * Switch to pager view. * h:: * Show man page. * Return:: @@ -316,6 +380,7 @@ struct keymap keymap[] = { { 'm', REQ_VIEW_MAIN }, { 'd', REQ_VIEW_DIFF }, { 'l', REQ_VIEW_LOG }, + { 'p', REQ_VIEW_PAGER }, { 'h', REQ_VIEW_HELP }, { KEY_TAB, REQ_VIEW_NEXT }, @@ -377,7 +442,11 @@ struct keymap keymap[] = { * n:: * Toggle line numbers on/off. * ':':: - * Open prompt. + * Open prompt. This allows you to specify what git command to run. + * Example: + * + * :log -p + * **/ { KEY_ESC, REQ_QUIT }, { 'q', REQ_QUIT }, @@ -389,6 +458,9 @@ struct keymap keymap[] = { /* wgetch() with nodelay() enabled returns ERR when there's no input. */ { ERR, REQ_SCREEN_UPDATE }, + + /* Use the ncurses SIGWINCH handler. */ + { KEY_RESIZE, REQ_SCREEN_RESIZE }, }; static enum request @@ -413,7 +485,7 @@ get_request(int key) * --------- --------------- ---------- ---------- ---------- */ \ /* Diff markup */ \ LINE(DIFF, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \ -LINE(INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \ +LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \ LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \ LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \ @@ -424,14 +496,17 @@ LINE(DIFF_RENAME, "rename ", COLOR_YELLOW, COLOR_DEFAULT, 0), \ LINE(DIFF_SIM, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \ LINE(DIFF_DISSIM, "dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \ /* Pretty print commit header */ \ -LINE(AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \ -LINE(MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \ -LINE(DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \ +LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \ +LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ +LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \ +LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \ +LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \ +LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \ /* Raw commit header */ \ LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \ LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \ LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \ -LINE(AUTHOR_IDENT, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \ +LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \ LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ /* Misc */ \ LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \ @@ -513,30 +588,78 @@ init_colors(void) } +/** + * ENVIRONMENT VARIABLES + * --------------------- + * It is possible to alter which commands are used for the different views. + * If for example you prefer commits in the main to be sorted by date and + * only show 500 commits, use: + * + * $ TIG_MAIN_CMD="git log --date-order -n500 --pretty=raw %s" tig + * + * Or set the variable permanently in your environment. + * + * Notice, how `%s` is used to specify the commit reference. There can + * be a maximum of 5 `%s` ref specifications. + * + * TIG_DIFF_CMD:: + * The command used for the diff view. By default, git show is used + * as a backend. + * + * TIG_LOG_CMD:: + * The command used for the log view. + * + * TIG_MAIN_CMD:: + * The command used for the main view. Note, you must always specify + * the option: `--pretty=raw` since the main view parser expects to + * read that format. + **/ + +#define TIG_DIFF_CMD \ + "git show --patch-with-stat --find-copies-harder -B -C %s" + +#define TIG_LOG_CMD \ + "git log --cc --stat -n100 %s" + +#define TIG_MAIN_CMD \ + "git log --topo-order --stat --pretty=raw %s" + +/* We silently ignore that the following are also exported. */ + +#define TIG_HELP_CMD \ + "man tig 2> /dev/null" + +#define TIG_PAGER_CMD \ + "" + + /* * Viewer */ struct view { const char *name; /* View name */ - const char *defcmd; /* Default command line */ + char *cmd_fmt; /* Default command line format */ + char *cmd_env; /* Command line set via environment */ char *id; /* Points to either of ref_{head,commit} */ size_t objsize; /* Size of objects in the line index */ struct view_ops { + /* Draw one line; @lineno must be < view->height. */ bool (*draw)(struct view *view, unsigned int lineno); + /* Read one line; updates view->line. */ bool (*read)(struct view *view, char *line); + /* Depending on view, change display based on current line. */ bool (*enter)(struct view *view); } *ops; char cmd[SIZEOF_CMD]; /* Command buffer */ - char ref[SIZEOF_REF]; /* Hovered Commit reference */ - /* The view reference that describes the content of this view. */ - char vref[SIZEOF_REF]; + char ref[SIZEOF_REF]; /* Hovered commit reference */ + char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */ - WINDOW *win; - WINDOW *title; - int height, width; + int height, width; /* The width and height of the main window */ + WINDOW *win; /* The main window */ + WINDOW *title; /* The title window living below the main window */ /* Navigation */ unsigned long offset; /* Offset of the window top */ @@ -544,7 +667,8 @@ struct view { /* Buffering */ unsigned long lines; /* Total number of lines */ - void **line; /* Line index */ + void **line; /* Line index; each line contains user data */ + unsigned int digits; /* Number of digits in the lines member. */ /* Loading */ FILE *pipe; @@ -554,24 +678,21 @@ struct view { static struct view_ops pager_ops; static struct view_ops main_ops; -#define DIFF_CMD \ - "git log --stat -n1 %s ; echo; " \ - "git diff --find-copies-harder -B -C %s^ %s" - -#define LOG_CMD \ - "git log --cc --stat -n100 %s" +char ref_head[SIZEOF_REF] = "HEAD"; +char ref_commit[SIZEOF_REF] = "HEAD"; -#define MAIN_CMD \ - "git log --stat --pretty=raw %s" +#define VIEW_STR(name, cmd, env, ref, objsize, ops) \ + { name, cmd, #env, ref, objsize, ops } -#define HELP_CMD \ - "man tig 2> /dev/null" +#define VIEW_(id, name, ops, ref, objsize) \ + VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, objsize, ops) static struct view views[] = { - { "main", MAIN_CMD, ref_head, sizeof(struct commit), &main_ops }, - { "diff", DIFF_CMD, ref_commit, sizeof(char), &pager_ops }, - { "log", LOG_CMD, ref_head, sizeof(char), &pager_ops }, - { "help", HELP_CMD, ref_head, sizeof(char), &pager_ops }, + VIEW_(MAIN, "main", &main_ops, ref_head, sizeof(struct commit)), + VIEW_(DIFF, "diff", &pager_ops, ref_commit, sizeof(char)), + VIEW_(LOG, "log", &pager_ops, ref_head, sizeof(char)), + VIEW_(HELP, "help", &pager_ops, ref_head, sizeof(char)), + VIEW_(PAGER, "pager", &pager_ops, "static", sizeof(char)), }; #define VIEW(req) (&views[(req) - REQ_OFFSET - 1]) @@ -669,7 +790,12 @@ update_view_title(struct view *view) wmove(view->title, 0, 0); /* [main] ref: 334b506... - commit 6 of 4383 (0%) */ - wprintw(view->title, "[%s] ref: %s", view->name, view->ref); + + if (*view->ref) + wprintw(view->title, "[%s] ref: %s", view->name, view->ref); + else + wprintw(view->title, "[%s]", view->name); + if (view->lines) { char *type = view == VIEW(REQ_VIEW_MAIN) ? "commit" : "line"; @@ -744,7 +870,7 @@ scroll_view(struct view *view, enum request request) lines = view->lines - view->offset; if (lines == 0 || view->offset + view->height >= view->lines) { - report("Already on last line"); + report("Cannot scroll beyond the last line"); return; } break; @@ -756,7 +882,7 @@ scroll_view(struct view *view, enum request request) lines = view->offset; if (lines == 0) { - report("Already on first line"); + report("Cannot scroll beyond the first line"); return; } @@ -808,11 +934,11 @@ move_view(struct view *view, enum request request) } if (steps <= 0 && view->lineno == 0) { - report("Already on first line"); + report("Cannot move beyond the first line"); return; - } else if (steps >= 0 && view->lineno + 1 == view->lines) { - report("Already on last line"); + } else if (steps >= 0 && view->lineno + 1 >= view->lines) { + report("Cannot move beyond the last line"); return; } @@ -869,13 +995,25 @@ begin_update(struct view *view) if (opt_cmd[0]) { string_copy(view->cmd, opt_cmd); opt_cmd[0] = 0; + /* When running random commands, the view ref could have become + * invalid so clear it. */ + view->ref[0] = 0; } else { - if (snprintf(view->cmd, sizeof(view->cmd), view->defcmd, - id, id, id) >= sizeof(view->cmd)) + char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt; + + if (snprintf(view->cmd, sizeof(view->cmd), format, + id, id, id, id, id) >= sizeof(view->cmd)) return FALSE; } - view->pipe = popen(view->cmd, "r"); + /* Special case for the pager view. */ + if (opt_pipe) { + view->pipe = opt_pipe; + opt_pipe = NULL; + } else { + view->pipe = popen(view->cmd, "r"); + } + if (!view->pipe) return FALSE; @@ -884,6 +1022,7 @@ begin_update(struct view *view) view->offset = 0; view->lines = 0; view->lineno = 0; + string_copy(view->vid, id); if (view->line) { int i; @@ -907,7 +1046,10 @@ end_update(struct view *view) if (!view->pipe) return; set_nonblocking_input(FALSE); - pclose(view->pipe); + if (view->pipe == stdin) + fclose(view->pipe); + else + pclose(view->pipe); view->pipe = NULL; } @@ -920,7 +1062,7 @@ update_view(struct view *view) /* The number of lines to read. If too low it will cause too much * redrawing (and possible flickering), if too high responsiveness * will suffer. */ - int lines = view->height; + unsigned long lines = view->height; int redraw_from = -1; if (!view->pipe) @@ -950,8 +1092,19 @@ update_view(struct view *view) break; } - /* CPU hogilicious! */ - update_view_title(view); + { + int digits; + + lines = view->lines; + for (digits = 0; lines; digits++) + lines /= 10; + + /* Keep the displayed view in sync with line number scaling. */ + if (digits != view->digits) { + view->digits = digits; + redraw_from = 0; + } + } if (redraw_from >= 0) { /* If this is an incremental update, redraw the previous line @@ -964,6 +1117,10 @@ update_view(struct view *view) redraw_view_from(view, redraw_from); } + /* Update the title _after_ the redraw so that if the redraw picks up a + * commit reference in view->ref it'll be available here. */ + update_view_title(view); + if (ferror(view->pipe)) { report("Failed to read: %s", strerror(errno)); goto end; @@ -991,10 +1148,19 @@ end: return FALSE; } +enum open_flags { + OPEN_DEFAULT = 0, /* Use default view switching. */ + OPEN_SPLIT = 1, /* Split current view. */ + OPEN_BACKGROUNDED = 2, /* Backgrounded. */ + OPEN_RELOAD = 4, /* Reload view even if it is the current. */ +}; + static void -switch_view(struct view *prev, enum request request, - bool backgrounded, bool split) +open_view(struct view *prev, enum request request, enum open_flags flags) { + bool backgrounded = !!(flags & OPEN_BACKGROUNDED); + bool split = !!(flags & OPEN_SPLIT); + bool reload = !!(flags & OPEN_RELOAD); struct view *view = VIEW(request); struct view *displayed; int nviews; @@ -1003,7 +1169,7 @@ switch_view(struct view *prev, enum request request, foreach_view (displayed, nviews) { if (prev != view && view == displayed && - !strcmp(view->id, prev->id)) { + !strcmp(view->vid, prev->vid)) { current_view = nviews; /* Blur out the title of the previous view. */ update_view_title(prev); @@ -1012,12 +1178,12 @@ switch_view(struct view *prev, enum request request, } } - if (view == prev && nviews == 1) { + if (view == prev && nviews == 1 && !reload) { report("Already in %s view", view->name); return; } - if (strcmp(view->vref, view->id) && + if ((reload || strcmp(view->vid, view->id)) && !begin_update(view)) { report("Failed to load %s view", view->name); return; @@ -1036,9 +1202,9 @@ switch_view(struct view *prev, enum request request, resize_display(); - if (split && prev->lineno - prev->offset > prev->height) { + if (split && prev->lineno - prev->offset >= prev->height) { /* Take the title line into account. */ - int lines = prev->lineno - prev->height + 1; + int lines = prev->lineno - prev->offset - prev->height + 1; /* Scroll the view that was split if the current line is * outside the new limited view. */ @@ -1096,10 +1262,15 @@ view_driver(struct view *view, enum request request) case REQ_VIEW_DIFF: case REQ_VIEW_LOG: case REQ_VIEW_HELP: - switch_view(view, request, FALSE, FALSE); + case REQ_VIEW_PAGER: + open_view(view, request, OPEN_DEFAULT); break; case REQ_ENTER: + if (!view->lines) { + report("Nothing to enter"); + break; + } return view->ops->enter(view); case REQ_VIEW_NEXT: @@ -1124,7 +1295,8 @@ view_driver(struct view *view, enum request request) break; case REQ_PROMPT: - switch_view(view, opt_request, FALSE, FALSE); + /* Always reload^Wrerun commands from the prompt. */ + open_view(view, opt_request, OPEN_RELOAD); break; case REQ_STOP_LOADING: @@ -1139,8 +1311,14 @@ view_driver(struct view *view, enum request request) report("Version: %s", VERSION); return TRUE; + case REQ_SCREEN_RESIZE: + resize_display(); + /* Fall-through */ case REQ_SCREEN_REDRAW: - redraw_view(view); + foreach_view (view, i) { + redraw_view(view); + update_view_title(view); + } break; case REQ_SCREEN_UPDATE: @@ -1194,13 +1372,17 @@ pager_draw(struct view *view, unsigned int lineno) linelen = MIN(linelen, view->width); if (opt_line_number) { - unsigned int real_lineno = view->offset + lineno + 1; + static char indent[] = " "; + unsigned long real_lineno = view->offset + lineno + 1; int col = 0; if (real_lineno == 1 || (real_lineno % opt_num_interval) == 0) - mvwprintw(view->win, lineno, 0, "%4d: ", real_lineno); - else - mvwaddstr(view->win, lineno, 0, " : "); + mvwprintw(view->win, lineno, 0, "%.*d", view->digits, real_lineno); + + else if (view->digits < sizeof(indent)) + mvwaddnstr(view->win, lineno, 0, indent, view->digits); + + waddstr(view->win, ": "); while (line) { if (*line == '\t') { @@ -1257,19 +1439,19 @@ pager_enter(struct view *view) char *line = view->line[view->lineno]; if (get_line_type(line) == LINE_COMMIT) { - switch_view(view, REQ_VIEW_DIFF, FALSE, FALSE); + open_view(view, REQ_VIEW_DIFF, OPEN_DEFAULT); } return TRUE; } - static struct view_ops pager_ops = { pager_draw, pager_read, pager_enter, }; + static bool main_draw(struct view *view, unsigned int lineno) { @@ -1288,6 +1470,7 @@ main_draw(struct view *view, unsigned int lineno) if (view->offset + lineno == view->lineno) { string_copy(view->ref, commit->id); + string_copy(ref_commit, view->ref); type = LINE_CURSOR; } else { type = LINE_MAIN_COMMIT; @@ -1341,7 +1524,7 @@ main_read(struct view *view, char *line) string_copy(commit->id, line); break; - case LINE_AUTHOR_IDENT: + case LINE_AUTHOR: { char *ident = line + STRING_SIZE("author "); char *end = strchr(ident, '<'); @@ -1397,8 +1580,10 @@ main_read(struct view *view, char *line) /* Require titles to start with a non-space character at the * offset used by git log. */ + /* FIXME: More gracefull handling of titles; append "..." to + * shortened titles, etc. */ if (strncmp(line, " ", 4) || - isspace(line[5])) + isspace(line[4])) break; string_copy(commit->title, line + 4); @@ -1410,7 +1595,7 @@ main_read(struct view *view, char *line) static bool main_enter(struct view *view) { - switch_view(view, REQ_VIEW_DIFF, TRUE, TRUE); + open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED); return TRUE; } @@ -1424,6 +1609,9 @@ static struct view_ops main_ops = { * Status management */ +/* Whether or not the curses interface has been initialized. */ +bool cursed = FALSE; + /* The status window is used for polling keystrokes. */ static WINDOW *status_win; @@ -1433,15 +1621,16 @@ report(const char *msg, ...) { va_list args; - va_start(args, msg); - /* Update the title window first, so the cursor ends up in the status * window. */ update_view_title(display[current_view]); + va_start(args, msg); + werase(status_win); wmove(status_win, 0, 0); - vwprintw(status_win, msg, args); + if (*msg) + vwprintw(status_win, msg, args); wrefresh(status_win); va_end(args); @@ -1449,19 +1638,14 @@ report(const char *msg, ...) /* Controls when nodelay should be in effect when polling user input. */ static void -set_nonblocking_input(int loading) +set_nonblocking_input(bool loading) { /* The number of loading views. */ static unsigned int nloading; - if (loading == TRUE) { - if (nloading++ == 0) - nodelay(status_win, TRUE); - return; - } - - if (nloading-- == 1) - nodelay(status_win, FALSE); + if ((loading == FALSE && nloading-- == 1) || + (loading == TRUE && nloading++ == 0)) + nodelay(status_win, loading); } static void @@ -1469,7 +1653,19 @@ init_display(void) { int x, y; - initscr(); /* Initialize the curses library */ + /* Initialize the curses library */ + if (isatty(STDIN_FILENO)) { + cursed = !!initscr(); + } else { + /* Leave stdin and stdout alone when acting as a pager. */ + FILE *io = fopen("/dev/tty", "r+"); + + cursed = !!newterm(NULL, io, io); + } + + if (!cursed) + die("Failed to initialize curses"); + nonl(); /* Tell curses not to do NL->CR/NL on output */ cbreak(); /* Take input chars one at a time, no wait for \n */ noecho(); /* Don't echo input */ @@ -1495,12 +1691,9 @@ init_display(void) static void quit(int sig) { - if (status_win) - delwin(status_win); - endwin(); - - /* FIXME: Shutdown gracefully. */ - + /* XXX: Restore tty modes and let the OS cleanup the rest! */ + if (cursed) + endwin(); exit(0); } @@ -1522,43 +1715,23 @@ static void die(const char *err, ...) int main(int argc, char *argv[]) { + struct view *view; enum request request; - int git_arg; + size_t i; signal(SIGINT, quit); - git_arg = parse_options(argc, argv); - if (git_arg < 0) + if (!parse_options(argc, argv)) return 0; - if (git_arg < argc) { - size_t buf_size; - - /* XXX: This is vulnerable to the user overriding options - * required for the main view parser. */ - if (opt_request == REQ_VIEW_MAIN) - string_copy(opt_cmd, "git log --stat --pretty=raw"); - else - string_copy(opt_cmd, "git"); - buf_size = strlen(opt_cmd); - - while (buf_size < sizeof(opt_cmd) && git_arg < argc) { - opt_cmd[buf_size++] = ' '; - buf_size = sq_quote(opt_cmd, buf_size, argv[git_arg++]); - } - - if (buf_size >= sizeof(opt_cmd)) - die("command too long"); - - opt_cmd[buf_size] = 0; - } + for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++) + view->cmd_env = getenv(view->cmd_env); request = opt_request; init_display(); while (view_driver(display[current_view], request)) { - struct view *view; int key; int i; @@ -1569,14 +1742,42 @@ main(int argc, char *argv[]) key = wgetch(status_win); request = get_request(key); - if (request == REQ_PROMPT) { + /* Some low-level request handling. This keeps handling of + * status_win restricted. */ + switch (request) { + case REQ_PROMPT: + report(":"); + /* Temporarily switch to line-oriented and echoed + * input. */ nocbreak(); echo(); - report(":"); - if (wgetnstr(status_win, opt_cmd, sizeof(opt_cmd)) == OK) - die("%s", opt_cmd); - cbreak(); /* Take input chars one at a time, no wait for \n */ - noecho(); /* Don't echo input */ + + if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) { + memcpy(opt_cmd, "git ", 4); + opt_request = REQ_VIEW_PAGER; + } else { + request = ERR; + } + + noecho(); + cbreak(); + break; + + case REQ_SCREEN_RESIZE: + { + int height, width; + + getmaxyx(stdscr, height, width); + + /* Resize the status view and let the view driver take + * care of resizing the displayed views. */ + wresize(status_win, 1, width); + mvwin(status_win, height - 1, 0); + wrefresh(status_win); + break; + } + default: + break; } } @@ -1590,15 +1791,7 @@ main(int argc, char *argv[]) * ---- * Features that should be explored. * - * - Dynamic scaling of line number indentation. - * - * - Internal command line (exmode-inspired) which allows to specify what git - * log or git diff command to run. Example: - * - * :log -p - * - * - Terminal resizing support. I am yet to figure out whether catching - * SIGWINCH is preferred over using ncurses' built-in support for resizing. + * - Searching. * * - Locale support. *