X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/f0b3ab80bf9ea3d4be57e85b41736e3e73544f64..219ee52a04d3b9d46f5d8165a9d61ee12e67bd01:/tig.c diff --git a/tig.c b/tig.c index 4abb0bf..cfdf026 100644 --- a/tig.c +++ b/tig.c @@ -1,5 +1,14 @@ /* Copyright (c) 2006 Jonas Fonseca - * See license info at the bottom. */ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ /** * TIG(1) * ====== @@ -429,6 +438,82 @@ parse_options(int argc, char *argv[]) } +/** + * ENVIRONMENT VARIABLES + * --------------------- + * Several options related to the interface with git can be configured + * via environment options. + * + * Repository references + * ~~~~~~~~~~~~~~~~~~~~~ + * Commits that are referenced by tags and branch heads will be marked + * by the reference name surrounded by '[' and ']': + * + * 2006-03-26 19:42 Petr Baudis | [cogito-0.17.1] Cogito 0.17.1 + * + * If you want to filter out certain directories under `.git/refs/`, say + * `tmp` you can do it by setting the following variable: + * + * $ TIG_LS_REMOTE="git ls-remote . | sed /\/tmp\//d" tig + * + * Or set the variable permanently in your environment. + * + * TIG_LS_REMOTE:: + * Set command for retrieving all repository references. The command + * should output data in the same format as git-ls-remote(1). + **/ + +#define TIG_LS_REMOTE \ + "git ls-remote . 2>/dev/null" + +/** + * [[view-commands]] + * View commands + * ~~~~~~~~~~~~~ + * It is possible to alter which commands are used for the different views. + * If for example you prefer commits in the main view 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. If you prefer to have both + * author and committer shown in the log view be sure to pass + * `--pretty=fuller` to git log. + * + * 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" + +/* ... silently ignore that the following are also exported. */ + +#define TIG_HELP_CMD \ + "man tig 2>/dev/null" + +#define TIG_PAGER_CMD \ + "" + + /* * Line-oriented content detection. */ @@ -542,81 +627,10 @@ init_colors(void) } } - -/** - * ENVIRONMENT VARIABLES - * --------------------- - * Several options related to the interface with git can be configured - * via environment options. - * - * Repository references - * ~~~~~~~~~~~~~~~~~~~~~ - * Commits that are referenced by tags and branch heads will be marked - * by the reference name surrounded by '[' and ']': - * - * 2006-03-26 19:42 Petr Baudis | [cogito-0.17.1] Cogito 0.17.1 - * - * If you want to filter out certain directories under `.git/refs/`, say - * `tmp` you can do it by setting the following variable: - * - * $ TIG_LS_REMOTE="git ls-remote . | sed /\/tmp\//d" tig - * - * Or set the variable permanently in your environment. - * - * TIG_LS_REMOTE:: - * Set command for retrieving all repository references. The command - * should output data in the same format as git-ls-remote(1). - **/ - -#define TIG_LS_REMOTE \ - "git ls-remote . 2>/dev/null" - -/** - * [[view-commands]] - * View commands - * ~~~~~~~~~~~~~ - * It is possible to alter which commands are used for the different views. - * If for example you prefer commits in the main view 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. If you prefer to have both - * author and committer shown in the log view be sure to pass - * `--pretty=fuller` to git log. - * - * 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" - -/* ... silently ignore that the following are also exported. */ - -#define TIG_HELP_CMD \ - "man tig 2>/dev/null" - -#define TIG_PAGER_CMD \ - "" +struct line { + enum line_type type; + void *data; /* User data */ +}; /** @@ -638,6 +652,7 @@ init_colors(void) **/ struct view; +struct view_ops; /* The display array of active views and the index of the current view. */ static struct view *display[2]; @@ -646,6 +661,7 @@ static unsigned int current_view; #define foreach_view(view, i) \ for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++) +#define displayed_views() (display[1] != NULL ? 2 : 1) /** * Current head and commit ID @@ -662,24 +678,13 @@ static unsigned int current_view; static char ref_commit[SIZEOF_REF] = "HEAD"; static char ref_head[SIZEOF_REF] = "HEAD"; - struct view { const char *name; /* View name */ const char *cmd_fmt; /* Default command line format */ const char *cmd_env; /* Command line set via environment */ const char *id; /* Points to either of ref_{head,commit} */ - struct view_ops { - /* What type of content being displayed. Used in the - * title bar. */ - const char *type; - /* 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; + struct view_ops *ops; /* View operations */ char cmd[SIZEOF_CMD]; /* Command buffer */ char ref[SIZEOF_REF]; /* Hovered commit reference */ @@ -699,7 +704,7 @@ struct view { /* Buffering */ unsigned long lines; /* Total number of lines */ - void **line; /* Line index; each line contains user data */ + struct line *line; /* Line index */ unsigned int digits; /* Number of digits in the lines member. */ /* Loading */ @@ -707,6 +712,17 @@ struct view { time_t start_time; }; +struct view_ops { + /* What type of content being displayed. Used in the title bar. */ + const char *type; + /* Draw one line; @lineno must be < view->height. */ + bool (*draw)(struct view *view, struct line *line, unsigned int lineno); + /* Read one line; updates view->line. */ + bool (*read)(struct view *view, struct line *prev, char *data); + /* Depending on view, change display based on current line. */ + bool (*enter)(struct view *view, struct line *line); +}; + static struct view_ops pager_ops; static struct view_ops main_ops; @@ -757,13 +773,22 @@ static struct view views[] = { #define VIEW(req) (&views[(req) - REQ_OFFSET - 1]) +static bool +draw_view_line(struct view *view, unsigned int lineno) +{ + if (view->offset + lineno >= view->lines) + return FALSE; + + return view->ops->draw(view, &view->line[view->offset + lineno], lineno); +} + static void redraw_view_from(struct view *view, int lineno) { assert(0 <= lineno && lineno < view->height); for (; lineno < view->height; lineno++) { - if (!view->ops->draw(view, lineno)) + if (!draw_view_line(view, lineno)) break; } @@ -806,14 +831,27 @@ update_view_title(struct view *view) else wprintw(view->title, "[%s]", view->name); - if (view->lines) { + if (view->lines || view->pipe) { + unsigned int lines = view->lines + ? (view->lineno + 1) * 100 / view->lines + : 0; + wprintw(view->title, " - %s %d of %d (%d%%)", view->ops->type, view->lineno + 1, view->lines, - (view->lineno + 1) * 100 / view->lines); + lines); + } + + if (view->pipe) { + time_t secs = time(NULL) - view->start_time; + + /* Three git seconds are a long time ... */ + if (secs > 2) + wprintw(view->title, " %lds", secs); } + wrefresh(view->title); } @@ -847,11 +885,8 @@ resize_display(void) offset = 0; foreach_view (view, i) { - /* Keep the height of all view->win windows one larger than is - * required so that the cursor can wrap-around on the last line - * without scrolling the window. */ if (!view->win) { - view->win = newwin(view->height + 1, 0, offset, 0); + view->win = newwin(view->height, 0, offset, 0); if (!view->win) die("Failed to create %s view", view->name); @@ -862,7 +897,7 @@ resize_display(void) die("Failed to create title window"); } else { - wresize(view->win, view->height + 1, view->width); + wresize(view->win, view->height, view->width); mvwin(view->win, offset, 0); mvwin(view->title, offset + view->height, 0); wrefresh(view->win); @@ -910,7 +945,7 @@ do_scroll_view(struct view *view, int lines, bool redraw) wscrl(view->win, lines); for (; line < end; line++) { - if (!view->ops->draw(view, line)) + if (!draw_view_line(view, line)) break; } } @@ -918,7 +953,7 @@ do_scroll_view(struct view *view, int lines, bool redraw) /* Move current line into the view. */ if (view->lineno < view->offset) { view->lineno = view->offset; - view->ops->draw(view, 0); + draw_view_line(view, 0); } else if (view->lineno >= view->offset + view->height) { if (view->lineno == view->offset + view->height) { @@ -928,7 +963,7 @@ do_scroll_view(struct view *view, int lines, bool redraw) wclrtoeol(view->win); } view->lineno = view->offset + view->height - 1; - view->ops->draw(view, view->lineno - view->offset); + draw_view_line(view, view->lineno - view->offset); } assert(view->offset <= view->lineno && view->lineno < view->lines); @@ -1037,7 +1072,7 @@ move_view(struct view *view, enum request request, bool redraw) wmove(view->win, prev_lineno, 0); wclrtoeol(view->win); - view->ops->draw(view, prev_lineno); + draw_view_line(view, prev_lineno); } /* Check whether the view needs to be scrolled */ @@ -1060,7 +1095,7 @@ move_view(struct view *view, enum request request, bool redraw) } /* Draw the current line */ - view->ops->draw(view, view->lineno - view->offset); + draw_view_line(view, view->lineno - view->offset); if (!redraw) return; @@ -1132,8 +1167,8 @@ begin_update(struct view *view) int i; for (i = 0; i < view->lines; i++) - if (view->line[i]) - free(view->line[i]); + if (view->line[i].data) + free(view->line[i].data); free(view->line); view->line = NULL; @@ -1149,7 +1184,7 @@ update_view(struct view *view) { char buffer[BUFSIZ]; char *line; - void **tmp; + struct line *tmp; /* The number of lines to read. If too low it will cause too much * redrawing (and possible flickering), if too high responsiveness * will suffer. */ @@ -1172,10 +1207,14 @@ update_view(struct view *view) while ((line = fgets(buffer, sizeof(buffer), view->pipe))) { int linelen = strlen(line); + struct line *prev = view->lines + ? &view->line[view->lines - 1] + : NULL; + if (linelen) line[linelen - 1] = 0; - if (!view->ops->read(view, line)) + if (!view->ops->read(view, prev, line)) goto alloc_error; if (lines-- == 1) @@ -1216,8 +1255,6 @@ update_view(struct view *view) goto end; } else if (feof(view->pipe)) { - time_t secs = time(NULL) - view->start_time; - if (view == VIEW(REQ_VIEW_HELP)) { const char *msg = TIG_HELP; @@ -1232,8 +1269,7 @@ update_view(struct view *view) goto end; } - report("Loaded %d lines in %ld second%s", view->lines, secs, - secs == 1 ? "" : "s"); + report(""); goto end; } @@ -1261,7 +1297,8 @@ open_view(struct view *prev, enum request request, enum open_flags flags) bool split = !!(flags & OPEN_SPLIT); bool reload = !!(flags & OPEN_RELOAD); struct view *view = VIEW(request); - int nviews = display[1] ? 2 : 1; + int nviews = displayed_views(); + struct view *base_view = display[0]; if (view == prev && nviews == 1 && !reload) { report("Already in %s view", view->name); @@ -1285,7 +1322,11 @@ open_view(struct view *prev, enum request request, enum open_flags flags) display[current_view] = view; } - resize_display(); + /* Resize the view when switching between split- and full-screen, + * or when switching between two different full-screen views. */ + if (nviews != displayed_views() || + (nviews == 1 && base_view != display[0])) + resize_display(); if (split && prev->lineno - prev->offset >= prev->height) { /* Take the title line into account. */ @@ -1311,7 +1352,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags) /* Clear the old view and let the incremental updating refill * the screen. */ wclear(view->win); - report("Loading..."); + report(""); } else { redraw_view(view); if (view == VIEW(REQ_VIEW_HELP)) @@ -1384,11 +1425,11 @@ view_driver(struct view *view, enum request request) report("Nothing to enter"); break; } - return view->ops->enter(view); + return view->ops->enter(view, &view->line[view->lineno]); case REQ_VIEW_NEXT: { - int nviews = display[1] ? 2 : 1; + int nviews = displayed_views(); int next_view = (current_view + 1) % nviews; if (next_view == current_view) { @@ -1464,24 +1505,18 @@ view_driver(struct view *view, enum request request) */ static bool -pager_draw(struct view *view, unsigned int lineno) +pager_draw(struct view *view, struct line *line, unsigned int lineno) { - enum line_type type; - char *line; - int linelen; + char *text = line->data; + enum line_type type = line->type; + int textlen = strlen(text); int attr; - if (view->offset + lineno >= view->lines) - return FALSE; - - line = view->line[view->offset + lineno]; - type = get_line_type(line); - wmove(view->win, lineno, 0); if (view->offset + lineno == view->lineno) { if (type == LINE_COMMIT) { - string_copy(view->ref, line + 7); + string_copy(view->ref, text + 7); string_copy(ref_commit, view->ref); } @@ -1492,8 +1527,6 @@ pager_draw(struct view *view, unsigned int lineno) attr = get_line_attr(type); wattrset(view->win, attr); - linelen = strlen(line); - if (opt_line_number || opt_tab_size < TABSIZE) { static char spaces[] = " "; int col_offset = 0, col = 0; @@ -1513,66 +1546,65 @@ pager_draw(struct view *view, unsigned int lineno) col_offset = view->digits + 2; } - while (line && col_offset + col < view->width) { + while (text && col_offset + col < view->width) { int cols_max = view->width - col_offset - col; - char *text = line; + char *pos = text; int cols; - if (*line == '\t') { + if (*text == '\t') { + text++; assert(sizeof(spaces) > TABSIZE); - line++; - text = spaces; + pos = spaces; cols = opt_tab_size - (col % opt_tab_size); } else { - line = strchr(line, '\t'); - cols = line ? line - text : strlen(text); + text = strchr(text, '\t'); + cols = line ? text - pos : strlen(pos); } - waddnstr(view->win, text, MIN(cols, cols_max)); + waddnstr(view->win, pos, MIN(cols, cols_max)); col += cols; } } else { int col = 0, pos = 0; - for (; pos < linelen && col < view->width; pos++, col++) - if (line[pos] == '\t') + for (; pos < textlen && col < view->width; pos++, col++) + if (text[pos] == '\t') col += TABSIZE - (col % TABSIZE) - 1; - waddnstr(view->win, line, pos); + waddnstr(view->win, text, pos); } return TRUE; } static bool -pager_read(struct view *view, char *line) +pager_read(struct view *view, struct line *prev, char *line) { /* Compress empty lines in the help view. */ if (view == VIEW(REQ_VIEW_HELP) && - !*line && - view->lines && - !*((char *) view->line[view->lines - 1])) + !*line && prev && !*((char *) prev->data)) return TRUE; - view->line[view->lines] = strdup(line); - if (!view->line[view->lines]) + view->line[view->lines].data = strdup(line); + if (!view->line[view->lines].data) return FALSE; + view->line[view->lines].type = get_line_type(line); + view->lines++; return TRUE; } static bool -pager_enter(struct view *view) +pager_enter(struct view *view, struct line *line) { - char *line = view->line[view->lineno]; int split = 0; - if ((view == VIEW(REQ_VIEW_LOG) || - view == VIEW(REQ_VIEW_PAGER)) && - get_line_type(line) == LINE_COMMIT) { + if (line->type == LINE_COMMIT && + (view == VIEW(REQ_VIEW_LOG) || + view == VIEW(REQ_VIEW_PAGER))) { open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT); split = 1; } @@ -1612,20 +1644,16 @@ struct commit { }; static bool -main_draw(struct view *view, unsigned int lineno) +main_draw(struct view *view, struct line *line, unsigned int lineno) { char buf[DATE_COLS + 1]; - struct commit *commit; + struct commit *commit = line->data; enum line_type type; int col = 0; size_t timelen; size_t authorlen; int trimmed; - if (view->offset + lineno >= view->lines) - return FALSE; - - commit = view->line[view->offset + lineno]; if (!*commit->author) return FALSE; @@ -1709,7 +1737,7 @@ main_draw(struct view *view, unsigned int lineno) /* Reads git log --pretty=raw output and parses it into the commit struct. */ static bool -main_read(struct view *view, char *line) +main_read(struct view *view, struct line *prev, char *line) { enum line_type type = get_line_type(line); struct commit *commit; @@ -1722,7 +1750,7 @@ main_read(struct view *view, char *line) line += STRING_SIZE("commit "); - view->line[view->lines++] = commit; + view->line[view->lines++].data = commit; string_copy(commit->id, line); commit->refs = get_refs(commit->id); break; @@ -1732,12 +1760,16 @@ main_read(struct view *view, char *line) char *ident = line + STRING_SIZE("author "); char *end = strchr(ident, '<'); + if (!prev) + break; + + commit = prev->data; + if (end) { for (; end > ident && isspace(end[-1]); end--) ; *end = 0; } - commit = view->line[view->lines - 1]; string_copy(commit->author, ident); /* Parse epoch and timezone */ @@ -1771,13 +1803,12 @@ main_read(struct view *view, char *line) break; } default: - /* We should only ever end up here if there has already been a - * commit line, however, be safe. */ - if (view->lines == 0) + if (!prev) break; + commit = prev->data; + /* Fill in the commit title if it has not already been set. */ - commit = view->line[view->lines - 1]; if (commit->title[0]) break; @@ -1796,7 +1827,7 @@ main_read(struct view *view, char *line) } static bool -main_enter(struct view *view) +main_enter(struct view *view, struct line *line) { enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT; @@ -1987,7 +2018,7 @@ unicode_width(unsigned long c) || c == 0x2329 || c == 0x232a || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) - /* CJK ... Yi */ + /* CJK ... Yi */ || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */ || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */ || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */ @@ -2571,6 +2602,9 @@ main(int argc, char *argv[]) * outside the current view causing bad wrapping. Same goes * for title and status windows. * + * - The cursor can wrap-around on the last line and cause the + * window to scroll. + * * TODO * ---- * Features that should be explored. @@ -2581,7 +2615,7 @@ main(int argc, char *argv[]) * * COPYRIGHT * --------- - * Copyright (c) Jonas Fonseca , 2006 + * Copyright (c) 2006 Jonas Fonseca * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by