X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/ff26aa294779c69563bb7d92c6e08f5a7128b074..9a66631561319b86f54f20346d953070238cc346:/tig.c diff --git a/tig.c b/tig.c index 53d4770..055ad11 100644 --- a/tig.c +++ b/tig.c @@ -1,5 +1,15 @@ /* 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 as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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) * ====== @@ -54,6 +64,7 @@ static void die(const char *err, ...); static void report(const char *msg, ...); +static int read_properties(const char *cmd, int separator, int (*read)(char *, int, char *, int)); static void set_nonblocking_input(bool loading); static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed); @@ -113,11 +124,11 @@ enum request { REQ_TOGGLE_LINE_NUMBERS, REQ_VIEW_NEXT, REQ_VIEW_CLOSE, + REQ_NEXT, + REQ_PREVIOUS, REQ_MOVE_UP, - REQ_MOVE_UP_ENTER, REQ_MOVE_DOWN, - REQ_MOVE_DOWN_ENTER, REQ_MOVE_PAGE_UP, REQ_MOVE_PAGE_DOWN, REQ_MOVE_FIRST_LINE, @@ -227,6 +238,8 @@ static int opt_num_interval = NUMBER_INTERVAL; static int opt_tab_size = TABSIZE; static enum request opt_request = REQ_VIEW_MAIN; static char opt_cmd[SIZEOF_CMD] = ""; +static char opt_encoding[20] = ""; +static bool opt_utf8 = TRUE; static FILE *opt_pipe = NULL; /* Returns the index of log or diff command or -1 to exit. */ @@ -280,14 +293,14 @@ parse_options(int argc, char *argv[]) } /** - * -t[NSPACES], --tab-size[=NSPACES]:: + * -b[NSPACES], --tab-size[=NSPACES]:: * Set the number of spaces tabs should be expanded to. **/ - if (!strncmp(opt, "-t", 2) || + if (!strncmp(opt, "-b", 2) || !strncmp(opt, "--tab-size", 10)) { char *num = opt; - if (opt[1] == 't') { + if (opt[1] == 'b') { num = opt + 2; } else if (opt[STRING_SIZE("--tab-size")] == '=') { @@ -425,10 +438,89 @@ parse_options(int argc, char *argv[]) } + if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8")) + opt_utf8 = FALSE; + return TRUE; } +/** + * 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" + +/** + * [[history-commands]] + * History 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 +634,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 +659,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 +668,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 +685,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 +711,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 +719,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 +780,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; } @@ -788,6 +820,10 @@ redraw_view(struct view *view) * [main] c622eefaa485995320bc743431bae0d497b1d875 - commit 1 of 61 (1%) * * By default, the title of the current view is highlighted using bold font. + * For long loading views (taking over 3 seconds) the time since loading + * started will be appended: + * + * [main] 77d9e40fbcea3238015aea403e06f61542df9a31 - commit 1 of 779 (0%) 5s **/ static void @@ -806,14 +842,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); } + wmove(view->title, 0, view->width - 1); wrefresh(view->title); } @@ -847,11 +896,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,10 +908,9 @@ 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); } offset += view->height + 1; @@ -884,6 +929,20 @@ redraw_display(void) } } +static void +update_display_cursor(void) +{ + 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 + * the cursor does not jump around. */ + if (view->lines) { + wmove(view->win, view->lineno - view->offset, view->width - 1); + wrefresh(view->win); + } +} /* * Navigation @@ -891,7 +950,7 @@ redraw_display(void) /* Scrolling backend */ static void -do_scroll_view(struct view *view, int lines) +do_scroll_view(struct view *view, int lines, bool redraw) { /* The rendering expects the new offset. */ view->offset += lines; @@ -910,7 +969,7 @@ do_scroll_view(struct view *view, int lines) wscrl(view->win, lines); for (; line < end; line++) { - if (!view->ops->draw(view, line)) + if (!draw_view_line(view, line)) break; } } @@ -918,7 +977,7 @@ do_scroll_view(struct view *view, int lines) /* 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,11 +987,14 @@ do_scroll_view(struct view *view, int lines) 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); + if (!redraw) + return; + redrawwin(view->win); wrefresh(view->win); report(""); @@ -975,12 +1037,12 @@ scroll_view(struct view *view, enum request request) die("request %d not handled in switch", request); } - do_scroll_view(view, lines); + do_scroll_view(view, lines, TRUE); } /* Cursor moving */ static void -move_view(struct view *view, enum request request) +move_view(struct view *view, enum request request, bool redraw) { int steps; @@ -1004,12 +1066,10 @@ move_view(struct view *view, enum request request) break; case REQ_MOVE_UP: - case REQ_MOVE_UP_ENTER: steps = -1; break; case REQ_MOVE_DOWN: - case REQ_MOVE_DOWN_ENTER: steps = 1; break; @@ -1036,7 +1096,7 @@ move_view(struct view *view, enum request request) 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 */ @@ -1054,12 +1114,15 @@ move_view(struct view *view, enum request request) } } - do_scroll_view(view, steps); + do_scroll_view(view, steps, redraw); return; } /* Draw the current line */ - view->ops->draw(view, view->lineno - view->offset); + draw_view_line(view, view->lineno - view->offset); + + if (!redraw) + return; redrawwin(view->win); wrefresh(view->win); @@ -1071,11 +1134,27 @@ move_view(struct view *view, enum request request) * Incremental updating */ +static void +end_update(struct view *view) +{ + if (!view->pipe) + return; + set_nonblocking_input(FALSE); + if (view->pipe == stdin) + fclose(view->pipe); + else + pclose(view->pipe); + view->pipe = NULL; +} + static bool begin_update(struct view *view) { const char *id = view->id; + if (view->pipe) + end_update(view); + if (opt_cmd[0]) { string_copy(view->cmd, opt_cmd); opt_cmd[0] = 0; @@ -1112,8 +1191,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; @@ -1124,25 +1203,12 @@ begin_update(struct view *view) return TRUE; } -static void -end_update(struct view *view) -{ - if (!view->pipe) - return; - set_nonblocking_input(FALSE); - if (view->pipe == stdin) - fclose(view->pipe); - else - pclose(view->pipe); - view->pipe = NULL; -} - static bool 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. */ @@ -1165,10 +1231,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) @@ -1209,8 +1279,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; @@ -1225,8 +1293,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; } @@ -1254,7 +1321,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); @@ -1278,7 +1346,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. */ @@ -1286,25 +1358,23 @@ 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); + do_scroll_view(prev, lines, TRUE); } if (prev && view != prev) { - /* "Blur" the previous view. */ - if (!backgrounded) + if (split && !backgrounded) { + /* "Blur" the previous view. */ update_view_title(prev); + } - /* Continue loading split views in the background. */ - if (!split) - end_update(prev); view->parent = prev; } - if (view->pipe) { + if (view->pipe && view->lines == 0) { /* 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)) @@ -1336,7 +1406,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); + move_view(view, request, TRUE); break; case REQ_SCROLL_LINE_DOWN: @@ -1354,9 +1424,22 @@ view_driver(struct view *view, enum request request) open_view(view, request, OPEN_DEFAULT); break; - case REQ_MOVE_UP_ENTER: - case REQ_MOVE_DOWN_ENTER: - move_view(view, request); + case REQ_NEXT: + case REQ_PREVIOUS: + request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP; + + if (view == VIEW(REQ_VIEW_DIFF) && + view->parent == VIEW(REQ_VIEW_MAIN)) { + bool redraw = display[1] == view; + + view = view->parent; + move_view(view, request, redraw); + if (redraw) + update_view_title(view); + } else { + move_view(view, request, TRUE); + break; + } /* Fall-through */ case REQ_ENTER: @@ -1364,11 +1447,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) { @@ -1393,9 +1476,10 @@ view_driver(struct view *view, enum request request) break; case REQ_STOP_LOADING: - foreach_view (view, i) { + for (i = 0; i < ARRAY_SIZE(views); i++) { + view = &views[i]; if (view->pipe) - report("Stopped loaded the %s view", view->name), + report("Stopped loading the %s view", view->name), end_update(view); } break; @@ -1416,11 +1500,15 @@ view_driver(struct view *view, enum request request) return TRUE; case REQ_VIEW_CLOSE: - if (view->parent) { + /* XXX: Mark closed views by letting view->parent point to the + * view itself. Parents to closed view should never be + * followed. */ + if (view->parent && + view->parent->parent != view->parent) { memset(display, 0, sizeof(display)); current_view = 0; display[current_view] = view->parent; - view->parent = NULL; + view->parent = view; resize_display(); redraw_display(); break; @@ -1444,24 +1532,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); } @@ -1472,8 +1554,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; @@ -1493,66 +1573,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; } @@ -1563,8 +1642,8 @@ pager_enter(struct view *view) scroll_view(view, REQ_SCROLL_LINE_DOWN); /* FIXME: A minor workaround. Scrolling the view will call report("") - * but if we are scolling a non-current view this won't properly update - * the view title. */ + * but if we are scrolling a non-current view this won't properly + * update the view title. */ if (split) update_view_title(view); @@ -1592,20 +1671,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; + int trimmed = 1; - if (view->offset + lineno >= view->lines) - return FALSE; - - commit = view->line[view->offset + lineno]; if (!*commit->author) return FALSE; @@ -1632,8 +1707,15 @@ main_draw(struct view *view, unsigned int lineno) if (type != LINE_CURSOR) wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR)); - /* FIXME: Make this optional, and add i18n.commitEncoding support. */ - authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed); + if (opt_utf8) { + authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed); + } else { + authorlen = strlen(commit->author); + if (authorlen > AUTHOR_COLS - 2) { + authorlen = AUTHOR_COLS - 2; + trimmed = 1; + } + } if (trimmed) { waddnstr(view->win, commit->author, authorlen); @@ -1689,7 +1771,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; @@ -1702,7 +1784,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; @@ -1712,12 +1794,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 */ @@ -1751,13 +1837,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; @@ -1776,9 +1861,11 @@ main_read(struct view *view, char *line) } static bool -main_enter(struct view *view) +main_enter(struct view *view, struct line *line) { - open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT); + enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT; + + open_view(view, REQ_VIEW_DIFF, flags); return TRUE; } @@ -1815,6 +1902,16 @@ static struct keymap keymap[] = { * Switch to pager view. * h:: * Show man page. + **/ + { 'm', REQ_VIEW_MAIN }, + { 'd', REQ_VIEW_DIFF }, + { 'l', REQ_VIEW_LOG }, + { 'p', REQ_VIEW_PAGER }, + { 'h', REQ_VIEW_HELP }, + + /** + * View manipulation + * ~~~~~~~~~~~~~~~~~ * q:: * Close view, if multiple views are open it will jump back to the * previous view in the view stack. If it is the last open view it @@ -1826,30 +1923,28 @@ static struct keymap keymap[] = { * pressing Enter will simply scroll the view one line down. * Tab:: * Switch to next view. + * Up:: + * This key is "context sensitive" and will move the cursor one + * line up. However, uf you opened a diff view from the main view + * (split- or full-screen) it will change the cursor to point to + * the previous commit in the main view and update the diff view + * to display it. + * Down:: + * Similar to 'Up' but will move down. **/ - { 'm', REQ_VIEW_MAIN }, - { 'd', REQ_VIEW_DIFF }, - { 'l', REQ_VIEW_LOG }, - { 'p', REQ_VIEW_PAGER }, - { 'h', REQ_VIEW_HELP }, - { 'q', REQ_VIEW_CLOSE }, { KEY_TAB, REQ_VIEW_NEXT }, { KEY_RETURN, REQ_ENTER }, + { KEY_UP, REQ_PREVIOUS }, + { KEY_DOWN, REQ_NEXT }, /** * Cursor navigation * ~~~~~~~~~~~~~~~~~ - * Up:: + * j:: * Move cursor one line up. - * Down:: - * Move cursor one line down. * k:: - * Move cursor one line up and enter. When used in the main view - * this will always show the diff of the current commit in the - * split diff view. - * j:: - * Move cursor one line down and enter. + * Move cursor one line down. * PgUp:: * b:: * -:: @@ -1862,10 +1957,8 @@ static struct keymap keymap[] = { * End:: * Jump to last line. **/ - { KEY_UP, REQ_MOVE_UP }, - { KEY_DOWN, REQ_MOVE_DOWN }, - { 'k', REQ_MOVE_UP_ENTER }, - { 'j', REQ_MOVE_DOWN_ENTER }, + { 'k', REQ_MOVE_UP }, + { 'j', REQ_MOVE_DOWN }, { KEY_HOME, REQ_MOVE_FIRST_LINE }, { KEY_END, REQ_MOVE_LAST_LINE }, { KEY_NPAGE, REQ_MOVE_PAGE_DOWN }, @@ -1959,7 +2052,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 */ @@ -2135,15 +2228,7 @@ report(const char *msg, ...) } update_view_title(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 - * the cursor does not jump around. */ - if (view->lines) { - wmove(view->win, view->lineno - view->offset, view->width - 1); - wrefresh(view->win); - } + update_display_cursor(); } /* Controls when nodelay should be in effect when polling user input. */ @@ -2255,73 +2340,120 @@ get_refs(char *id) } static int +read_ref(char *id, int idlen, char *name, int namelen) +{ + struct ref *ref; + bool tag = FALSE; + bool tag_commit = FALSE; + + /* Commits referenced by tags has "^{}" appended. */ + if (name[namelen - 1] == '}') { + while (namelen > 0 && name[namelen] != '^') + namelen--; + if (namelen > 0) + tag_commit = TRUE; + name[namelen] = 0; + } + + if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) { + if (!tag_commit) + return OK; + name += STRING_SIZE("refs/tags/"); + tag = TRUE; + + } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) { + name += STRING_SIZE("refs/heads/"); + + } else if (!strcmp(name, "HEAD")) { + return OK; + } + + refs = realloc(refs, sizeof(*refs) * (refs_size + 1)); + if (!refs) + return ERR; + + ref = &refs[refs_size++]; + ref->name = strdup(name); + if (!ref->name) + return ERR; + + ref->tag = tag; + string_copy(ref->id, id); + + return OK; +} + +static int load_refs(void) { const char *cmd_env = getenv("TIG_LS_REMOTE"); const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE; - FILE *pipe = popen(cmd, "r"); - char buffer[BUFSIZ]; - char *line; - if (!pipe) - return ERR; + return read_properties(cmd, '\t', read_ref); +} - while ((line = fgets(buffer, sizeof(buffer), pipe))) { - char *name = strchr(line, '\t'); - struct ref *ref; - int namelen; - bool tag = FALSE; - bool tag_commit = FALSE; +static int +read_config_option(char *name, int namelen, char *value, int valuelen) +{ + if (!strcmp(name, "i18n.commitencoding")) { + string_copy(opt_encoding, value); + } - if (!name) - continue; + return OK; +} - *name++ = 0; - namelen = strlen(name) - 1; +static int +load_config(void) +{ + return read_properties("git repo-config --list", '=', + read_config_option); +} - /* Commits referenced by tags has "^{}" appended. */ - if (name[namelen - 1] == '}') { - while (namelen > 0 && name[namelen] != '^') - namelen--; - if (namelen > 0) - tag_commit = TRUE; - } - name[namelen] = 0; +static int +read_properties(const char *cmd, int separator, + int (*read_property)(char *, int, char *, int)) +{ + FILE *pipe = popen(cmd, "r"); + char buffer[BUFSIZ]; + char *name; + int state = OK; - if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) { - if (!tag_commit) - continue; - name += STRING_SIZE("refs/tags/"); - tag = TRUE; + if (!pipe) + return ERR; - } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) { - name += STRING_SIZE("refs/heads/"); + while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) { + char *value = strchr(name, separator); + int namelen; + int valuelen; + + if (value) { + namelen = value - name; + *value++ = 0; + valuelen = strlen(value); + if (valuelen > 0) { + valuelen--; + value[valuelen] = 0; + } - } else if (!strcmp(name, "HEAD")) { - continue; + } else { + namelen = strlen(name); + value = ""; + valuelen = 0; } - refs = realloc(refs, sizeof(*refs) * (refs_size + 1)); - if (!refs) - return ERR; - - ref = &refs[refs_size++]; - ref->tag = tag; - ref->name = strdup(name); - if (!ref->name) - return ERR; - - string_copy(ref->id, line); + if (namelen) + state = read_property(name, namelen, value, valuelen); } - if (ferror(pipe)) - return ERR; + if (state != ERR && ferror(pipe)) + state = ERR; pclose(pipe); - return OK; + return state; } + /* * Main */ @@ -2366,6 +2498,11 @@ main(int argc, char *argv[]) signal(SIGINT, quit); + /* Load the repo config file first so options can be overwritten from + * the command line. */ + if (load_config() == ERR) + die("Failed to load repo config."); + if (!parse_options(argc, argv)) return 0; @@ -2408,7 +2545,9 @@ main(int argc, char *argv[]) memcpy(opt_cmd, "git ", 4); opt_request = REQ_VIEW_PAGER; } else { - request = ERR; + report("Prompt interrupted by loading view, " + "press 'z' to stop loading views"); + request = REQ_SCREEN_UPDATE; } noecho(); @@ -2450,7 +2589,8 @@ main(int argc, char *argv[]) * * You can tune the interaction with git by making use of the options * explained in this section. For example, by configuring the environment - * variables described in the <> section. + * variables described in the <> + * section. * * Limit by path name * ~~~~~~~~~~~~~~~~~~ @@ -2543,6 +2683,11 @@ 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. + * + * - The prompt doesn't work while loading. + * * TODO * ---- * Features that should be explored. @@ -2553,7 +2698,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 @@ -2562,10 +2707,12 @@ main(int argc, char *argv[]) * * SEE ALSO * -------- - * [verse] - * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)], - * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)] - * gitk(1): git repository browser written using tcl/tk, - * qgit(1): git repository browser written using c++/Qt, - * gitview(1): git repository browser written using python/gtk. + * - link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)], + * - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)] + * + * Other git repository browsers: + * + * - gitk(1) + * - qgit(1) + * - gitview(1) **/