X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/3c571d672aa2edbe8afc04cf452ea323eef96f6c..HEAD:/tig.c diff --git a/tig.c b/tig.c index e48af7e..ebd7abe 100644 --- a/tig.c +++ b/tig.c @@ -12,7 +12,7 @@ */ #ifndef VERSION -#define VERSION "tig-0.4.git" +#define VERSION "tig-0.5.git" #endif #ifndef DEBUG @@ -59,6 +59,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, #define SIZEOF_STR 1024 /* Default string size. */ #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */ +#define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */ #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */ /* This color name can be used to refer to the default term colors. */ @@ -80,7 +81,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3) #define TIG_LS_REMOTE \ - "git ls-remote . 2>/dev/null" + "git ls-remote $(git rev-parse --git-dir) 2>/dev/null" #define TIG_DIFF_CMD \ "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null" @@ -109,7 +110,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, struct ref { char *name; /* Ref name; tag or head names are shortened. */ - char id[41]; /* Commit SHA1 ID */ + char id[SIZEOF_REV]; /* Commit SHA1 ID */ unsigned int tag:1; /* Is it a tag? */ unsigned int next:1; /* For ref lists: are there more refs? */ }; @@ -450,6 +451,17 @@ parse_options(int argc, char *argv[]) for (i = 1; i < argc; i++) { char *opt = argv[i]; + if (!strcmp(opt, "log") || + !strcmp(opt, "diff") || + !strcmp(opt, "show")) { + opt_request = opt[0] == 'l' + ? REQ_VIEW_LOG : REQ_VIEW_DIFF; + break; + } + + if (opt[0] && opt[0] != '-') + break; + if (!strcmp(opt, "-l")) { opt_request = REQ_VIEW_LOG; continue; @@ -485,17 +497,6 @@ parse_options(int argc, char *argv[]) break; } - if (!strcmp(opt, "log") || - !strcmp(opt, "diff") || - !strcmp(opt, "show")) { - opt_request = opt[0] == 'l' - ? REQ_VIEW_LOG : REQ_VIEW_DIFF; - break; - } - - if (opt[0] && opt[0] != '-') - break; - die("unknown option '%s'\n\n%s", opt, usage); } @@ -509,7 +510,7 @@ parse_options(int argc, char *argv[]) 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"); + string_copy(opt_cmd, "git log --pretty=raw"); else string_copy(opt_cmd, "git"); buf_size = strlen(opt_cmd); @@ -523,7 +524,6 @@ parse_options(int argc, char *argv[]) die("command too long"); opt_cmd[buf_size] = 0; - } if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8")) @@ -565,6 +565,7 @@ LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \ LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \ LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \ +LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \ LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \ LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \ LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \ @@ -728,9 +729,6 @@ static struct keybinding default_keybindings[] = { { 'g', REQ_TOGGLE_REV_GRAPH }, { ':', REQ_PROMPT }, - /* wgetch() with nodelay() enabled returns ERR when there's no input. */ - { ERR, REQ_NONE }, - /* Using the ncurses SIGWINCH handler. */ { KEY_RESIZE, REQ_SCREEN_RESIZE }, }; @@ -1162,6 +1160,9 @@ struct view_ops; static struct view *display[2]; static unsigned int current_view; +/* Reading from the prompt? */ +static bool input_mode = FALSE; + #define foreach_displayed_view(view, i) \ for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++) @@ -1263,6 +1264,7 @@ draw_view_line(struct view *view, unsigned int lineno) { struct line *line; bool selected = (view->offset + lineno == view->lineno); + bool draw_ok; assert(view_is_displayed(view)); @@ -1280,7 +1282,11 @@ draw_view_line(struct view *view, unsigned int lineno) wclrtoeol(view->win); } - return view->ops->draw(view, line, lineno, selected); + scrollok(view->win, FALSE); + draw_ok = view->ops->draw(view, line, lineno, selected); + scrollok(view->win, TRUE); + + return draw_ok; } static void @@ -1294,7 +1300,10 @@ redraw_view_from(struct view *view, int lineno) } redrawwin(view->win); - wrefresh(view->win); + if (input_mode) + wnoutrefresh(view->win); + else + wrefresh(view->win); } static void @@ -1308,20 +1317,11 @@ redraw_view(struct view *view) static void update_view_title(struct view *view) { - assert(view_is_displayed(view)); - - if (view == display[current_view]) - wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS)); - else - wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR)); - - werase(view->title); - wmove(view->title, 0, 0); + char buf[SIZEOF_STR]; + char state[SIZEOF_STR]; + size_t bufpos = 0, statelen = 0; - if (*view->ref) - wprintw(view->title, "[%s] %s", view->name, view->ref); - else - wprintw(view->title, "[%s]", view->name); + assert(view_is_displayed(view)); if (view->lines || view->pipe) { unsigned int view_lines = view->offset + view->height; @@ -1329,23 +1329,48 @@ update_view_title(struct view *view) ? MIN(view_lines, view->lines) * 100 / view->lines : 0; - wprintw(view->title, " - %s %d of %d (%d%%)", - view->ops->type, - view->lineno + 1, - view->lines, - lines); + string_format_from(state, &statelen, "- %s %d of %d (%d%%)", + view->ops->type, + view->lineno + 1, + view->lines, + lines); + + if (view->pipe) { + time_t secs = time(NULL) - view->start_time; + + /* Three git seconds are a long time ... */ + if (secs > 2) + string_format_from(state, &statelen, " %lds", secs); + } } - if (view->pipe) { - time_t secs = time(NULL) - view->start_time; + string_format_from(buf, &bufpos, "[%s]", view->name); + if (*view->ref && bufpos < view->width) { + size_t refsize = strlen(view->ref); + size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen; - /* Three git seconds are a long time ... */ - if (secs > 2) - wprintw(view->title, " %lds", secs); + if (minsize < view->width) + refsize = view->width - minsize + 7; + string_format_from(buf, &bufpos, " %.*s", refsize, view->ref); } + if (statelen && bufpos < view->width) { + string_format_from(buf, &bufpos, " %s", state); + } + + if (view == display[current_view]) + wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS)); + else + wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR)); + + mvwaddnstr(view->title, 0, 0, buf, bufpos); + wclrtoeol(view->title); wmove(view->title, 0, view->width - 1); - wrefresh(view->title); + + if (input_mode) + wnoutrefresh(view->title); + else + wrefresh(view->title); } static void @@ -1412,10 +1437,8 @@ redraw_display(void) } static void -update_display_cursor(void) +update_display_cursor(struct view *view) { - struct view *view = display[current_view]; - /* Move the cursor to the right-most column of the cursor line. * * XXX: This could turn out to be a bit expensive, but it ensures that @@ -1432,9 +1455,9 @@ update_display_cursor(void) /* Scrolling backend */ static void -do_scroll_view(struct view *view, int lines, bool redraw) +do_scroll_view(struct view *view, int lines) { - assert(view_is_displayed(view)); + bool redraw_current_line = FALSE; /* The rendering expects the new offset. */ view->offset += lines; @@ -1442,6 +1465,17 @@ do_scroll_view(struct view *view, int lines, bool redraw) assert(0 <= view->offset && view->offset < view->lines); assert(lines); + /* Move current line into the view. */ + if (view->lineno < view->offset) { + view->lineno = view->offset; + redraw_current_line = TRUE; + } else if (view->lineno >= view->offset + view->height) { + view->lineno = view->offset + view->height - 1; + redraw_current_line = TRUE; + } + + assert(view->offset <= view->lineno && view->lineno < view->lines); + /* Redraw the whole screen if scrolling is pointless. */ if (view->height < ABS(lines)) { redraw_view(view); @@ -1456,29 +1490,11 @@ do_scroll_view(struct view *view, int lines, bool redraw) if (!draw_view_line(view, line)) break; } - } - /* Move current line into the view. */ - if (view->lineno < view->offset) { - view->lineno = view->offset; - draw_view_line(view, 0); - - } else if (view->lineno >= view->offset + view->height) { - if (view->lineno == view->offset + view->height) { - /* Clear the hidden line so it doesn't show if the view - * is scrolled up. */ - wmove(view->win, view->height, 0); - wclrtoeol(view->win); - } - view->lineno = view->offset + view->height - 1; - draw_view_line(view, view->lineno - view->offset); + if (redraw_current_line) + draw_view_line(view, view->lineno - view->offset); } - assert(view->offset <= view->lineno && view->lineno < view->lines); - - if (!redraw) - return; - redrawwin(view->win); wrefresh(view->win); report(""); @@ -1490,6 +1506,8 @@ scroll_view(struct view *view, enum request request) { int lines = 1; + assert(view_is_displayed(view)); + switch (request) { case REQ_SCROLL_PAGE_DOWN: lines = view->height; @@ -1521,13 +1539,14 @@ scroll_view(struct view *view, enum request request) die("request %d not handled in switch", request); } - do_scroll_view(view, lines, TRUE); + do_scroll_view(view, lines); } /* Cursor moving */ static void -move_view(struct view *view, enum request request, bool redraw) +move_view(struct view *view, enum request request) { + int scroll_steps = 0; int steps; switch (request) { @@ -1574,34 +1593,40 @@ move_view(struct view *view, enum request request, bool redraw) view->lineno += steps; assert(0 <= view->lineno && view->lineno < view->lines); - /* Repaint the old "current" line if we be scrolling */ - if (ABS(steps) < view->height) - draw_view_line(view, view->lineno - steps - view->offset); - /* Check whether the view needs to be scrolled */ if (view->lineno < view->offset || view->lineno >= view->offset + view->height) { + scroll_steps = steps; if (steps < 0 && -steps > view->offset) { - steps = -view->offset; + scroll_steps = -view->offset; } else if (steps > 0) { if (view->lineno == view->lines - 1 && view->lines > view->height) { - steps = view->lines - view->offset - 1; - if (steps >= view->height) - steps -= view->height - 1; + scroll_steps = view->lines - view->offset - 1; + if (scroll_steps >= view->height) + scroll_steps -= view->height - 1; } } + } - do_scroll_view(view, steps, redraw); + if (!view_is_displayed(view)) { + view->offset += steps; + view->ops->select(view, &view->line[view->lineno]); return; } - /* Draw the current line */ - draw_view_line(view, view->lineno - view->offset); + /* Repaint the old "current" line if we be scrolling */ + if (ABS(steps) < view->height) + draw_view_line(view, view->lineno - steps - view->offset); - if (!redraw) + if (scroll_steps) { + do_scroll_view(view, scroll_steps); return; + } + + /* Draw the current line */ + draw_view_line(view, view->lineno - view->offset); redrawwin(view->win); wrefresh(view->win); @@ -1613,7 +1638,7 @@ move_view(struct view *view, enum request request, bool redraw) * Searching */ -static void search_view(struct view *view, enum request request, const char *search); +static void search_view(struct view *view, enum request request); static bool find_next_line(struct view *view, unsigned long lineno, struct line *line) @@ -1653,7 +1678,7 @@ find_next(struct view *view, enum request request) if (!*opt_search) report("No previous search"); else - search_view(view, request, opt_search); + search_view(view, request); return; } @@ -1688,7 +1713,7 @@ find_next(struct view *view, enum request request) } static void -search_view(struct view *view, enum request request, const char *search) +search_view(struct view *view, enum request request) { int regex_err; @@ -1701,7 +1726,7 @@ search_view(struct view *view, enum request request, const char *search) return; } - regex_err = regcomp(view->regex, search, REG_EXTENDED); + regex_err = regcomp(view->regex, opt_search, REG_EXTENDED); if (regex_err != 0) { char buf[SIZEOF_STR] = "unknown error"; @@ -1710,7 +1735,7 @@ search_view(struct view *view, enum request request, const char *search) return; } - string_copy(view->grep, search); + string_copy(view->grep, opt_search); find_next(view, request); } @@ -2016,7 +2041,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags) /* Scroll the view that was split if the current line is * outside the new limited view. */ - do_scroll_view(prev, lines, TRUE); + do_scroll_view(prev, lines); } if (prev && view != prev) { @@ -2061,7 +2086,7 @@ view_driver(struct view *view, enum request request) case REQ_MOVE_PAGE_DOWN: case REQ_MOVE_FIRST_LINE: case REQ_MOVE_LAST_LINE: - move_view(view, request, TRUE); + move_view(view, request); break; case REQ_SCROLL_LINE_DOWN: @@ -2094,14 +2119,12 @@ view_driver(struct view *view, enum request request) view->parent == VIEW(REQ_VIEW_MAIN)) || (view == VIEW(REQ_VIEW_BLOB) && view->parent == VIEW(REQ_VIEW_TREE))) { - bool redraw = display[1] == view; - view = view->parent; - move_view(view, request, redraw); - if (redraw) + move_view(view, request); + if (view_is_displayed(view)) update_view_title(view); } else { - move_view(view, request, TRUE); + move_view(view, request); break; } /* Fall-through */ @@ -2146,7 +2169,7 @@ view_driver(struct view *view, enum request request) case REQ_SEARCH: case REQ_SEARCH_BACK: - search_view(view, request, opt_search); + search_view(view, request); break; case REQ_FIND_NEXT: @@ -2287,7 +2310,7 @@ add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep) char *ref = NULL; FILE *pipe; - if (!string_format(refbuf, "git describe %s", commit_id)) + if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id)) return TRUE; pipe = popen(refbuf, "r"); @@ -2446,7 +2469,7 @@ static struct view_ops pager_ops = { * Tree backend */ -/* Parse output from git ls-tree: +/* Parse output from git-ls-tree(1): * * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README @@ -2556,8 +2579,7 @@ tree_read(struct view *view, char *text) static bool tree_enter(struct view *view, struct line *line) { - enum open_flags flags = OPEN_DEFAULT; - char *data = line->data; + enum open_flags flags; enum request request; switch (line->type) { @@ -2576,6 +2598,7 @@ tree_enter(struct view *view, struct line *line) } else { size_t pathlen = strlen(opt_path); size_t origlen = pathlen; + char *data = line->data; char *basename = data + SIZEOF_TREE_ATTR; if (!string_format_from(opt_path, &pathlen, "%s/", basename)) { @@ -2586,15 +2609,12 @@ tree_enter(struct view *view, struct line *line) /* Trees and subtrees share the same ID, so they are not not * unique like blobs. */ - flags |= OPEN_RELOAD; + flags = OPEN_RELOAD; request = REQ_VIEW_TREE; break; case LINE_TREE_FILE: - /* This causes the blob view to become split, and not having it - * in the tree dir case will make the blob view automatically - * disappear when moving to a different directory. */ - flags |= OPEN_SPLIT; + flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT; request = REQ_VIEW_BLOB; break; @@ -2604,29 +2624,27 @@ tree_enter(struct view *view, struct line *line) open_view(view, request, flags); - if (!VIEW(request)->pipe) - return TRUE; - - /* For tree views insert the path to the parent as the first line. */ - if (request == REQ_VIEW_BLOB) { - /* Mirror what is showed in the title bar. */ - string_ncopy(ref_blob, data + STRING_SIZE("100644 blob "), 40); - string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob); - return TRUE; - } - return TRUE; } static void tree_select(struct view *view, struct line *line) { - if (line->type == LINE_TREE_DIR || line->type == LINE_TREE_FILE) { - char *text = line->data; + char *text = line->data; + + text += STRING_SIZE("100644 blob "); + + if (line->type == LINE_TREE_FILE) { + string_ncopy(ref_blob, text, 40); + /* Also update the blob view's ref, since all there must always + * be in sync. */ + string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob); - string_ncopy(view->ref, text + STRING_SIZE("100644 blob "), 40); - string_copy(ref_blob, view->ref); + } else if (line->type != LINE_TREE_DIR) { + return; } + + string_ncopy(view->ref, text, 40); } static struct view_ops tree_ops = { @@ -2664,8 +2682,8 @@ static struct view_ops blob_ops = { */ struct commit { - char id[41]; /* SHA1 ID. */ - char title[75]; /* First line of the commit message. */ + char id[SIZEOF_REV]; /* SHA1 ID. */ + char title[128]; /* First line of the commit message. */ char author[75]; /* Author of the commit. */ struct tm time; /* Date from the author ident. */ struct ref **refs; /* Repository references. */ @@ -2804,43 +2822,32 @@ main_read(struct view *view, char *line) case LINE_AUTHOR: { + /* Parse author lines where the name may be empty: + * author 1138474660 +0100 + */ char *ident = line + STRING_SIZE("author "); - char *end = strchr(ident, '<'); + char *nameend = strchr(ident, '<'); + char *emailend = strchr(ident, '>'); - if (!commit) + if (!commit || !nameend || !emailend) break; - if (end) { - char *email = end + 1; - - for (; end > ident && isspace(end[-1]); end--) ; - - if (end == ident && *email) { - ident = email; - end = strchr(ident, '>'); - for (; end > ident && isspace(end[-1]); end--) ; - } - *end = 0; + *nameend = *emailend = 0; + ident = chomp_string(ident); + if (!*ident) { + ident = chomp_string(nameend + 1); + if (!*ident) + ident = "Unknown"; } - /* End is NULL or ident meaning there's no author. */ - if (end <= ident) - ident = "Unknown"; - string_copy(commit->author, ident); /* Parse epoch and timezone */ - if (end) { - char *secs = strchr(end + 1, '>'); - char *zone; - time_t time; + if (emailend[1] == ' ') { + char *secs = emailend + 2; + char *zone = strchr(secs, ' '); + time_t time = (time_t) atol(secs); - if (!secs || secs[1] != ' ') - break; - - secs += 2; - time = (time_t) atol(secs); - zone = strchr(secs, ' '); if (zone && strlen(zone) == STRING_SIZE(" +0700")) { long tz; @@ -2855,6 +2862,7 @@ main_read(struct view *view, char *line) time -= tz; } + gmtime_r(&time, &commit->time); } break; @@ -2869,13 +2877,19 @@ 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[4])) + if (strncmp(line, " ", 4)) break; + line += 4; + /* Well, if the title starts with a whitespace character, + * try to be forgiving. Otherwise we end up with no title. */ + while (isspace(*line)) + line++; + if (*line == '\0') + break; + /* FIXME: More graceful handling of titles; append "..." to + * shortened titles, etc. */ - string_copy(commit->title, line + 4); + string_copy(commit->title, line); } return TRUE; @@ -3110,33 +3124,37 @@ static bool cursed = FALSE; /* The status window is used for polling keystrokes. */ static WINDOW *status_win; +static bool status_empty = TRUE; + /* Update status and title window. */ static void report(const char *msg, ...) { - static bool empty = TRUE; struct view *view = display[current_view]; - if (!empty || *msg) { + if (input_mode) + return; + + if (!status_empty || *msg) { va_list args; va_start(args, msg); - werase(status_win); wmove(status_win, 0, 0); if (*msg) { vwprintw(status_win, msg, args); - empty = FALSE; + status_empty = FALSE; } else { - empty = TRUE; + status_empty = TRUE; } + wclrtoeol(status_win); wrefresh(status_win); va_end(args); } update_view_title(view); - update_display_cursor(); + update_display_cursor(view); } /* Controls when nodelay should be in effect when polling user input. */ @@ -3199,10 +3217,16 @@ read_prompt(const char *prompt) struct view *view; int i, key; + input_mode = TRUE; + foreach_view (view, i) update_view(view); - report("%s%.*s", prompt, pos, buf); + input_mode = FALSE; + + mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf); + wclrtoeol(status_win); + /* Refresh, accept single keystroke of input */ key = wgetch(status_win); switch (key) { @@ -3237,11 +3261,12 @@ read_prompt(const char *prompt) } } - if (status == CANCEL) { - /* Clear the status window */ - report(""); + /* Clear the status window */ + status_empty = FALSE; + report(""); + + if (status == CANCEL) return NULL; - } buf[pos++] = 0; @@ -3500,6 +3525,13 @@ main(int argc, char *argv[]) /* Refresh, accept single keystroke of input */ key = wgetch(status_win); + /* wgetch() with nodelay() enabled returns ERR when there's no + * input. */ + if (key == ERR) { + request = REQ_NONE; + continue; + } + request = get_keybinding(display[current_view]->keymap, key); /* Some low-level request handling. This keeps access to