1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
15 #define VERSION "tig-0.3"
35 static void die(const char *err, ...);
36 static void report(const char *msg, ...);
37 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
38 static void set_nonblocking_input(bool loading);
39 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
40 static void load_help_page(void);
42 #define ABS(x) ((x) >= 0 ? (x) : -(x))
43 #define MIN(x, y) ((x) < (y) ? (x) : (y))
45 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
46 #define STRING_SIZE(x) (sizeof(x) - 1)
48 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
49 #define SIZEOF_CMD 1024 /* Size of command buffer. */
50 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
52 /* This color name can be used to refer to the default term colors. */
53 #define COLOR_DEFAULT (-1)
55 /* The format and size of the date column in the main view. */
56 #define DATE_FORMAT "%Y-%m-%d %H:%M"
57 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
59 #define AUTHOR_COLS 20
61 /* The default interval between line numbers. */
62 #define NUMBER_INTERVAL 1
66 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
68 #define TIG_LS_REMOTE \
69 "git ls-remote . 2>/dev/null"
71 #define TIG_DIFF_CMD \
72 "git show --patch-with-stat --find-copies-harder -B -C %s"
75 "git log --cc --stat -n100 %s"
77 #define TIG_MAIN_CMD \
78 "git log --topo-order --stat --pretty=raw %s"
80 /* XXX: Needs to be defined to the empty string. */
81 #define TIG_HELP_CMD ""
82 #define TIG_PAGER_CMD ""
84 /* Some ascii-shorthands fitted into the ncurses namespace. */
86 #define KEY_RETURN '\r'
91 char *name; /* Ref name; tag or head names are shortened. */
92 char id[41]; /* Commit SHA1 ID */
93 unsigned int tag:1; /* Is it a tag? */
94 unsigned int next:1; /* For ref lists: are there more refs? */
97 static struct ref **get_refs(char *id);
106 set_from_int_map(struct int_map *map, size_t map_size,
107 int *value, const char *name, int namelen)
112 for (i = 0; i < map_size; i++)
113 if (namelen == map[i].namelen &&
114 !strncasecmp(name, map[i].name, namelen)) {
115 *value = map[i].value;
128 string_ncopy(char *dst, const char *src, int dstlen)
130 strncpy(dst, src, dstlen - 1);
135 /* Shorthand for safely copying into a fixed buffer. */
136 #define string_copy(dst, src) \
137 string_ncopy(dst, src, sizeof(dst))
140 chomp_string(char *name)
144 while (isspace(*name))
147 namelen = strlen(name) - 1;
148 while (namelen > 0 && isspace(name[namelen]))
155 string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...)
158 int pos = bufpos ? *bufpos : 0;
161 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
167 return pos >= bufsize ? FALSE : TRUE;
170 #define string_format(buf, fmt, args...) \
171 string_nformat(buf, sizeof(buf), NULL, fmt, args)
173 #define string_format_from(buf, from, fmt, args...) \
174 string_nformat(buf, sizeof(buf), from, fmt, args)
178 * NOTE: The following is a slightly modified copy of the git project's shell
179 * quoting routines found in the quote.c file.
181 * Help to copy the thing properly quoted for the shell safety. any single
182 * quote is replaced with '\'', any exclamation point is replaced with '\!',
183 * and the whole thing is enclosed in a
186 * original sq_quote result
187 * name ==> name ==> 'name'
188 * a b ==> a b ==> 'a b'
189 * a'b ==> a'\''b ==> 'a'\''b'
190 * a!b ==> a'\!'b ==> 'a'\!'b'
194 sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
198 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
201 while ((c = *src++)) {
202 if (c == '\'' || c == '!') {
222 /* XXX: Keep the view request first and in sync with views[]. */ \
223 REQ_GROUP("View switching") \
224 REQ_(VIEW_MAIN, "Show main view"), \
225 REQ_(VIEW_DIFF, "Show diff view"), \
226 REQ_(VIEW_LOG, "Show log view"), \
227 REQ_(VIEW_HELP, "Show help page"), \
228 REQ_(VIEW_PAGER, "Show pager view"), \
230 REQ_GROUP("View manipulation") \
231 REQ_(ENTER, "Enter current line and scroll"), \
232 REQ_(NEXT, "Move to next"), \
233 REQ_(PREVIOUS, "Move to previous"), \
234 REQ_(VIEW_NEXT, "Move focus to next view"), \
235 REQ_(VIEW_CLOSE, "Close the current view"), \
236 REQ_(QUIT, "Close all views and quit"), \
238 REQ_GROUP("Cursor navigation") \
239 REQ_(MOVE_UP, "Move cursor one line up"), \
240 REQ_(MOVE_DOWN, "Move cursor one line down"), \
241 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
242 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
243 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
244 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
246 REQ_GROUP("Scrolling") \
247 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
248 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
249 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
250 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
253 REQ_(PROMPT, "Bring up the prompt"), \
254 REQ_(SCREEN_UPDATE, "Update the screen"), \
255 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
256 REQ_(SCREEN_RESIZE, "Resize the screen"), \
257 REQ_(SHOW_VERSION, "Show version information"), \
258 REQ_(STOP_LOADING, "Stop all loading views"), \
259 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
260 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"),
263 /* User action requests. */
265 #define REQ_GROUP(help)
266 #define REQ_(req, help) REQ_##req
268 /* Offset all requests to avoid conflicts with ncurses getch values. */
269 REQ_OFFSET = KEY_MAX + 1,
276 struct request_info {
277 enum request request;
281 static struct request_info req_info[] = {
282 #define REQ_GROUP(help) { 0, (help) },
283 #define REQ_(req, help) { REQ_##req, (help) }
293 static const char usage[] =
294 VERSION " (" __DATE__ ")\n"
296 "Usage: tig [options]\n"
297 " or: tig [options] [--] [git log options]\n"
298 " or: tig [options] log [git log options]\n"
299 " or: tig [options] diff [git diff options]\n"
300 " or: tig [options] show [git show options]\n"
301 " or: tig [options] < [git command output]\n"
304 " -l Start up in log view\n"
305 " -d Start up in diff view\n"
306 " -n[I], --line-number[=I] Show line numbers with given interval\n"
307 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
308 " -- Mark end of tig options\n"
309 " -v, --version Show version and exit\n"
310 " -h, --help Show help message and exit\n";
312 /* Option and state variables. */
313 static bool opt_line_number = FALSE;
314 static bool opt_rev_graph = TRUE;
315 static int opt_num_interval = NUMBER_INTERVAL;
316 static int opt_tab_size = TABSIZE;
317 static enum request opt_request = REQ_VIEW_MAIN;
318 static char opt_cmd[SIZEOF_CMD] = "";
319 static char opt_encoding[20] = "";
320 static bool opt_utf8 = TRUE;
321 static FILE *opt_pipe = NULL;
329 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
339 int namelen = strlen(name);
343 if (strncmp(opt, name, namelen))
346 if (opt[namelen] == '=')
347 value = opt + namelen + 1;
350 if (!short_name || opt[1] != short_name)
355 va_start(args, type);
356 if (type == OPT_INT) {
357 number = va_arg(args, int *);
359 *number = atoi(value);
366 /* Returns the index of log or diff command or -1 to exit. */
368 parse_options(int argc, char *argv[])
372 for (i = 1; i < argc; i++) {
375 if (!strcmp(opt, "-l")) {
376 opt_request = REQ_VIEW_LOG;
380 if (!strcmp(opt, "-d")) {
381 opt_request = REQ_VIEW_DIFF;
385 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
386 opt_line_number = TRUE;
390 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
391 opt_tab_size = MIN(opt_tab_size, TABSIZE);
395 if (check_option(opt, 'v', "version", OPT_NONE)) {
396 printf("tig version %s\n", VERSION);
400 if (check_option(opt, 'h', "help", OPT_NONE)) {
405 if (!strcmp(opt, "--")) {
410 if (!strcmp(opt, "log") ||
411 !strcmp(opt, "diff") ||
412 !strcmp(opt, "show")) {
413 opt_request = opt[0] == 'l'
414 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
418 if (opt[0] && opt[0] != '-')
421 die("unknown command '%s'", opt);
424 if (!isatty(STDIN_FILENO)) {
425 opt_request = REQ_VIEW_PAGER;
428 } else if (i < argc) {
431 if (opt_request == REQ_VIEW_MAIN)
432 /* XXX: This is vulnerable to the user overriding
433 * options required for the main view parser. */
434 string_copy(opt_cmd, "git log --stat --pretty=raw");
436 string_copy(opt_cmd, "git");
437 buf_size = strlen(opt_cmd);
439 while (buf_size < sizeof(opt_cmd) && i < argc) {
440 opt_cmd[buf_size++] = ' ';
441 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
444 if (buf_size >= sizeof(opt_cmd))
445 die("command too long");
447 opt_cmd[buf_size] = 0;
451 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
459 * Line-oriented content detection.
463 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
464 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
465 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
466 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
467 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
468 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
469 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
470 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
471 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
472 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
473 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
474 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
475 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
476 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
477 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
478 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
479 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
480 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
481 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
482 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
483 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
484 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
485 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
486 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
487 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
488 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
489 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
490 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
491 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
492 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
493 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
494 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
495 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
496 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
497 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
498 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
499 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
500 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
503 #define LINE(type, line, fg, bg, attr) \
510 const char *name; /* Option name. */
511 int namelen; /* Size of option name. */
512 const char *line; /* The start of line to match. */
513 int linelen; /* Size of string to match. */
514 int fg, bg, attr; /* Color and text attributes for the lines. */
517 static struct line_info line_info[] = {
518 #define LINE(type, line, fg, bg, attr) \
519 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
524 static enum line_type
525 get_line_type(char *line)
527 int linelen = strlen(line);
530 for (type = 0; type < ARRAY_SIZE(line_info); type++)
531 /* Case insensitive search matches Signed-off-by lines better. */
532 if (linelen >= line_info[type].linelen &&
533 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
540 get_line_attr(enum line_type type)
542 assert(type < ARRAY_SIZE(line_info));
543 return COLOR_PAIR(type) | line_info[type].attr;
546 static struct line_info *
547 get_line_info(char *name, int namelen)
552 /* Diff-Header -> DIFF_HEADER */
553 for (i = 0; i < namelen; i++) {
556 else if (name[i] == '.')
560 for (type = 0; type < ARRAY_SIZE(line_info); type++)
561 if (namelen == line_info[type].namelen &&
562 !strncasecmp(line_info[type].name, name, namelen))
563 return &line_info[type];
571 int default_bg = COLOR_BLACK;
572 int default_fg = COLOR_WHITE;
577 if (use_default_colors() != ERR) {
582 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
583 struct line_info *info = &line_info[type];
584 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
585 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
587 init_pair(type, fg, bg);
593 void *data; /* User data */
598 * User config file handling.
601 static struct int_map color_map[] = {
602 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
614 #define set_color(color, name, namelen) \
615 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, namelen)
617 static struct int_map attr_map[] = {
618 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
628 #define set_attribute(attr, name, namelen) \
629 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, namelen)
631 static int config_lineno;
632 static bool config_errors;
633 static char *config_msg;
636 set_option(char *opt, int optlen, char *value, int valuelen)
638 /* Reads: "color" object fgcolor bgcolor [attr] */
639 if (!strcmp(opt, "color")) {
640 struct line_info *info;
642 value = chomp_string(value);
643 valuelen = strcspn(value, " \t");
644 info = get_line_info(value, valuelen);
646 config_msg = "Unknown color name";
650 value = chomp_string(value + valuelen);
651 valuelen = strcspn(value, " \t");
652 if (set_color(&info->fg, value, valuelen) == ERR) {
653 config_msg = "Unknown color";
657 value = chomp_string(value + valuelen);
658 valuelen = strcspn(value, " \t");
659 if (set_color(&info->bg, value, valuelen) == ERR) {
660 config_msg = "Unknown color";
664 value = chomp_string(value + valuelen);
666 set_attribute(&info->attr, value, strlen(value)) == ERR) {
667 config_msg = "Unknown attribute";
678 read_option(char *opt, int optlen, char *value, int valuelen)
681 config_msg = "Internal error";
683 optlen = strcspn(opt, "#;");
685 /* The whole line is a commend or empty. */
688 } else if (opt[optlen] != 0) {
689 /* Part of the option name is a comment, so the value part
690 * should be ignored. */
692 opt[optlen] = value[valuelen] = 0;
694 /* Else look for comment endings in the value. */
695 valuelen = strcspn(value, "#;");
699 if (set_option(opt, optlen, value, valuelen) == ERR) {
700 fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
701 config_lineno, optlen, opt, config_msg);
702 config_errors = TRUE;
705 /* Always keep going if errors are encountered. */
712 char *home = getenv("HOME");
717 config_errors = FALSE;
719 if (!home || !string_format(buf, "%s/.tigrc", home))
722 /* It's ok that the file doesn't exist. */
723 file = fopen(buf, "r");
727 if (read_properties(file, " \t", read_option) == ERR ||
728 config_errors == TRUE)
729 fprintf(stderr, "Errors while loading %s.\n", buf);
742 /* The display array of active views and the index of the current view. */
743 static struct view *display[2];
744 static unsigned int current_view;
746 #define foreach_view(view, i) \
747 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
749 #define displayed_views() (display[1] != NULL ? 2 : 1)
751 /* Current head and commit ID */
752 static char ref_commit[SIZEOF_REF] = "HEAD";
753 static char ref_head[SIZEOF_REF] = "HEAD";
756 const char *name; /* View name */
757 const char *cmd_fmt; /* Default command line format */
758 const char *cmd_env; /* Command line set via environment */
759 const char *id; /* Points to either of ref_{head,commit} */
761 struct view_ops *ops; /* View operations */
763 char cmd[SIZEOF_CMD]; /* Command buffer */
764 char ref[SIZEOF_REF]; /* Hovered commit reference */
765 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
767 int height, width; /* The width and height of the main window */
768 WINDOW *win; /* The main window */
769 WINDOW *title; /* The title window living below the main window */
772 unsigned long offset; /* Offset of the window top */
773 unsigned long lineno; /* Current line number */
775 /* If non-NULL, points to the view that opened this view. If this view
776 * is closed tig will switch back to the parent view. */
780 unsigned long lines; /* Total number of lines */
781 struct line *line; /* Line index */
782 unsigned long line_size;/* Total number of allocated lines */
783 unsigned int digits; /* Number of digits in the lines member. */
791 /* What type of content being displayed. Used in the title bar. */
793 /* Draw one line; @lineno must be < view->height. */
794 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
795 /* Read one line; updates view->line. */
796 bool (*read)(struct view *view, char *data);
797 /* Depending on view, change display based on current line. */
798 bool (*enter)(struct view *view, struct line *line);
801 static struct view_ops pager_ops;
802 static struct view_ops main_ops;
804 #define VIEW_STR(name, cmd, env, ref, ops) \
805 { name, cmd, #env, ref, ops }
807 #define VIEW_(id, name, ops, ref) \
808 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops)
811 static struct view views[] = {
812 VIEW_(MAIN, "main", &main_ops, ref_head),
813 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
814 VIEW_(LOG, "log", &pager_ops, ref_head),
815 VIEW_(HELP, "help", &pager_ops, "static"),
816 VIEW_(PAGER, "pager", &pager_ops, "static"),
819 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
823 draw_view_line(struct view *view, unsigned int lineno)
825 if (view->offset + lineno >= view->lines)
828 return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
832 redraw_view_from(struct view *view, int lineno)
834 assert(0 <= lineno && lineno < view->height);
836 for (; lineno < view->height; lineno++) {
837 if (!draw_view_line(view, lineno))
841 redrawwin(view->win);
846 redraw_view(struct view *view)
849 redraw_view_from(view, 0);
854 update_view_title(struct view *view)
856 if (view == display[current_view])
857 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
859 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
862 wmove(view->title, 0, 0);
865 wprintw(view->title, "[%s] %s", view->name, view->ref);
867 wprintw(view->title, "[%s]", view->name);
869 if (view->lines || view->pipe) {
870 unsigned int lines = view->lines
871 ? (view->lineno + 1) * 100 / view->lines
874 wprintw(view->title, " - %s %d of %d (%d%%)",
882 time_t secs = time(NULL) - view->start_time;
884 /* Three git seconds are a long time ... */
886 wprintw(view->title, " %lds", secs);
889 wmove(view->title, 0, view->width - 1);
890 wrefresh(view->title);
897 struct view *base = display[0];
898 struct view *view = display[1] ? display[1] : display[0];
900 /* Setup window dimensions */
902 getmaxyx(stdscr, base->height, base->width);
904 /* Make room for the status window. */
908 /* Horizontal split. */
909 view->width = base->width;
910 view->height = SCALE_SPLIT_VIEW(base->height);
911 base->height -= view->height;
913 /* Make room for the title bar. */
917 /* Make room for the title bar. */
922 foreach_view (view, i) {
924 view->win = newwin(view->height, 0, offset, 0);
926 die("Failed to create %s view", view->name);
928 scrollok(view->win, TRUE);
930 view->title = newwin(1, 0, offset + view->height, 0);
932 die("Failed to create title window");
935 wresize(view->win, view->height, view->width);
936 mvwin(view->win, offset, 0);
937 mvwin(view->title, offset + view->height, 0);
940 offset += view->height + 1;
950 foreach_view (view, i) {
952 update_view_title(view);
957 update_display_cursor(void)
959 struct view *view = display[current_view];
961 /* Move the cursor to the right-most column of the cursor line.
963 * XXX: This could turn out to be a bit expensive, but it ensures that
964 * the cursor does not jump around. */
966 wmove(view->win, view->lineno - view->offset, view->width - 1);
975 /* Scrolling backend */
977 do_scroll_view(struct view *view, int lines, bool redraw)
979 /* The rendering expects the new offset. */
980 view->offset += lines;
982 assert(0 <= view->offset && view->offset < view->lines);
985 /* Redraw the whole screen if scrolling is pointless. */
986 if (view->height < ABS(lines)) {
990 int line = lines > 0 ? view->height - lines : 0;
991 int end = line + ABS(lines);
993 wscrl(view->win, lines);
995 for (; line < end; line++) {
996 if (!draw_view_line(view, line))
1001 /* Move current line into the view. */
1002 if (view->lineno < view->offset) {
1003 view->lineno = view->offset;
1004 draw_view_line(view, 0);
1006 } else if (view->lineno >= view->offset + view->height) {
1007 if (view->lineno == view->offset + view->height) {
1008 /* Clear the hidden line so it doesn't show if the view
1009 * is scrolled up. */
1010 wmove(view->win, view->height, 0);
1011 wclrtoeol(view->win);
1013 view->lineno = view->offset + view->height - 1;
1014 draw_view_line(view, view->lineno - view->offset);
1017 assert(view->offset <= view->lineno && view->lineno < view->lines);
1022 redrawwin(view->win);
1023 wrefresh(view->win);
1027 /* Scroll frontend */
1029 scroll_view(struct view *view, enum request request)
1034 case REQ_SCROLL_PAGE_DOWN:
1035 lines = view->height;
1036 case REQ_SCROLL_LINE_DOWN:
1037 if (view->offset + lines > view->lines)
1038 lines = view->lines - view->offset;
1040 if (lines == 0 || view->offset + view->height >= view->lines) {
1041 report("Cannot scroll beyond the last line");
1046 case REQ_SCROLL_PAGE_UP:
1047 lines = view->height;
1048 case REQ_SCROLL_LINE_UP:
1049 if (lines > view->offset)
1050 lines = view->offset;
1053 report("Cannot scroll beyond the first line");
1061 die("request %d not handled in switch", request);
1064 do_scroll_view(view, lines, TRUE);
1069 move_view(struct view *view, enum request request, bool redraw)
1074 case REQ_MOVE_FIRST_LINE:
1075 steps = -view->lineno;
1078 case REQ_MOVE_LAST_LINE:
1079 steps = view->lines - view->lineno - 1;
1082 case REQ_MOVE_PAGE_UP:
1083 steps = view->height > view->lineno
1084 ? -view->lineno : -view->height;
1087 case REQ_MOVE_PAGE_DOWN:
1088 steps = view->lineno + view->height >= view->lines
1089 ? view->lines - view->lineno - 1 : view->height;
1101 die("request %d not handled in switch", request);
1104 if (steps <= 0 && view->lineno == 0) {
1105 report("Cannot move beyond the first line");
1108 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1109 report("Cannot move beyond the last line");
1113 /* Move the current line */
1114 view->lineno += steps;
1115 assert(0 <= view->lineno && view->lineno < view->lines);
1117 /* Repaint the old "current" line if we be scrolling */
1118 if (ABS(steps) < view->height) {
1119 int prev_lineno = view->lineno - steps - view->offset;
1121 wmove(view->win, prev_lineno, 0);
1122 wclrtoeol(view->win);
1123 draw_view_line(view, prev_lineno);
1126 /* Check whether the view needs to be scrolled */
1127 if (view->lineno < view->offset ||
1128 view->lineno >= view->offset + view->height) {
1129 if (steps < 0 && -steps > view->offset) {
1130 steps = -view->offset;
1132 } else if (steps > 0) {
1133 if (view->lineno == view->lines - 1 &&
1134 view->lines > view->height) {
1135 steps = view->lines - view->offset - 1;
1136 if (steps >= view->height)
1137 steps -= view->height - 1;
1141 do_scroll_view(view, steps, redraw);
1145 /* Draw the current line */
1146 draw_view_line(view, view->lineno - view->offset);
1151 redrawwin(view->win);
1152 wrefresh(view->win);
1158 * Incremental updating
1162 end_update(struct view *view)
1166 set_nonblocking_input(FALSE);
1167 if (view->pipe == stdin)
1175 begin_update(struct view *view)
1177 const char *id = view->id;
1183 string_copy(view->cmd, opt_cmd);
1185 /* When running random commands, the view ref could have become
1186 * invalid so clear it. */
1189 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1191 if (!string_format(view->cmd, format, id, id, id, id, id))
1195 /* Special case for the pager view. */
1197 view->pipe = opt_pipe;
1200 view->pipe = popen(view->cmd, "r");
1206 set_nonblocking_input(TRUE);
1211 string_copy(view->vid, id);
1216 for (i = 0; i < view->lines; i++)
1217 if (view->line[i].data)
1218 free(view->line[i].data);
1224 view->start_time = time(NULL);
1229 static struct line *
1230 realloc_lines(struct view *view, size_t line_size)
1232 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1238 view->line_size = line_size;
1243 update_view(struct view *view)
1245 char buffer[BUFSIZ];
1247 /* The number of lines to read. If too low it will cause too much
1248 * redrawing (and possible flickering), if too high responsiveness
1250 unsigned long lines = view->height;
1251 int redraw_from = -1;
1256 /* Only redraw if lines are visible. */
1257 if (view->offset + view->height >= view->lines)
1258 redraw_from = view->lines - view->offset;
1260 if (!realloc_lines(view, view->lines + lines))
1263 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1264 int linelen = strlen(line);
1267 line[linelen - 1] = 0;
1269 if (!view->ops->read(view, line))
1279 lines = view->lines;
1280 for (digits = 0; lines; digits++)
1283 /* Keep the displayed view in sync with line number scaling. */
1284 if (digits != view->digits) {
1285 view->digits = digits;
1290 if (redraw_from >= 0) {
1291 /* If this is an incremental update, redraw the previous line
1292 * since for commits some members could have changed when
1293 * loading the main view. */
1294 if (redraw_from > 0)
1297 /* Incrementally draw avoids flickering. */
1298 redraw_view_from(view, redraw_from);
1301 /* Update the title _after_ the redraw so that if the redraw picks up a
1302 * commit reference in view->ref it'll be available here. */
1303 update_view_title(view);
1305 if (ferror(view->pipe)) {
1306 report("Failed to read: %s", strerror(errno));
1309 } else if (feof(view->pipe)) {
1317 report("Allocation failure");
1325 OPEN_DEFAULT = 0, /* Use default view switching. */
1326 OPEN_SPLIT = 1, /* Split current view. */
1327 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1328 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1332 open_view(struct view *prev, enum request request, enum open_flags flags)
1334 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1335 bool split = !!(flags & OPEN_SPLIT);
1336 bool reload = !!(flags & OPEN_RELOAD);
1337 struct view *view = VIEW(request);
1338 int nviews = displayed_views();
1339 struct view *base_view = display[0];
1341 if (view == prev && nviews == 1 && !reload) {
1342 report("Already in %s view", view->name);
1346 if (view == VIEW(REQ_VIEW_HELP)) {
1349 } else if ((reload || strcmp(view->vid, view->id)) &&
1350 !begin_update(view)) {
1351 report("Failed to load %s view", view->name);
1360 /* Maximize the current view. */
1361 memset(display, 0, sizeof(display));
1363 display[current_view] = view;
1366 /* Resize the view when switching between split- and full-screen,
1367 * or when switching between two different full-screen views. */
1368 if (nviews != displayed_views() ||
1369 (nviews == 1 && base_view != display[0]))
1372 if (split && prev->lineno - prev->offset >= prev->height) {
1373 /* Take the title line into account. */
1374 int lines = prev->lineno - prev->offset - prev->height + 1;
1376 /* Scroll the view that was split if the current line is
1377 * outside the new limited view. */
1378 do_scroll_view(prev, lines, TRUE);
1381 if (prev && view != prev) {
1382 if (split && !backgrounded) {
1383 /* "Blur" the previous view. */
1384 update_view_title(prev);
1387 view->parent = prev;
1390 if (view->pipe && view->lines == 0) {
1391 /* Clear the old view and let the incremental updating refill
1400 /* If the view is backgrounded the above calls to report()
1401 * won't redraw the view title. */
1403 update_view_title(view);
1408 * User request switch noodle
1412 view_driver(struct view *view, enum request request)
1419 case REQ_MOVE_PAGE_UP:
1420 case REQ_MOVE_PAGE_DOWN:
1421 case REQ_MOVE_FIRST_LINE:
1422 case REQ_MOVE_LAST_LINE:
1423 move_view(view, request, TRUE);
1426 case REQ_SCROLL_LINE_DOWN:
1427 case REQ_SCROLL_LINE_UP:
1428 case REQ_SCROLL_PAGE_DOWN:
1429 case REQ_SCROLL_PAGE_UP:
1430 scroll_view(view, request);
1437 case REQ_VIEW_PAGER:
1438 open_view(view, request, OPEN_DEFAULT);
1443 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1445 if (view == VIEW(REQ_VIEW_DIFF) &&
1446 view->parent == VIEW(REQ_VIEW_MAIN)) {
1447 bool redraw = display[1] == view;
1449 view = view->parent;
1450 move_view(view, request, redraw);
1452 update_view_title(view);
1454 move_view(view, request, TRUE);
1461 report("Nothing to enter");
1464 return view->ops->enter(view, &view->line[view->lineno]);
1468 int nviews = displayed_views();
1469 int next_view = (current_view + 1) % nviews;
1471 if (next_view == current_view) {
1472 report("Only one view is displayed");
1476 current_view = next_view;
1477 /* Blur out the title of the previous view. */
1478 update_view_title(view);
1482 case REQ_TOGGLE_LINENO:
1483 opt_line_number = !opt_line_number;
1487 case REQ_TOGGLE_REV_GRAPH:
1488 opt_rev_graph = !opt_rev_graph;
1493 /* Always reload^Wrerun commands from the prompt. */
1494 open_view(view, opt_request, OPEN_RELOAD);
1497 case REQ_STOP_LOADING:
1498 for (i = 0; i < ARRAY_SIZE(views); i++) {
1501 report("Stopped loading the %s view", view->name),
1506 case REQ_SHOW_VERSION:
1507 report("%s (built %s)", VERSION, __DATE__);
1510 case REQ_SCREEN_RESIZE:
1513 case REQ_SCREEN_REDRAW:
1517 case REQ_SCREEN_UPDATE:
1521 case REQ_VIEW_CLOSE:
1522 /* XXX: Mark closed views by letting view->parent point to the
1523 * view itself. Parents to closed view should never be
1526 view->parent->parent != view->parent) {
1527 memset(display, 0, sizeof(display));
1529 display[current_view] = view->parent;
1530 view->parent = view;
1540 /* An unknown key will show most commonly used commands. */
1541 report("Unknown key, press 'h' for help");
1554 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1556 char *text = line->data;
1557 enum line_type type = line->type;
1558 int textlen = strlen(text);
1561 wmove(view->win, lineno, 0);
1563 if (view->offset + lineno == view->lineno) {
1564 if (type == LINE_COMMIT) {
1565 string_copy(view->ref, text + 7);
1566 string_copy(ref_commit, view->ref);
1570 wchgat(view->win, -1, 0, type, NULL);
1573 attr = get_line_attr(type);
1574 wattrset(view->win, attr);
1576 if (opt_line_number || opt_tab_size < TABSIZE) {
1577 static char spaces[] = " ";
1578 int col_offset = 0, col = 0;
1580 if (opt_line_number) {
1581 unsigned long real_lineno = view->offset + lineno + 1;
1583 if (real_lineno == 1 ||
1584 (real_lineno % opt_num_interval) == 0) {
1585 wprintw(view->win, "%.*d", view->digits, real_lineno);
1588 waddnstr(view->win, spaces,
1589 MIN(view->digits, STRING_SIZE(spaces)));
1591 waddstr(view->win, ": ");
1592 col_offset = view->digits + 2;
1595 while (text && col_offset + col < view->width) {
1596 int cols_max = view->width - col_offset - col;
1600 if (*text == '\t') {
1602 assert(sizeof(spaces) > TABSIZE);
1604 cols = opt_tab_size - (col % opt_tab_size);
1607 text = strchr(text, '\t');
1608 cols = line ? text - pos : strlen(pos);
1611 waddnstr(view->win, pos, MIN(cols, cols_max));
1616 int col = 0, pos = 0;
1618 for (; pos < textlen && col < view->width; pos++, col++)
1619 if (text[pos] == '\t')
1620 col += TABSIZE - (col % TABSIZE) - 1;
1622 waddnstr(view->win, text, pos);
1629 add_pager_refs(struct view *view, struct line *line)
1632 char *data = line->data;
1634 int bufpos = 0, refpos = 0;
1635 const char *sep = "Refs: ";
1637 assert(line->type == LINE_COMMIT);
1639 refs = get_refs(data + STRING_SIZE("commit "));
1644 struct ref *ref = refs[refpos];
1645 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
1647 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
1650 } while (refs[refpos++]->next);
1652 if (!realloc_lines(view, view->line_size + 1))
1655 line = &view->line[view->lines];
1656 line->data = strdup(buf);
1660 line->type = LINE_PP_REFS;
1665 pager_read(struct view *view, char *data)
1667 struct line *line = &view->line[view->lines];
1669 line->data = strdup(data);
1673 line->type = get_line_type(line->data);
1676 if (line->type == LINE_COMMIT &&
1677 (view == VIEW(REQ_VIEW_DIFF) ||
1678 view == VIEW(REQ_VIEW_LOG)))
1679 add_pager_refs(view, line);
1685 pager_enter(struct view *view, struct line *line)
1689 if (line->type == LINE_COMMIT &&
1690 (view == VIEW(REQ_VIEW_LOG) ||
1691 view == VIEW(REQ_VIEW_PAGER))) {
1692 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1696 /* Always scroll the view even if it was split. That way
1697 * you can use Enter to scroll through the log view and
1698 * split open each commit diff. */
1699 scroll_view(view, REQ_SCROLL_LINE_DOWN);
1701 /* FIXME: A minor workaround. Scrolling the view will call report("")
1702 * but if we are scrolling a non-current view this won't properly
1703 * update the view title. */
1705 update_view_title(view);
1710 static struct view_ops pager_ops = {
1723 char id[41]; /* SHA1 ID. */
1724 char title[75]; /* First line of the commit message. */
1725 char author[75]; /* Author of the commit. */
1726 struct tm time; /* Date from the author ident. */
1727 struct ref **refs; /* Repository references. */
1728 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
1729 size_t graph_size; /* The width of the graph array. */
1733 main_draw(struct view *view, struct line *line, unsigned int lineno)
1735 char buf[DATE_COLS + 1];
1736 struct commit *commit = line->data;
1737 enum line_type type;
1743 if (!*commit->author)
1746 wmove(view->win, lineno, col);
1748 if (view->offset + lineno == view->lineno) {
1749 string_copy(view->ref, commit->id);
1750 string_copy(ref_commit, view->ref);
1752 wattrset(view->win, get_line_attr(type));
1753 wchgat(view->win, -1, 0, type, NULL);
1756 type = LINE_MAIN_COMMIT;
1757 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1760 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1761 waddnstr(view->win, buf, timelen);
1762 waddstr(view->win, " ");
1765 wmove(view->win, lineno, col);
1766 if (type != LINE_CURSOR)
1767 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1770 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1772 authorlen = strlen(commit->author);
1773 if (authorlen > AUTHOR_COLS - 2) {
1774 authorlen = AUTHOR_COLS - 2;
1780 waddnstr(view->win, commit->author, authorlen);
1781 if (type != LINE_CURSOR)
1782 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1783 waddch(view->win, '~');
1785 waddstr(view->win, commit->author);
1789 if (type != LINE_CURSOR)
1790 wattrset(view->win, A_NORMAL);
1792 if (opt_rev_graph && commit->graph_size) {
1795 wmove(view->win, lineno, col);
1796 /* Using waddch() instead of waddnstr() ensures that
1797 * they'll be rendered correctly for the cursor line. */
1798 for (i = 0; i < commit->graph_size; i++)
1799 waddch(view->win, commit->graph[i]);
1801 col += commit->graph_size + 1;
1804 wmove(view->win, lineno, col);
1810 if (type == LINE_CURSOR)
1812 else if (commit->refs[i]->tag)
1813 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1815 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1816 waddstr(view->win, "[");
1817 waddstr(view->win, commit->refs[i]->name);
1818 waddstr(view->win, "]");
1819 if (type != LINE_CURSOR)
1820 wattrset(view->win, A_NORMAL);
1821 waddstr(view->win, " ");
1822 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1823 } while (commit->refs[i++]->next);
1826 if (type != LINE_CURSOR)
1827 wattrset(view->win, get_line_attr(type));
1830 int titlelen = strlen(commit->title);
1832 if (col + titlelen > view->width)
1833 titlelen = view->width - col;
1835 waddnstr(view->win, commit->title, titlelen);
1841 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1843 main_read(struct view *view, char *line)
1845 enum line_type type = get_line_type(line);
1846 struct commit *commit = view->lines
1847 ? view->line[view->lines - 1].data : NULL;
1851 commit = calloc(1, sizeof(struct commit));
1855 line += STRING_SIZE("commit ");
1857 view->line[view->lines++].data = commit;
1858 string_copy(commit->id, line);
1859 commit->refs = get_refs(commit->id);
1860 commit->graph[commit->graph_size++] = ACS_LTEE;
1865 char *ident = line + STRING_SIZE("author ");
1866 char *end = strchr(ident, '<');
1872 for (; end > ident && isspace(end[-1]); end--) ;
1876 string_copy(commit->author, ident);
1878 /* Parse epoch and timezone */
1880 char *secs = strchr(end + 1, '>');
1884 if (!secs || secs[1] != ' ')
1888 time = (time_t) atol(secs);
1889 zone = strchr(secs, ' ');
1890 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1894 tz = ('0' - zone[1]) * 60 * 60 * 10;
1895 tz += ('0' - zone[2]) * 60 * 60;
1896 tz += ('0' - zone[3]) * 60;
1897 tz += ('0' - zone[4]) * 60;
1904 gmtime_r(&time, &commit->time);
1912 /* Fill in the commit title if it has not already been set. */
1913 if (commit->title[0])
1916 /* Require titles to start with a non-space character at the
1917 * offset used by git log. */
1918 /* FIXME: More gracefull handling of titles; append "..." to
1919 * shortened titles, etc. */
1920 if (strncmp(line, " ", 4) ||
1924 string_copy(commit->title, line + 4);
1931 main_enter(struct view *view, struct line *line)
1933 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
1935 open_view(view, REQ_VIEW_DIFF, flags);
1939 static struct view_ops main_ops = {
1956 static struct keymap keymap[] = {
1957 /* View switching */
1958 { 'm', REQ_VIEW_MAIN },
1959 { 'd', REQ_VIEW_DIFF },
1960 { 'l', REQ_VIEW_LOG },
1961 { 'p', REQ_VIEW_PAGER },
1962 { 'h', REQ_VIEW_HELP },
1963 { '?', REQ_VIEW_HELP },
1965 /* View manipulation */
1966 { 'q', REQ_VIEW_CLOSE },
1967 { KEY_TAB, REQ_VIEW_NEXT },
1968 { KEY_RETURN, REQ_ENTER },
1969 { KEY_UP, REQ_PREVIOUS },
1970 { KEY_DOWN, REQ_NEXT },
1972 /* Cursor navigation */
1973 { 'k', REQ_MOVE_UP },
1974 { 'j', REQ_MOVE_DOWN },
1975 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1976 { KEY_END, REQ_MOVE_LAST_LINE },
1977 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1978 { ' ', REQ_MOVE_PAGE_DOWN },
1979 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1980 { 'b', REQ_MOVE_PAGE_UP },
1981 { '-', REQ_MOVE_PAGE_UP },
1984 { KEY_IC, REQ_SCROLL_LINE_UP },
1985 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1986 { 'w', REQ_SCROLL_PAGE_UP },
1987 { 's', REQ_SCROLL_PAGE_DOWN },
1991 { 'z', REQ_STOP_LOADING },
1992 { 'v', REQ_SHOW_VERSION },
1993 { 'r', REQ_SCREEN_REDRAW },
1994 { 'n', REQ_TOGGLE_LINENO },
1995 { 'g', REQ_TOGGLE_REV_GRAPH},
1996 { ':', REQ_PROMPT },
1998 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
1999 { ERR, REQ_SCREEN_UPDATE },
2001 /* Use the ncurses SIGWINCH handler. */
2002 { KEY_RESIZE, REQ_SCREEN_RESIZE },
2006 get_request(int key)
2010 for (i = 0; i < ARRAY_SIZE(keymap); i++)
2011 if (keymap[i].alias == key)
2012 return keymap[i].request;
2014 return (enum request) key;
2022 static struct key key_table[] = {
2023 { "Enter", KEY_RETURN },
2025 { "Backspace", KEY_BACKSPACE },
2027 { "Escape", KEY_ESC },
2028 { "Left", KEY_LEFT },
2029 { "Right", KEY_RIGHT },
2031 { "Down", KEY_DOWN },
2032 { "Insert", KEY_IC },
2033 { "Delete", KEY_DC },
2034 { "Home", KEY_HOME },
2036 { "PageUp", KEY_PPAGE },
2037 { "PageDown", KEY_NPAGE },
2047 { "F10", KEY_F(10) },
2048 { "F11", KEY_F(11) },
2049 { "F12", KEY_F(12) },
2053 get_key(enum request request)
2055 static char buf[BUFSIZ];
2056 static char key_char[] = "'X'";
2063 for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2067 if (keymap[i].request != request)
2070 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2071 if (key_table[key].value == keymap[i].alias)
2072 seq = key_table[key].name;
2075 keymap[i].alias < 127 &&
2076 isprint(keymap[i].alias)) {
2077 key_char[1] = (char) keymap[i].alias;
2084 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
2085 return "Too many keybindings!";
2092 static void load_help_page(void)
2095 struct view *view = VIEW(REQ_VIEW_HELP);
2096 int lines = ARRAY_SIZE(req_info) + 2;
2099 if (view->lines > 0)
2102 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2103 if (!req_info[i].request)
2106 view->line = calloc(lines, sizeof(*view->line));
2108 report("Allocation failure");
2112 pager_read(view, "Quick reference for tig keybindings:");
2114 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2117 if (!req_info[i].request) {
2118 pager_read(view, "");
2119 pager_read(view, req_info[i].help);
2123 key = get_key(req_info[i].request);
2124 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
2127 pager_read(view, buf);
2133 * Unicode / UTF-8 handling
2135 * NOTE: Much of the following code for dealing with unicode is derived from
2136 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2137 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2140 /* I've (over)annotated a lot of code snippets because I am not entirely
2141 * confident that the approach taken by this small UTF-8 interface is correct.
2145 unicode_width(unsigned long c)
2148 (c <= 0x115f /* Hangul Jamo */
2151 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
2153 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
2154 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
2155 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
2156 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
2157 || (c >= 0xffe0 && c <= 0xffe6)
2158 || (c >= 0x20000 && c <= 0x2fffd)
2159 || (c >= 0x30000 && c <= 0x3fffd)))
2165 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2166 * Illegal bytes are set one. */
2167 static const unsigned char utf8_bytes[256] = {
2168 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2169 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2170 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2171 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2172 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2173 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2174 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
2175 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
2178 /* Decode UTF-8 multi-byte representation into a unicode character. */
2179 static inline unsigned long
2180 utf8_to_unicode(const char *string, size_t length)
2182 unsigned long unicode;
2186 unicode = string[0];
2189 unicode = (string[0] & 0x1f) << 6;
2190 unicode += (string[1] & 0x3f);
2193 unicode = (string[0] & 0x0f) << 12;
2194 unicode += ((string[1] & 0x3f) << 6);
2195 unicode += (string[2] & 0x3f);
2198 unicode = (string[0] & 0x0f) << 18;
2199 unicode += ((string[1] & 0x3f) << 12);
2200 unicode += ((string[2] & 0x3f) << 6);
2201 unicode += (string[3] & 0x3f);
2204 unicode = (string[0] & 0x0f) << 24;
2205 unicode += ((string[1] & 0x3f) << 18);
2206 unicode += ((string[2] & 0x3f) << 12);
2207 unicode += ((string[3] & 0x3f) << 6);
2208 unicode += (string[4] & 0x3f);
2211 unicode = (string[0] & 0x01) << 30;
2212 unicode += ((string[1] & 0x3f) << 24);
2213 unicode += ((string[2] & 0x3f) << 18);
2214 unicode += ((string[3] & 0x3f) << 12);
2215 unicode += ((string[4] & 0x3f) << 6);
2216 unicode += (string[5] & 0x3f);
2219 die("Invalid unicode length");
2222 /* Invalid characters could return the special 0xfffd value but NUL
2223 * should be just as good. */
2224 return unicode > 0xffff ? 0 : unicode;
2227 /* Calculates how much of string can be shown within the given maximum width
2228 * and sets trimmed parameter to non-zero value if all of string could not be
2231 * Additionally, adds to coloffset how many many columns to move to align with
2232 * the expected position. Takes into account how multi-byte and double-width
2233 * characters will effect the cursor position.
2235 * Returns the number of bytes to output from string to satisfy max_width. */
2237 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2239 const char *start = string;
2240 const char *end = strchr(string, '\0');
2246 while (string < end) {
2247 int c = *(unsigned char *) string;
2248 unsigned char bytes = utf8_bytes[c];
2250 unsigned long unicode;
2252 if (string + bytes > end)
2255 /* Change representation to figure out whether
2256 * it is a single- or double-width character. */
2258 unicode = utf8_to_unicode(string, bytes);
2259 /* FIXME: Graceful handling of invalid unicode character. */
2263 ucwidth = unicode_width(unicode);
2265 if (width > max_width) {
2270 /* The column offset collects the differences between the
2271 * number of bytes encoding a character and the number of
2272 * columns will be used for rendering said character.
2274 * So if some character A is encoded in 2 bytes, but will be
2275 * represented on the screen using only 1 byte this will and up
2276 * adding 1 to the multi-byte column offset.
2278 * Assumes that no double-width character can be encoding in
2279 * less than two bytes. */
2280 if (bytes > ucwidth)
2281 mbwidth += bytes - ucwidth;
2286 *coloffset += mbwidth;
2288 return string - start;
2296 /* Whether or not the curses interface has been initialized. */
2297 static bool cursed = FALSE;
2299 /* The status window is used for polling keystrokes. */
2300 static WINDOW *status_win;
2302 /* Update status and title window. */
2304 report(const char *msg, ...)
2306 static bool empty = TRUE;
2307 struct view *view = display[current_view];
2309 if (!empty || *msg) {
2312 va_start(args, msg);
2315 wmove(status_win, 0, 0);
2317 vwprintw(status_win, msg, args);
2322 wrefresh(status_win);
2327 update_view_title(view);
2328 update_display_cursor();
2331 /* Controls when nodelay should be in effect when polling user input. */
2333 set_nonblocking_input(bool loading)
2335 static unsigned int loading_views;
2337 if ((loading == FALSE && loading_views-- == 1) ||
2338 (loading == TRUE && loading_views++ == 0))
2339 nodelay(status_win, loading);
2347 /* Initialize the curses library */
2348 if (isatty(STDIN_FILENO)) {
2349 cursed = !!initscr();
2351 /* Leave stdin and stdout alone when acting as a pager. */
2352 FILE *io = fopen("/dev/tty", "r+");
2354 cursed = !!newterm(NULL, io, io);
2358 die("Failed to initialize curses");
2360 nonl(); /* Tell curses not to do NL->CR/NL on output */
2361 cbreak(); /* Take input chars one at a time, no wait for \n */
2362 noecho(); /* Don't echo input */
2363 leaveok(stdscr, TRUE);
2368 getmaxyx(stdscr, y, x);
2369 status_win = newwin(1, 0, y - 1, 0);
2371 die("Failed to create status window");
2373 /* Enable keyboard mapping */
2374 keypad(status_win, TRUE);
2375 wbkgdset(status_win, get_line_attr(LINE_STATUS));
2380 * Repository references
2383 static struct ref *refs;
2384 static size_t refs_size;
2386 /* Id <-> ref store */
2387 static struct ref ***id_refs;
2388 static size_t id_refs_size;
2390 static struct ref **
2393 struct ref ***tmp_id_refs;
2394 struct ref **ref_list = NULL;
2395 size_t ref_list_size = 0;
2398 for (i = 0; i < id_refs_size; i++)
2399 if (!strcmp(id, id_refs[i][0]->id))
2402 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2406 id_refs = tmp_id_refs;
2408 for (i = 0; i < refs_size; i++) {
2411 if (strcmp(id, refs[i].id))
2414 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2422 if (ref_list_size > 0)
2423 ref_list[ref_list_size - 1]->next = 1;
2424 ref_list[ref_list_size] = &refs[i];
2426 /* XXX: The properties of the commit chains ensures that we can
2427 * safely modify the shared ref. The repo references will
2428 * always be similar for the same id. */
2429 ref_list[ref_list_size]->next = 0;
2434 id_refs[id_refs_size++] = ref_list;
2440 read_ref(char *id, int idlen, char *name, int namelen)
2445 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2446 /* Commits referenced by tags has "^{}" appended. */
2447 if (name[namelen - 1] != '}')
2450 while (namelen > 0 && name[namelen] != '^')
2454 namelen -= STRING_SIZE("refs/tags/");
2455 name += STRING_SIZE("refs/tags/");
2457 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2458 namelen -= STRING_SIZE("refs/heads/");
2459 name += STRING_SIZE("refs/heads/");
2461 } else if (!strcmp(name, "HEAD")) {
2465 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2469 ref = &refs[refs_size++];
2470 ref->name = malloc(namelen + 1);
2474 strncpy(ref->name, name, namelen);
2475 ref->name[namelen] = 0;
2477 string_copy(ref->id, id);
2485 const char *cmd_env = getenv("TIG_LS_REMOTE");
2486 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2488 return read_properties(popen(cmd, "r"), "\t", read_ref);
2492 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2494 if (!strcmp(name, "i18n.commitencoding"))
2495 string_copy(opt_encoding, value);
2501 load_repo_config(void)
2503 return read_properties(popen("git repo-config --list", "r"),
2504 "=", read_repo_config_option);
2508 read_properties(FILE *pipe, const char *separators,
2509 int (*read_property)(char *, int, char *, int))
2511 char buffer[BUFSIZ];
2518 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2523 name = chomp_string(name);
2524 namelen = strcspn(name, separators);
2526 if (name[namelen]) {
2528 value = chomp_string(name + namelen + 1);
2529 valuelen = strlen(value);
2536 state = read_property(name, namelen, value, valuelen);
2539 if (state != ERR && ferror(pipe))
2553 #define __NORETURN __attribute__((__noreturn__))
2558 static void __NORETURN
2561 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2567 static void __NORETURN
2568 die(const char *err, ...)
2574 va_start(args, err);
2575 fputs("tig: ", stderr);
2576 vfprintf(stderr, err, args);
2577 fputs("\n", stderr);
2584 main(int argc, char *argv[])
2587 enum request request;
2590 signal(SIGINT, quit);
2592 if (load_options() == ERR)
2593 die("Failed to load user config.");
2595 /* Load the repo config file so options can be overwritten from
2596 * the command line. */
2597 if (load_repo_config() == ERR)
2598 die("Failed to load repo config.");
2600 if (!parse_options(argc, argv))
2603 if (load_refs() == ERR)
2604 die("Failed to load refs.");
2606 /* Require a git repository unless when running in pager mode. */
2607 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2608 die("Not a git repository");
2610 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2611 view->cmd_env = getenv(view->cmd_env);
2613 request = opt_request;
2617 while (view_driver(display[current_view], request)) {
2621 foreach_view (view, i)
2624 /* Refresh, accept single keystroke of input */
2625 key = wgetch(status_win);
2626 request = get_request(key);
2628 /* Some low-level request handling. This keeps access to
2629 * status_win restricted. */
2633 /* Temporarily switch to line-oriented and echoed
2638 if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2639 memcpy(opt_cmd, "git ", 4);
2640 opt_request = REQ_VIEW_PAGER;
2642 report("Prompt interrupted by loading view, "
2643 "press 'z' to stop loading views");
2644 request = REQ_SCREEN_UPDATE;
2651 case REQ_SCREEN_RESIZE:
2655 getmaxyx(stdscr, height, width);
2657 /* Resize the status view and let the view driver take
2658 * care of resizing the displayed views. */
2659 wresize(status_win, 1, width);
2660 mvwin(status_win, height - 1, 0);
2661 wrefresh(status_win);