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.6.git"
33 #include <sys/types.h>
43 #define __NORETURN __attribute__((__noreturn__))
48 static void __NORETURN die(const char *err, ...);
49 static void report(const char *msg, ...);
50 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading);
52 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
54 #define ABS(x) ((x) >= 0 ? (x) : -(x))
55 #define MIN(x, y) ((x) < (y) ? (x) : (y))
57 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x) (sizeof(x) - 1)
60 #define SIZEOF_STR 1024 /* Default string size. */
61 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
66 #define REVGRAPH_INIT 'I'
67 #define REVGRAPH_MERGE 'M'
68 #define REVGRAPH_BRANCH '+'
69 #define REVGRAPH_COMMIT '*'
70 #define REVGRAPH_LINE '|'
72 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
74 /* This color name can be used to refer to the default term colors. */
75 #define COLOR_DEFAULT (-1)
77 #define ICONV_NONE ((iconv_t) -1)
79 /* The format and size of the date column in the main view. */
80 #define DATE_FORMAT "%Y-%m-%d %H:%M"
81 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
83 #define AUTHOR_COLS 20
85 /* The default interval between line numbers. */
86 #define NUMBER_INTERVAL 1
90 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
92 #define TIG_LS_REMOTE \
93 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
95 #define TIG_DIFF_CMD \
96 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
99 "git log --cc --stat -n100 %s 2>/dev/null"
101 #define TIG_MAIN_CMD \
102 "git log --topo-order --pretty=raw %s 2>/dev/null"
104 #define TIG_TREE_CMD \
107 #define TIG_BLOB_CMD \
108 "git cat-file blob %s"
110 /* XXX: Needs to be defined to the empty string. */
111 #define TIG_HELP_CMD ""
112 #define TIG_PAGER_CMD ""
114 /* Some ascii-shorthands fitted into the ncurses namespace. */
116 #define KEY_RETURN '\r'
121 char *name; /* Ref name; tag or head names are shortened. */
122 char id[SIZEOF_REV]; /* Commit SHA1 ID */
123 unsigned int tag:1; /* Is it a tag? */
124 unsigned int remote:1; /* Is it a remote ref? */
125 unsigned int next:1; /* For ref lists: are there more refs? */
128 static struct ref **get_refs(char *id);
137 set_from_int_map(struct int_map *map, size_t map_size,
138 int *value, const char *name, int namelen)
143 for (i = 0; i < map_size; i++)
144 if (namelen == map[i].namelen &&
145 !strncasecmp(name, map[i].name, namelen)) {
146 *value = map[i].value;
159 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
161 if (srclen > dstlen - 1)
164 strncpy(dst, src, srclen);
168 /* Shorthands for safely copying into a fixed buffer. */
170 #define string_copy(dst, src) \
171 string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
173 #define string_ncopy(dst, src, srclen) \
174 string_ncopy_do(dst, sizeof(dst), src, srclen)
176 #define string_copy_rev(dst, src) \
177 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
180 chomp_string(char *name)
184 while (isspace(*name))
187 namelen = strlen(name) - 1;
188 while (namelen > 0 && isspace(name[namelen]))
195 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
198 size_t pos = bufpos ? *bufpos : 0;
201 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
207 return pos >= bufsize ? FALSE : TRUE;
210 #define string_format(buf, fmt, args...) \
211 string_nformat(buf, sizeof(buf), NULL, fmt, args)
213 #define string_format_from(buf, from, fmt, args...) \
214 string_nformat(buf, sizeof(buf), from, fmt, args)
217 string_enum_compare(const char *str1, const char *str2, int len)
221 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
223 /* Diff-Header == DIFF_HEADER */
224 for (i = 0; i < len; i++) {
225 if (toupper(str1[i]) == toupper(str2[i]))
228 if (string_enum_sep(str1[i]) &&
229 string_enum_sep(str2[i]))
232 return str1[i] - str2[i];
240 * NOTE: The following is a slightly modified copy of the git project's shell
241 * quoting routines found in the quote.c file.
243 * Help to copy the thing properly quoted for the shell safety. any single
244 * quote is replaced with '\'', any exclamation point is replaced with '\!',
245 * and the whole thing is enclosed in a
248 * original sq_quote result
249 * name ==> name ==> 'name'
250 * a b ==> a b ==> 'a b'
251 * a'b ==> a'\''b ==> 'a'\''b'
252 * a!b ==> a'\!'b ==> 'a'\!'b'
256 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
260 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
263 while ((c = *src++)) {
264 if (c == '\'' || c == '!') {
275 if (bufsize < SIZEOF_STR)
287 /* XXX: Keep the view request first and in sync with views[]. */ \
288 REQ_GROUP("View switching") \
289 REQ_(VIEW_MAIN, "Show main view"), \
290 REQ_(VIEW_DIFF, "Show diff view"), \
291 REQ_(VIEW_LOG, "Show log view"), \
292 REQ_(VIEW_TREE, "Show tree view"), \
293 REQ_(VIEW_BLOB, "Show blob view"), \
294 REQ_(VIEW_HELP, "Show help page"), \
295 REQ_(VIEW_PAGER, "Show pager view"), \
297 REQ_GROUP("View manipulation") \
298 REQ_(ENTER, "Enter current line and scroll"), \
299 REQ_(NEXT, "Move to next"), \
300 REQ_(PREVIOUS, "Move to previous"), \
301 REQ_(VIEW_NEXT, "Move focus to next view"), \
302 REQ_(VIEW_CLOSE, "Close the current view"), \
303 REQ_(QUIT, "Close all views and quit"), \
305 REQ_GROUP("Cursor navigation") \
306 REQ_(MOVE_UP, "Move cursor one line up"), \
307 REQ_(MOVE_DOWN, "Move cursor one line down"), \
308 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
309 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
310 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
311 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
313 REQ_GROUP("Scrolling") \
314 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
315 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
316 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
317 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
319 REQ_GROUP("Searching") \
320 REQ_(SEARCH, "Search the view"), \
321 REQ_(SEARCH_BACK, "Search backwards in the view"), \
322 REQ_(FIND_NEXT, "Find next search match"), \
323 REQ_(FIND_PREV, "Find previous search match"), \
326 REQ_(NONE, "Do nothing"), \
327 REQ_(PROMPT, "Bring up the prompt"), \
328 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
329 REQ_(SCREEN_RESIZE, "Resize the screen"), \
330 REQ_(SHOW_VERSION, "Show version information"), \
331 REQ_(STOP_LOADING, "Stop all loading views"), \
332 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
333 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
336 /* User action requests. */
338 #define REQ_GROUP(help)
339 #define REQ_(req, help) REQ_##req
341 /* Offset all requests to avoid conflicts with ncurses getch values. */
342 REQ_OFFSET = KEY_MAX + 1,
350 struct request_info {
351 enum request request;
357 static struct request_info req_info[] = {
358 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
359 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
366 get_request(const char *name)
368 int namelen = strlen(name);
371 for (i = 0; i < ARRAY_SIZE(req_info); i++)
372 if (req_info[i].namelen == namelen &&
373 !string_enum_compare(req_info[i].name, name, namelen))
374 return req_info[i].request;
384 static const char usage[] =
385 VERSION " (" __DATE__ ")\n"
387 "Usage: tig [options]\n"
388 " or: tig [options] [--] [git log options]\n"
389 " or: tig [options] log [git log options]\n"
390 " or: tig [options] diff [git diff options]\n"
391 " or: tig [options] show [git show options]\n"
392 " or: tig [options] < [git command output]\n"
395 " -l Start up in log view\n"
396 " -d Start up in diff view\n"
397 " -n[I], --line-number[=I] Show line numbers with given interval\n"
398 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
399 " -- Mark end of tig options\n"
400 " -v, --version Show version and exit\n"
401 " -h, --help Show help message and exit\n";
403 /* Option and state variables. */
404 static bool opt_line_number = FALSE;
405 static bool opt_rev_graph = FALSE;
406 static int opt_num_interval = NUMBER_INTERVAL;
407 static int opt_tab_size = TABSIZE;
408 static enum request opt_request = REQ_VIEW_MAIN;
409 static char opt_cmd[SIZEOF_STR] = "";
410 static char opt_path[SIZEOF_STR] = "";
411 static FILE *opt_pipe = NULL;
412 static char opt_encoding[20] = "UTF-8";
413 static bool opt_utf8 = TRUE;
414 static char opt_codeset[20] = "UTF-8";
415 static iconv_t opt_iconv = ICONV_NONE;
416 static char opt_search[SIZEOF_STR] = "";
424 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
434 int namelen = strlen(name);
438 if (strncmp(opt, name, namelen))
441 if (opt[namelen] == '=')
442 value = opt + namelen + 1;
445 if (!short_name || opt[1] != short_name)
450 va_start(args, type);
451 if (type == OPT_INT) {
452 number = va_arg(args, int *);
454 *number = atoi(value);
461 /* Returns the index of log or diff command or -1 to exit. */
463 parse_options(int argc, char *argv[])
467 for (i = 1; i < argc; i++) {
470 if (!strcmp(opt, "log") ||
471 !strcmp(opt, "diff") ||
472 !strcmp(opt, "show")) {
473 opt_request = opt[0] == 'l'
474 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
478 if (opt[0] && opt[0] != '-')
481 if (!strcmp(opt, "-l")) {
482 opt_request = REQ_VIEW_LOG;
486 if (!strcmp(opt, "-d")) {
487 opt_request = REQ_VIEW_DIFF;
491 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
492 opt_line_number = TRUE;
496 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
497 opt_tab_size = MIN(opt_tab_size, TABSIZE);
501 if (check_option(opt, 'v', "version", OPT_NONE)) {
502 printf("tig version %s\n", VERSION);
506 if (check_option(opt, 'h', "help", OPT_NONE)) {
511 if (!strcmp(opt, "--")) {
516 die("unknown option '%s'\n\n%s", opt, usage);
519 if (!isatty(STDIN_FILENO)) {
520 opt_request = REQ_VIEW_PAGER;
523 } else if (i < argc) {
526 if (opt_request == REQ_VIEW_MAIN)
527 /* XXX: This is vulnerable to the user overriding
528 * options required for the main view parser. */
529 string_copy(opt_cmd, "git log --pretty=raw");
531 string_copy(opt_cmd, "git");
532 buf_size = strlen(opt_cmd);
534 while (buf_size < sizeof(opt_cmd) && i < argc) {
535 opt_cmd[buf_size++] = ' ';
536 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
539 if (buf_size >= sizeof(opt_cmd))
540 die("command too long");
542 opt_cmd[buf_size] = 0;
545 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
553 * Line-oriented content detection.
557 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
558 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
559 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
560 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
561 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
562 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
563 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
564 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
565 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
566 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
567 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
568 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
569 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
570 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
571 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
572 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
573 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
574 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
575 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
576 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
577 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
578 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
579 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
580 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
581 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
582 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
583 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
586 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
587 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
588 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
589 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
590 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
591 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
592 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
593 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
594 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
595 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
596 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
597 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
598 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL)
601 #define LINE(type, line, fg, bg, attr) \
608 const char *name; /* Option name. */
609 int namelen; /* Size of option name. */
610 const char *line; /* The start of line to match. */
611 int linelen; /* Size of string to match. */
612 int fg, bg, attr; /* Color and text attributes for the lines. */
615 static struct line_info line_info[] = {
616 #define LINE(type, line, fg, bg, attr) \
617 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
622 static enum line_type
623 get_line_type(char *line)
625 int linelen = strlen(line);
628 for (type = 0; type < ARRAY_SIZE(line_info); type++)
629 /* Case insensitive search matches Signed-off-by lines better. */
630 if (linelen >= line_info[type].linelen &&
631 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
638 get_line_attr(enum line_type type)
640 assert(type < ARRAY_SIZE(line_info));
641 return COLOR_PAIR(type) | line_info[type].attr;
644 static struct line_info *
645 get_line_info(char *name, int namelen)
649 for (type = 0; type < ARRAY_SIZE(line_info); type++)
650 if (namelen == line_info[type].namelen &&
651 !string_enum_compare(line_info[type].name, name, namelen))
652 return &line_info[type];
660 int default_bg = COLOR_BLACK;
661 int default_fg = COLOR_WHITE;
666 if (use_default_colors() != ERR) {
671 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
672 struct line_info *info = &line_info[type];
673 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
674 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
676 init_pair(type, fg, bg);
684 unsigned int selected:1;
686 void *data; /* User data */
696 enum request request;
697 struct keybinding *next;
700 static struct keybinding default_keybindings[] = {
702 { 'm', REQ_VIEW_MAIN },
703 { 'd', REQ_VIEW_DIFF },
704 { 'l', REQ_VIEW_LOG },
705 { 't', REQ_VIEW_TREE },
706 { 'f', REQ_VIEW_BLOB },
707 { 'p', REQ_VIEW_PAGER },
708 { 'h', REQ_VIEW_HELP },
710 /* View manipulation */
711 { 'q', REQ_VIEW_CLOSE },
712 { KEY_TAB, REQ_VIEW_NEXT },
713 { KEY_RETURN, REQ_ENTER },
714 { KEY_UP, REQ_PREVIOUS },
715 { KEY_DOWN, REQ_NEXT },
717 /* Cursor navigation */
718 { 'k', REQ_MOVE_UP },
719 { 'j', REQ_MOVE_DOWN },
720 { KEY_HOME, REQ_MOVE_FIRST_LINE },
721 { KEY_END, REQ_MOVE_LAST_LINE },
722 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
723 { ' ', REQ_MOVE_PAGE_DOWN },
724 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
725 { 'b', REQ_MOVE_PAGE_UP },
726 { '-', REQ_MOVE_PAGE_UP },
729 { KEY_IC, REQ_SCROLL_LINE_UP },
730 { KEY_DC, REQ_SCROLL_LINE_DOWN },
731 { 'w', REQ_SCROLL_PAGE_UP },
732 { 's', REQ_SCROLL_PAGE_DOWN },
736 { '?', REQ_SEARCH_BACK },
737 { 'n', REQ_FIND_NEXT },
738 { 'N', REQ_FIND_PREV },
742 { 'z', REQ_STOP_LOADING },
743 { 'v', REQ_SHOW_VERSION },
744 { 'r', REQ_SCREEN_REDRAW },
745 { '.', REQ_TOGGLE_LINENO },
746 { 'g', REQ_TOGGLE_REV_GRAPH },
749 /* Using the ncurses SIGWINCH handler. */
750 { KEY_RESIZE, REQ_SCREEN_RESIZE },
753 #define KEYMAP_INFO \
764 #define KEYMAP_(name) KEYMAP_##name
769 static struct int_map keymap_table[] = {
770 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
775 #define set_keymap(map, name) \
776 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
778 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
781 add_keybinding(enum keymap keymap, enum request request, int key)
783 struct keybinding *keybinding;
785 keybinding = calloc(1, sizeof(*keybinding));
787 die("Failed to allocate keybinding");
789 keybinding->alias = key;
790 keybinding->request = request;
791 keybinding->next = keybindings[keymap];
792 keybindings[keymap] = keybinding;
795 /* Looks for a key binding first in the given map, then in the generic map, and
796 * lastly in the default keybindings. */
798 get_keybinding(enum keymap keymap, int key)
800 struct keybinding *kbd;
803 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
804 if (kbd->alias == key)
807 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
808 if (kbd->alias == key)
811 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
812 if (default_keybindings[i].alias == key)
813 return default_keybindings[i].request;
815 return (enum request) key;
824 static struct key key_table[] = {
825 { "Enter", KEY_RETURN },
827 { "Backspace", KEY_BACKSPACE },
829 { "Escape", KEY_ESC },
830 { "Left", KEY_LEFT },
831 { "Right", KEY_RIGHT },
833 { "Down", KEY_DOWN },
834 { "Insert", KEY_IC },
835 { "Delete", KEY_DC },
837 { "Home", KEY_HOME },
839 { "PageUp", KEY_PPAGE },
840 { "PageDown", KEY_NPAGE },
850 { "F10", KEY_F(10) },
851 { "F11", KEY_F(11) },
852 { "F12", KEY_F(12) },
856 get_key_value(const char *name)
860 for (i = 0; i < ARRAY_SIZE(key_table); i++)
861 if (!strcasecmp(key_table[i].name, name))
862 return key_table[i].value;
864 if (strlen(name) == 1 && isprint(*name))
871 get_key(enum request request)
873 static char buf[BUFSIZ];
874 static char key_char[] = "'X'";
881 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
882 struct keybinding *keybinding = &default_keybindings[i];
886 if (keybinding->request != request)
889 for (key = 0; key < ARRAY_SIZE(key_table); key++)
890 if (key_table[key].value == keybinding->alias)
891 seq = key_table[key].name;
894 keybinding->alias < 127 &&
895 isprint(keybinding->alias)) {
896 key_char[1] = (char) keybinding->alias;
903 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
904 return "Too many keybindings!";
913 * User config file handling.
916 static struct int_map color_map[] = {
917 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
929 #define set_color(color, name) \
930 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
932 static struct int_map attr_map[] = {
933 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
943 #define set_attribute(attr, name) \
944 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
946 static int config_lineno;
947 static bool config_errors;
948 static char *config_msg;
950 /* Wants: object fgcolor bgcolor [attr] */
952 option_color_command(int argc, char *argv[])
954 struct line_info *info;
956 if (argc != 3 && argc != 4) {
957 config_msg = "Wrong number of arguments given to color command";
961 info = get_line_info(argv[0], strlen(argv[0]));
963 config_msg = "Unknown color name";
967 if (set_color(&info->fg, argv[1]) == ERR ||
968 set_color(&info->bg, argv[2]) == ERR) {
969 config_msg = "Unknown color";
973 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
974 config_msg = "Unknown attribute";
981 /* Wants: name = value */
983 option_set_command(int argc, char *argv[])
986 config_msg = "Wrong number of arguments given to set command";
990 if (strcmp(argv[1], "=")) {
991 config_msg = "No value assigned";
995 if (!strcmp(argv[0], "show-rev-graph")) {
996 opt_rev_graph = (!strcmp(argv[2], "1") ||
997 !strcmp(argv[2], "true") ||
998 !strcmp(argv[2], "yes"));
1002 if (!strcmp(argv[0], "line-number-interval")) {
1003 opt_num_interval = atoi(argv[2]);
1007 if (!strcmp(argv[0], "tab-size")) {
1008 opt_tab_size = atoi(argv[2]);
1012 if (!strcmp(argv[0], "commit-encoding")) {
1013 char *arg = argv[2];
1014 int delimiter = *arg;
1017 switch (delimiter) {
1020 for (arg++, i = 0; arg[i]; i++)
1021 if (arg[i] == delimiter) {
1026 string_copy(opt_encoding, arg);
1031 config_msg = "Unknown variable name";
1035 /* Wants: mode request key */
1037 option_bind_command(int argc, char *argv[])
1039 enum request request;
1044 config_msg = "Wrong number of arguments given to bind command";
1048 if (set_keymap(&keymap, argv[0]) == ERR) {
1049 config_msg = "Unknown key map";
1053 key = get_key_value(argv[1]);
1055 config_msg = "Unknown key";
1059 request = get_request(argv[2]);
1060 if (request == REQ_UNKNOWN) {
1061 config_msg = "Unknown request name";
1065 add_keybinding(keymap, request, key);
1071 set_option(char *opt, char *value)
1078 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1079 argv[argc++] = value;
1086 while (isspace(*value))
1090 if (!strcmp(opt, "color"))
1091 return option_color_command(argc, argv);
1093 if (!strcmp(opt, "set"))
1094 return option_set_command(argc, argv);
1096 if (!strcmp(opt, "bind"))
1097 return option_bind_command(argc, argv);
1099 config_msg = "Unknown option command";
1104 read_option(char *opt, int optlen, char *value, int valuelen)
1109 config_msg = "Internal error";
1111 /* Check for comment markers, since read_properties() will
1112 * only ensure opt and value are split at first " \t". */
1113 optlen = strcspn(opt, "#");
1117 if (opt[optlen] != 0) {
1118 config_msg = "No option value";
1122 /* Look for comment endings in the value. */
1123 int len = strcspn(value, "#");
1125 if (len < valuelen) {
1127 value[valuelen] = 0;
1130 status = set_option(opt, value);
1133 if (status == ERR) {
1134 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1135 config_lineno, optlen, opt, config_msg);
1136 config_errors = TRUE;
1139 /* Always keep going if errors are encountered. */
1146 char *home = getenv("HOME");
1147 char buf[SIZEOF_STR];
1151 config_errors = FALSE;
1153 if (!home || !string_format(buf, "%s/.tigrc", home))
1156 /* It's ok that the file doesn't exist. */
1157 file = fopen(buf, "r");
1161 if (read_properties(file, " \t", read_option) == ERR ||
1162 config_errors == TRUE)
1163 fprintf(stderr, "Errors while loading %s.\n", buf);
1176 /* The display array of active views and the index of the current view. */
1177 static struct view *display[2];
1178 static unsigned int current_view;
1180 /* Reading from the prompt? */
1181 static bool input_mode = FALSE;
1183 #define foreach_displayed_view(view, i) \
1184 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1186 #define displayed_views() (display[1] != NULL ? 2 : 1)
1188 /* Current head and commit ID */
1189 static char ref_blob[SIZEOF_REF] = "";
1190 static char ref_commit[SIZEOF_REF] = "HEAD";
1191 static char ref_head[SIZEOF_REF] = "HEAD";
1194 const char *name; /* View name */
1195 const char *cmd_fmt; /* Default command line format */
1196 const char *cmd_env; /* Command line set via environment */
1197 const char *id; /* Points to either of ref_{head,commit,blob} */
1199 struct view_ops *ops; /* View operations */
1201 enum keymap keymap; /* What keymap does this view have */
1203 char cmd[SIZEOF_STR]; /* Command buffer */
1204 char ref[SIZEOF_REF]; /* Hovered commit reference */
1205 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1207 int height, width; /* The width and height of the main window */
1208 WINDOW *win; /* The main window */
1209 WINDOW *title; /* The title window living below the main window */
1212 unsigned long offset; /* Offset of the window top */
1213 unsigned long lineno; /* Current line number */
1216 char grep[SIZEOF_STR]; /* Search string */
1217 regex_t *regex; /* Pre-compiled regex */
1219 /* If non-NULL, points to the view that opened this view. If this view
1220 * is closed tig will switch back to the parent view. */
1221 struct view *parent;
1224 unsigned long lines; /* Total number of lines */
1225 struct line *line; /* Line index */
1226 unsigned long line_size;/* Total number of allocated lines */
1227 unsigned int digits; /* Number of digits in the lines member. */
1235 /* What type of content being displayed. Used in the title bar. */
1237 /* Draw one line; @lineno must be < view->height. */
1238 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1239 /* Read one line; updates view->line. */
1240 bool (*read)(struct view *view, char *data);
1241 /* Depending on view, change display based on current line. */
1242 bool (*enter)(struct view *view, struct line *line);
1243 /* Search for regex in a line. */
1244 bool (*grep)(struct view *view, struct line *line);
1246 void (*select)(struct view *view, struct line *line);
1249 static struct view_ops pager_ops;
1250 static struct view_ops main_ops;
1251 static struct view_ops tree_ops;
1252 static struct view_ops blob_ops;
1254 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1255 { name, cmd, #env, ref, ops, map}
1257 #define VIEW_(id, name, ops, ref) \
1258 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1261 static struct view views[] = {
1262 VIEW_(MAIN, "main", &main_ops, ref_head),
1263 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1264 VIEW_(LOG, "log", &pager_ops, ref_head),
1265 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1266 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1267 VIEW_(HELP, "help", &pager_ops, ""),
1268 VIEW_(PAGER, "pager", &pager_ops, ""),
1271 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1273 #define foreach_view(view, i) \
1274 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1276 #define view_is_displayed(view) \
1277 (view == display[0] || view == display[1])
1280 draw_view_line(struct view *view, unsigned int lineno)
1283 bool selected = (view->offset + lineno == view->lineno);
1286 assert(view_is_displayed(view));
1288 if (view->offset + lineno >= view->lines)
1291 line = &view->line[view->offset + lineno];
1294 line->selected = TRUE;
1295 view->ops->select(view, line);
1296 } else if (line->selected) {
1297 line->selected = FALSE;
1298 wmove(view->win, lineno, 0);
1299 wclrtoeol(view->win);
1302 scrollok(view->win, FALSE);
1303 draw_ok = view->ops->draw(view, line, lineno, selected);
1304 scrollok(view->win, TRUE);
1310 redraw_view_from(struct view *view, int lineno)
1312 assert(0 <= lineno && lineno < view->height);
1314 for (; lineno < view->height; lineno++) {
1315 if (!draw_view_line(view, lineno))
1319 redrawwin(view->win);
1321 wnoutrefresh(view->win);
1323 wrefresh(view->win);
1327 redraw_view(struct view *view)
1330 redraw_view_from(view, 0);
1335 update_view_title(struct view *view)
1337 char buf[SIZEOF_STR];
1338 char state[SIZEOF_STR];
1339 size_t bufpos = 0, statelen = 0;
1341 assert(view_is_displayed(view));
1343 if (view->lines || view->pipe) {
1344 unsigned int view_lines = view->offset + view->height;
1345 unsigned int lines = view->lines
1346 ? MIN(view_lines, view->lines) * 100 / view->lines
1349 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1356 time_t secs = time(NULL) - view->start_time;
1358 /* Three git seconds are a long time ... */
1360 string_format_from(state, &statelen, " %lds", secs);
1364 string_format_from(buf, &bufpos, "[%s]", view->name);
1365 if (*view->ref && bufpos < view->width) {
1366 size_t refsize = strlen(view->ref);
1367 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1369 if (minsize < view->width)
1370 refsize = view->width - minsize + 7;
1371 string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
1374 if (statelen && bufpos < view->width) {
1375 string_format_from(buf, &bufpos, " %s", state);
1378 if (view == display[current_view])
1379 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1381 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1383 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1384 wclrtoeol(view->title);
1385 wmove(view->title, 0, view->width - 1);
1388 wnoutrefresh(view->title);
1390 wrefresh(view->title);
1394 resize_display(void)
1397 struct view *base = display[0];
1398 struct view *view = display[1] ? display[1] : display[0];
1400 /* Setup window dimensions */
1402 getmaxyx(stdscr, base->height, base->width);
1404 /* Make room for the status window. */
1408 /* Horizontal split. */
1409 view->width = base->width;
1410 view->height = SCALE_SPLIT_VIEW(base->height);
1411 base->height -= view->height;
1413 /* Make room for the title bar. */
1417 /* Make room for the title bar. */
1422 foreach_displayed_view (view, i) {
1424 view->win = newwin(view->height, 0, offset, 0);
1426 die("Failed to create %s view", view->name);
1428 scrollok(view->win, TRUE);
1430 view->title = newwin(1, 0, offset + view->height, 0);
1432 die("Failed to create title window");
1435 wresize(view->win, view->height, view->width);
1436 mvwin(view->win, offset, 0);
1437 mvwin(view->title, offset + view->height, 0);
1440 offset += view->height + 1;
1445 redraw_display(void)
1450 foreach_displayed_view (view, i) {
1452 update_view_title(view);
1457 update_display_cursor(struct view *view)
1459 /* Move the cursor to the right-most column of the cursor line.
1461 * XXX: This could turn out to be a bit expensive, but it ensures that
1462 * the cursor does not jump around. */
1464 wmove(view->win, view->lineno - view->offset, view->width - 1);
1465 wrefresh(view->win);
1473 /* Scrolling backend */
1475 do_scroll_view(struct view *view, int lines)
1477 bool redraw_current_line = FALSE;
1479 /* The rendering expects the new offset. */
1480 view->offset += lines;
1482 assert(0 <= view->offset && view->offset < view->lines);
1485 /* Move current line into the view. */
1486 if (view->lineno < view->offset) {
1487 view->lineno = view->offset;
1488 redraw_current_line = TRUE;
1489 } else if (view->lineno >= view->offset + view->height) {
1490 view->lineno = view->offset + view->height - 1;
1491 redraw_current_line = TRUE;
1494 assert(view->offset <= view->lineno && view->lineno < view->lines);
1496 /* Redraw the whole screen if scrolling is pointless. */
1497 if (view->height < ABS(lines)) {
1501 int line = lines > 0 ? view->height - lines : 0;
1502 int end = line + ABS(lines);
1504 wscrl(view->win, lines);
1506 for (; line < end; line++) {
1507 if (!draw_view_line(view, line))
1511 if (redraw_current_line)
1512 draw_view_line(view, view->lineno - view->offset);
1515 redrawwin(view->win);
1516 wrefresh(view->win);
1520 /* Scroll frontend */
1522 scroll_view(struct view *view, enum request request)
1526 assert(view_is_displayed(view));
1529 case REQ_SCROLL_PAGE_DOWN:
1530 lines = view->height;
1531 case REQ_SCROLL_LINE_DOWN:
1532 if (view->offset + lines > view->lines)
1533 lines = view->lines - view->offset;
1535 if (lines == 0 || view->offset + view->height >= view->lines) {
1536 report("Cannot scroll beyond the last line");
1541 case REQ_SCROLL_PAGE_UP:
1542 lines = view->height;
1543 case REQ_SCROLL_LINE_UP:
1544 if (lines > view->offset)
1545 lines = view->offset;
1548 report("Cannot scroll beyond the first line");
1556 die("request %d not handled in switch", request);
1559 do_scroll_view(view, lines);
1564 move_view(struct view *view, enum request request)
1566 int scroll_steps = 0;
1570 case REQ_MOVE_FIRST_LINE:
1571 steps = -view->lineno;
1574 case REQ_MOVE_LAST_LINE:
1575 steps = view->lines - view->lineno - 1;
1578 case REQ_MOVE_PAGE_UP:
1579 steps = view->height > view->lineno
1580 ? -view->lineno : -view->height;
1583 case REQ_MOVE_PAGE_DOWN:
1584 steps = view->lineno + view->height >= view->lines
1585 ? view->lines - view->lineno - 1 : view->height;
1597 die("request %d not handled in switch", request);
1600 if (steps <= 0 && view->lineno == 0) {
1601 report("Cannot move beyond the first line");
1604 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1605 report("Cannot move beyond the last line");
1609 /* Move the current line */
1610 view->lineno += steps;
1611 assert(0 <= view->lineno && view->lineno < view->lines);
1613 /* Check whether the view needs to be scrolled */
1614 if (view->lineno < view->offset ||
1615 view->lineno >= view->offset + view->height) {
1616 scroll_steps = steps;
1617 if (steps < 0 && -steps > view->offset) {
1618 scroll_steps = -view->offset;
1620 } else if (steps > 0) {
1621 if (view->lineno == view->lines - 1 &&
1622 view->lines > view->height) {
1623 scroll_steps = view->lines - view->offset - 1;
1624 if (scroll_steps >= view->height)
1625 scroll_steps -= view->height - 1;
1630 if (!view_is_displayed(view)) {
1631 view->offset += scroll_steps;
1632 assert(0 <= view->offset && view->offset < view->lines);
1633 view->ops->select(view, &view->line[view->lineno]);
1637 /* Repaint the old "current" line if we be scrolling */
1638 if (ABS(steps) < view->height)
1639 draw_view_line(view, view->lineno - steps - view->offset);
1642 do_scroll_view(view, scroll_steps);
1646 /* Draw the current line */
1647 draw_view_line(view, view->lineno - view->offset);
1649 redrawwin(view->win);
1650 wrefresh(view->win);
1659 static void search_view(struct view *view, enum request request);
1662 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1664 assert(view_is_displayed(view));
1666 if (!view->ops->grep(view, line))
1669 if (lineno - view->offset >= view->height) {
1670 view->offset = lineno;
1671 view->lineno = lineno;
1675 unsigned long old_lineno = view->lineno - view->offset;
1677 view->lineno = lineno;
1678 draw_view_line(view, old_lineno);
1680 draw_view_line(view, view->lineno - view->offset);
1681 redrawwin(view->win);
1682 wrefresh(view->win);
1685 report("Line %ld matches '%s'", lineno + 1, view->grep);
1690 find_next(struct view *view, enum request request)
1692 unsigned long lineno = view->lineno;
1697 report("No previous search");
1699 search_view(view, request);
1709 case REQ_SEARCH_BACK:
1718 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1719 lineno += direction;
1721 /* Note, lineno is unsigned long so will wrap around in which case it
1722 * will become bigger than view->lines. */
1723 for (; lineno < view->lines; lineno += direction) {
1724 struct line *line = &view->line[lineno];
1726 if (find_next_line(view, lineno, line))
1730 report("No match found for '%s'", view->grep);
1734 search_view(struct view *view, enum request request)
1739 regfree(view->regex);
1742 view->regex = calloc(1, sizeof(*view->regex));
1747 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1748 if (regex_err != 0) {
1749 char buf[SIZEOF_STR] = "unknown error";
1751 regerror(regex_err, view->regex, buf, sizeof(buf));
1752 report("Search failed: %s", buf);
1756 string_copy(view->grep, opt_search);
1758 find_next(view, request);
1762 * Incremental updating
1766 end_update(struct view *view)
1770 set_nonblocking_input(FALSE);
1771 if (view->pipe == stdin)
1779 begin_update(struct view *view)
1781 const char *id = view->id;
1787 string_copy(view->cmd, opt_cmd);
1789 /* When running random commands, initially show the
1790 * command in the title. However, it maybe later be
1791 * overwritten if a commit line is selected. */
1792 string_copy(view->ref, view->cmd);
1794 } else if (view == VIEW(REQ_VIEW_TREE)) {
1795 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1796 char path[SIZEOF_STR];
1798 if (strcmp(view->vid, view->id))
1799 opt_path[0] = path[0] = 0;
1800 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1803 if (!string_format(view->cmd, format, id, path))
1807 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1809 if (!string_format(view->cmd, format, id, id, id, id, id))
1812 /* Put the current ref_* value to the view title ref
1813 * member. This is needed by the blob view. Most other
1814 * views sets it automatically after loading because the
1815 * first line is a commit line. */
1816 string_copy(view->ref, id);
1819 /* Special case for the pager view. */
1821 view->pipe = opt_pipe;
1824 view->pipe = popen(view->cmd, "r");
1830 set_nonblocking_input(TRUE);
1835 string_copy_rev(view->vid, id);
1840 for (i = 0; i < view->lines; i++)
1841 if (view->line[i].data)
1842 free(view->line[i].data);
1848 view->start_time = time(NULL);
1853 static struct line *
1854 realloc_lines(struct view *view, size_t line_size)
1856 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1862 view->line_size = line_size;
1867 update_view(struct view *view)
1869 char in_buffer[BUFSIZ];
1870 char out_buffer[BUFSIZ * 2];
1872 /* The number of lines to read. If too low it will cause too much
1873 * redrawing (and possible flickering), if too high responsiveness
1875 unsigned long lines = view->height;
1876 int redraw_from = -1;
1881 /* Only redraw if lines are visible. */
1882 if (view->offset + view->height >= view->lines)
1883 redraw_from = view->lines - view->offset;
1885 /* FIXME: This is probably not perfect for backgrounded views. */
1886 if (!realloc_lines(view, view->lines + lines))
1889 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1890 size_t linelen = strlen(line);
1893 line[linelen - 1] = 0;
1895 if (opt_iconv != ICONV_NONE) {
1897 size_t inlen = linelen;
1899 char *outbuf = out_buffer;
1900 size_t outlen = sizeof(out_buffer);
1904 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1905 if (ret != (size_t) -1) {
1907 linelen = strlen(out_buffer);
1911 if (!view->ops->read(view, line))
1921 lines = view->lines;
1922 for (digits = 0; lines; digits++)
1925 /* Keep the displayed view in sync with line number scaling. */
1926 if (digits != view->digits) {
1927 view->digits = digits;
1932 if (!view_is_displayed(view))
1935 if (view == VIEW(REQ_VIEW_TREE)) {
1936 /* Clear the view and redraw everything since the tree sorting
1937 * might have rearranged things. */
1940 } else if (redraw_from >= 0) {
1941 /* If this is an incremental update, redraw the previous line
1942 * since for commits some members could have changed when
1943 * loading the main view. */
1944 if (redraw_from > 0)
1947 /* Since revision graph visualization requires knowledge
1948 * about the parent commit, it causes a further one-off
1949 * needed to be redrawn for incremental updates. */
1950 if (redraw_from > 0 && opt_rev_graph)
1953 /* Incrementally draw avoids flickering. */
1954 redraw_view_from(view, redraw_from);
1957 /* Update the title _after_ the redraw so that if the redraw picks up a
1958 * commit reference in view->ref it'll be available here. */
1959 update_view_title(view);
1962 if (ferror(view->pipe)) {
1963 report("Failed to read: %s", strerror(errno));
1966 } else if (feof(view->pipe)) {
1974 report("Allocation failure");
1977 view->ops->read(view, NULL);
1982 static struct line *
1983 add_line_text(struct view *view, char *data, enum line_type type)
1985 struct line *line = &view->line[view->lines];
1990 line->data = strdup(data);
2005 static void open_help_view(struct view *view)
2008 int lines = ARRAY_SIZE(req_info) + 2;
2011 if (view->lines > 0)
2014 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2015 if (!req_info[i].request)
2018 view->line = calloc(lines, sizeof(*view->line));
2020 report("Allocation failure");
2024 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2026 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2029 if (!req_info[i].request) {
2030 add_line_text(view, "", LINE_DEFAULT);
2031 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2035 key = get_key(req_info[i].request);
2036 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2039 add_line_text(view, buf, LINE_DEFAULT);
2044 OPEN_DEFAULT = 0, /* Use default view switching. */
2045 OPEN_SPLIT = 1, /* Split current view. */
2046 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2047 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2051 open_view(struct view *prev, enum request request, enum open_flags flags)
2053 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2054 bool split = !!(flags & OPEN_SPLIT);
2055 bool reload = !!(flags & OPEN_RELOAD);
2056 struct view *view = VIEW(request);
2057 int nviews = displayed_views();
2058 struct view *base_view = display[0];
2060 if (view == prev && nviews == 1 && !reload) {
2061 report("Already in %s view", view->name);
2065 if (view == VIEW(REQ_VIEW_HELP)) {
2066 open_help_view(view);
2068 } else if ((reload || strcmp(view->vid, view->id)) &&
2069 !begin_update(view)) {
2070 report("Failed to load %s view", view->name);
2079 /* Maximize the current view. */
2080 memset(display, 0, sizeof(display));
2082 display[current_view] = view;
2085 /* Resize the view when switching between split- and full-screen,
2086 * or when switching between two different full-screen views. */
2087 if (nviews != displayed_views() ||
2088 (nviews == 1 && base_view != display[0]))
2091 if (split && prev->lineno - prev->offset >= prev->height) {
2092 /* Take the title line into account. */
2093 int lines = prev->lineno - prev->offset - prev->height + 1;
2095 /* Scroll the view that was split if the current line is
2096 * outside the new limited view. */
2097 do_scroll_view(prev, lines);
2100 if (prev && view != prev) {
2101 if (split && !backgrounded) {
2102 /* "Blur" the previous view. */
2103 update_view_title(prev);
2106 view->parent = prev;
2109 if (view->pipe && view->lines == 0) {
2110 /* Clear the old view and let the incremental updating refill
2119 /* If the view is backgrounded the above calls to report()
2120 * won't redraw the view title. */
2122 update_view_title(view);
2127 * User request switch noodle
2131 view_driver(struct view *view, enum request request)
2138 case REQ_MOVE_PAGE_UP:
2139 case REQ_MOVE_PAGE_DOWN:
2140 case REQ_MOVE_FIRST_LINE:
2141 case REQ_MOVE_LAST_LINE:
2142 move_view(view, request);
2145 case REQ_SCROLL_LINE_DOWN:
2146 case REQ_SCROLL_LINE_UP:
2147 case REQ_SCROLL_PAGE_DOWN:
2148 case REQ_SCROLL_PAGE_UP:
2149 scroll_view(view, request);
2154 report("No file chosen, press %s to open tree view",
2155 get_key(REQ_VIEW_TREE));
2158 open_view(view, request, OPEN_DEFAULT);
2161 case REQ_VIEW_PAGER:
2162 if (!VIEW(REQ_VIEW_PAGER)->lines) {
2163 report("No pager content, press %s to run command from prompt",
2164 get_key(REQ_PROMPT));
2167 open_view(view, request, OPEN_DEFAULT);
2175 open_view(view, request, OPEN_DEFAULT);
2180 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2182 if ((view == VIEW(REQ_VIEW_DIFF) &&
2183 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2184 (view == VIEW(REQ_VIEW_BLOB) &&
2185 view->parent == VIEW(REQ_VIEW_TREE))) {
2186 view = view->parent;
2187 move_view(view, request);
2188 if (view_is_displayed(view))
2189 update_view_title(view);
2191 move_view(view, request);
2198 report("Nothing to enter");
2201 return view->ops->enter(view, &view->line[view->lineno]);
2205 int nviews = displayed_views();
2206 int next_view = (current_view + 1) % nviews;
2208 if (next_view == current_view) {
2209 report("Only one view is displayed");
2213 current_view = next_view;
2214 /* Blur out the title of the previous view. */
2215 update_view_title(view);
2219 case REQ_TOGGLE_LINENO:
2220 opt_line_number = !opt_line_number;
2224 case REQ_TOGGLE_REV_GRAPH:
2225 opt_rev_graph = !opt_rev_graph;
2230 /* Always reload^Wrerun commands from the prompt. */
2231 open_view(view, opt_request, OPEN_RELOAD);
2235 case REQ_SEARCH_BACK:
2236 search_view(view, request);
2241 find_next(view, request);
2244 case REQ_STOP_LOADING:
2245 for (i = 0; i < ARRAY_SIZE(views); i++) {
2248 report("Stopped loading the %s view", view->name),
2253 case REQ_SHOW_VERSION:
2254 report("%s (built %s)", VERSION, __DATE__);
2257 case REQ_SCREEN_RESIZE:
2260 case REQ_SCREEN_REDRAW:
2268 case REQ_VIEW_CLOSE:
2269 /* XXX: Mark closed views by letting view->parent point to the
2270 * view itself. Parents to closed view should never be
2273 view->parent->parent != view->parent) {
2274 memset(display, 0, sizeof(display));
2276 display[current_view] = view->parent;
2277 view->parent = view;
2287 /* An unknown key will show most commonly used commands. */
2288 report("Unknown key, press 'h' for help");
2301 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2303 char *text = line->data;
2304 enum line_type type = line->type;
2305 int textlen = strlen(text);
2308 wmove(view->win, lineno, 0);
2312 wchgat(view->win, -1, 0, type, NULL);
2315 attr = get_line_attr(type);
2316 wattrset(view->win, attr);
2318 if (opt_line_number || opt_tab_size < TABSIZE) {
2319 static char spaces[] = " ";
2320 int col_offset = 0, col = 0;
2322 if (opt_line_number) {
2323 unsigned long real_lineno = view->offset + lineno + 1;
2325 if (real_lineno == 1 ||
2326 (real_lineno % opt_num_interval) == 0) {
2327 wprintw(view->win, "%.*d", view->digits, real_lineno);
2330 waddnstr(view->win, spaces,
2331 MIN(view->digits, STRING_SIZE(spaces)));
2333 waddstr(view->win, ": ");
2334 col_offset = view->digits + 2;
2337 while (text && col_offset + col < view->width) {
2338 int cols_max = view->width - col_offset - col;
2342 if (*text == '\t') {
2344 assert(sizeof(spaces) > TABSIZE);
2346 cols = opt_tab_size - (col % opt_tab_size);
2349 text = strchr(text, '\t');
2350 cols = line ? text - pos : strlen(pos);
2353 waddnstr(view->win, pos, MIN(cols, cols_max));
2358 int col = 0, pos = 0;
2360 for (; pos < textlen && col < view->width; pos++, col++)
2361 if (text[pos] == '\t')
2362 col += TABSIZE - (col % TABSIZE) - 1;
2364 waddnstr(view->win, text, pos);
2371 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2373 char refbuf[SIZEOF_STR];
2377 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2380 pipe = popen(refbuf, "r");
2384 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2385 ref = chomp_string(ref);
2391 /* This is the only fatal call, since it can "corrupt" the buffer. */
2392 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2399 add_pager_refs(struct view *view, struct line *line)
2401 char buf[SIZEOF_STR];
2402 char *commit_id = line->data + STRING_SIZE("commit ");
2404 size_t bufpos = 0, refpos = 0;
2405 const char *sep = "Refs: ";
2406 bool is_tag = FALSE;
2408 assert(line->type == LINE_COMMIT);
2410 refs = get_refs(commit_id);
2412 if (view == VIEW(REQ_VIEW_DIFF))
2413 goto try_add_describe_ref;
2418 struct ref *ref = refs[refpos];
2419 char *fmt = ref->tag ? "%s[%s]" :
2420 ref->remote ? "%s<%s>" : "%s%s";
2422 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2427 } while (refs[refpos++]->next);
2429 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2430 try_add_describe_ref:
2431 /* Add <tag>-g<commit_id> "fake" reference. */
2432 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2439 if (!realloc_lines(view, view->line_size + 1))
2442 add_line_text(view, buf, LINE_PP_REFS);
2446 pager_read(struct view *view, char *data)
2453 line = add_line_text(view, data, get_line_type(data));
2457 if (line->type == LINE_COMMIT &&
2458 (view == VIEW(REQ_VIEW_DIFF) ||
2459 view == VIEW(REQ_VIEW_LOG)))
2460 add_pager_refs(view, line);
2466 pager_enter(struct view *view, struct line *line)
2470 if (line->type == LINE_COMMIT &&
2471 (view == VIEW(REQ_VIEW_LOG) ||
2472 view == VIEW(REQ_VIEW_PAGER))) {
2473 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2477 /* Always scroll the view even if it was split. That way
2478 * you can use Enter to scroll through the log view and
2479 * split open each commit diff. */
2480 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2482 /* FIXME: A minor workaround. Scrolling the view will call report("")
2483 * but if we are scrolling a non-current view this won't properly
2484 * update the view title. */
2486 update_view_title(view);
2492 pager_grep(struct view *view, struct line *line)
2495 char *text = line->data;
2500 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2507 pager_select(struct view *view, struct line *line)
2509 if (line->type == LINE_COMMIT) {
2510 char *text = line->data + STRING_SIZE("commit ");
2512 if (view != VIEW(REQ_VIEW_PAGER))
2513 string_copy_rev(view->ref, text);
2514 string_copy_rev(ref_commit, text);
2518 static struct view_ops pager_ops = {
2532 /* Parse output from git-ls-tree(1):
2534 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2535 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2536 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2537 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2540 #define SIZEOF_TREE_ATTR \
2541 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2543 #define TREE_UP_FORMAT "040000 tree %s\t.."
2546 tree_compare_entry(enum line_type type1, char *name1,
2547 enum line_type type2, char *name2)
2549 if (type1 != type2) {
2550 if (type1 == LINE_TREE_DIR)
2555 return strcmp(name1, name2);
2559 tree_read(struct view *view, char *text)
2561 size_t textlen = text ? strlen(text) : 0;
2562 char buf[SIZEOF_STR];
2564 enum line_type type;
2565 bool first_read = view->lines == 0;
2567 if (textlen <= SIZEOF_TREE_ATTR)
2570 type = text[STRING_SIZE("100644 ")] == 't'
2571 ? LINE_TREE_DIR : LINE_TREE_FILE;
2574 /* Add path info line */
2575 if (!string_format(buf, "Directory path /%s", opt_path) ||
2576 !realloc_lines(view, view->line_size + 1) ||
2577 !add_line_text(view, buf, LINE_DEFAULT))
2580 /* Insert "link" to parent directory. */
2582 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2583 !realloc_lines(view, view->line_size + 1) ||
2584 !add_line_text(view, buf, LINE_TREE_DIR))
2589 /* Strip the path part ... */
2591 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2592 size_t striplen = strlen(opt_path);
2593 char *path = text + SIZEOF_TREE_ATTR;
2595 if (pathlen > striplen)
2596 memmove(path, path + striplen,
2597 pathlen - striplen + 1);
2600 /* Skip "Directory ..." and ".." line. */
2601 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2602 struct line *line = &view->line[pos];
2603 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2604 char *path2 = text + SIZEOF_TREE_ATTR;
2605 int cmp = tree_compare_entry(line->type, path1, type, path2);
2610 text = strdup(text);
2614 if (view->lines > pos)
2615 memmove(&view->line[pos + 1], &view->line[pos],
2616 (view->lines - pos) * sizeof(*line));
2618 line = &view->line[pos];
2625 if (!add_line_text(view, text, type))
2628 /* Move the current line to the first tree entry. */
2636 tree_enter(struct view *view, struct line *line)
2638 enum open_flags flags;
2639 enum request request;
2641 switch (line->type) {
2643 /* Depending on whether it is a subdir or parent (updir?) link
2644 * mangle the path buffer. */
2645 if (line == &view->line[1] && *opt_path) {
2646 size_t path_len = strlen(opt_path);
2647 char *dirsep = opt_path + path_len - 1;
2649 while (dirsep > opt_path && dirsep[-1] != '/')
2655 size_t pathlen = strlen(opt_path);
2656 size_t origlen = pathlen;
2657 char *data = line->data;
2658 char *basename = data + SIZEOF_TREE_ATTR;
2660 if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2661 opt_path[origlen] = 0;
2666 /* Trees and subtrees share the same ID, so they are not not
2667 * unique like blobs. */
2668 flags = OPEN_RELOAD;
2669 request = REQ_VIEW_TREE;
2672 case LINE_TREE_FILE:
2673 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2674 request = REQ_VIEW_BLOB;
2681 open_view(view, request, flags);
2687 tree_select(struct view *view, struct line *line)
2689 char *text = line->data + STRING_SIZE("100644 blob ");
2691 if (line->type == LINE_TREE_FILE) {
2692 string_copy_rev(ref_blob, text);
2694 } else if (line->type != LINE_TREE_DIR) {
2698 string_copy_rev(view->ref, text);
2701 static struct view_ops tree_ops = {
2711 blob_read(struct view *view, char *line)
2713 return add_line_text(view, line, LINE_DEFAULT);
2716 static struct view_ops blob_ops = {
2731 char id[SIZEOF_REV]; /* SHA1 ID. */
2732 char title[128]; /* First line of the commit message. */
2733 char author[75]; /* Author of the commit. */
2734 struct tm time; /* Date from the author ident. */
2735 struct ref **refs; /* Repository references. */
2736 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
2737 size_t graph_size; /* The width of the graph array. */
2740 /* Size of rev graph with no "padding" columns */
2741 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
2744 struct rev_graph *prev, *next, *parents;
2745 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
2747 struct commit *commit;
2751 /* Parents of the commit being visualized. */
2752 static struct rev_graph graph_parents[4];
2754 /* The current stack of revisions on the graph. */
2755 static struct rev_graph graph_stacks[4] = {
2756 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
2757 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
2758 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
2759 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
2763 graph_parent_is_merge(struct rev_graph *graph)
2765 return graph->parents->size > 1;
2769 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
2771 struct commit *commit = graph->commit;
2773 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
2774 commit->graph[commit->graph_size++] = symbol;
2778 done_rev_graph(struct rev_graph *graph)
2780 if (graph_parent_is_merge(graph) &&
2781 graph->pos < graph->size - 1 &&
2782 graph->next->size == graph->size + graph->parents->size - 1) {
2783 size_t i = graph->pos + graph->parents->size - 1;
2785 graph->commit->graph_size = i * 2;
2786 while (i < graph->next->size - 1) {
2787 append_to_rev_graph(graph, ' ');
2788 append_to_rev_graph(graph, '\\');
2793 graph->size = graph->pos = 0;
2794 graph->commit = NULL;
2795 memset(graph->parents, 0, sizeof(*graph->parents));
2799 push_rev_graph(struct rev_graph *graph, char *parent)
2803 /* "Collapse" duplicate parents lines.
2805 * FIXME: This needs to also update update the drawn graph but
2806 * for now it just serves as a method for pruning graph lines. */
2807 for (i = 0; i < graph->size; i++)
2808 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
2811 if (graph->size < SIZEOF_REVITEMS) {
2812 string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
2817 get_rev_graph_symbol(struct rev_graph *graph)
2821 if (graph->parents->size == 0)
2822 symbol = REVGRAPH_INIT;
2823 else if (graph_parent_is_merge(graph))
2824 symbol = REVGRAPH_MERGE;
2825 else if (graph->pos >= graph->size)
2826 symbol = REVGRAPH_BRANCH;
2828 symbol = REVGRAPH_COMMIT;
2834 draw_rev_graph(struct rev_graph *graph)
2837 chtype separator, line;
2839 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
2840 static struct rev_filler fillers[] = {
2841 { ' ', REVGRAPH_LINE },
2846 chtype symbol = get_rev_graph_symbol(graph);
2847 struct rev_filler *filler;
2850 filler = &fillers[DEFAULT];
2852 for (i = 0; i < graph->pos; i++) {
2853 append_to_rev_graph(graph, filler->line);
2854 if (graph_parent_is_merge(graph->prev) &&
2855 graph->prev->pos == i)
2856 filler = &fillers[RSHARP];
2858 append_to_rev_graph(graph, filler->separator);
2861 /* Place the symbol for this revision. */
2862 append_to_rev_graph(graph, symbol);
2864 if (graph->prev->size > graph->size)
2865 filler = &fillers[RDIAG];
2867 filler = &fillers[DEFAULT];
2871 for (; i < graph->size; i++) {
2872 append_to_rev_graph(graph, filler->separator);
2873 append_to_rev_graph(graph, filler->line);
2874 if (graph_parent_is_merge(graph->prev) &&
2875 i < graph->prev->pos + graph->parents->size)
2876 filler = &fillers[RSHARP];
2877 if (graph->prev->size > graph->size)
2878 filler = &fillers[LDIAG];
2881 if (graph->prev->size > graph->size) {
2882 append_to_rev_graph(graph, filler->separator);
2883 if (filler->line != ' ')
2884 append_to_rev_graph(graph, filler->line);
2888 /* Prepare the next rev graph */
2890 prepare_rev_graph(struct rev_graph *graph)
2894 /* First, traverse all lines of revisions up to the active one. */
2895 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
2896 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
2899 push_rev_graph(graph->next, graph->rev[graph->pos]);
2902 /* Interleave the new revision parent(s). */
2903 for (i = 0; i < graph->parents->size; i++)
2904 push_rev_graph(graph->next, graph->parents->rev[i]);
2906 /* Lastly, put any remaining revisions. */
2907 for (i = graph->pos + 1; i < graph->size; i++)
2908 push_rev_graph(graph->next, graph->rev[i]);
2912 update_rev_graph(struct rev_graph *graph)
2914 /* If this is the finalizing update ... */
2916 prepare_rev_graph(graph);
2918 /* Graph visualization needs a one rev look-ahead,
2919 * so the first update doesn't visualize anything. */
2920 if (!graph->prev->commit)
2923 draw_rev_graph(graph->prev);
2924 done_rev_graph(graph->prev->prev);
2933 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2935 char buf[DATE_COLS + 1];
2936 struct commit *commit = line->data;
2937 enum line_type type;
2943 if (!*commit->author)
2946 wmove(view->win, lineno, col);
2950 wattrset(view->win, get_line_attr(type));
2951 wchgat(view->win, -1, 0, type, NULL);
2954 type = LINE_MAIN_COMMIT;
2955 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2958 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2959 waddnstr(view->win, buf, timelen);
2960 waddstr(view->win, " ");
2963 wmove(view->win, lineno, col);
2964 if (type != LINE_CURSOR)
2965 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2968 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2970 authorlen = strlen(commit->author);
2971 if (authorlen > AUTHOR_COLS - 2) {
2972 authorlen = AUTHOR_COLS - 2;
2978 waddnstr(view->win, commit->author, authorlen);
2979 if (type != LINE_CURSOR)
2980 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2981 waddch(view->win, '~');
2983 waddstr(view->win, commit->author);
2987 if (type != LINE_CURSOR)
2988 wattrset(view->win, A_NORMAL);
2990 if (opt_rev_graph && commit->graph_size) {
2993 wmove(view->win, lineno, col);
2994 /* Using waddch() instead of waddnstr() ensures that
2995 * they'll be rendered correctly for the cursor line. */
2996 for (i = 0; i < commit->graph_size; i++)
2997 waddch(view->win, commit->graph[i]);
2999 waddch(view->win, ' ');
3000 col += commit->graph_size + 1;
3003 wmove(view->win, lineno, col);
3009 if (type == LINE_CURSOR)
3011 else if (commit->refs[i]->tag)
3012 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3013 else if (commit->refs[i]->remote)
3014 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3016 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3017 waddstr(view->win, "[");
3018 waddstr(view->win, commit->refs[i]->name);
3019 waddstr(view->win, "]");
3020 if (type != LINE_CURSOR)
3021 wattrset(view->win, A_NORMAL);
3022 waddstr(view->win, " ");
3023 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3024 } while (commit->refs[i++]->next);
3027 if (type != LINE_CURSOR)
3028 wattrset(view->win, get_line_attr(type));
3031 int titlelen = strlen(commit->title);
3033 if (col + titlelen > view->width)
3034 titlelen = view->width - col;
3036 waddnstr(view->win, commit->title, titlelen);
3042 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3044 main_read(struct view *view, char *line)
3046 static struct rev_graph *graph = graph_stacks;
3047 enum line_type type;
3048 struct commit *commit = view->lines
3049 ? view->line[view->lines - 1].data : NULL;
3052 update_rev_graph(graph);
3056 type = get_line_type(line);
3060 commit = calloc(1, sizeof(struct commit));
3064 line += STRING_SIZE("commit ");
3066 view->line[view->lines++].data = commit;
3067 string_copy_rev(commit->id, line);
3068 commit->refs = get_refs(commit->id);
3069 graph->commit = commit;
3074 line += STRING_SIZE("parent ");
3075 push_rev_graph(graph->parents, line);
3081 /* Parse author lines where the name may be empty:
3082 * author <email@address.tld> 1138474660 +0100
3084 char *ident = line + STRING_SIZE("author ");
3085 char *nameend = strchr(ident, '<');
3086 char *emailend = strchr(ident, '>');
3088 if (!commit || !nameend || !emailend)
3091 update_rev_graph(graph);
3092 graph = graph->next;
3094 *nameend = *emailend = 0;
3095 ident = chomp_string(ident);
3097 ident = chomp_string(nameend + 1);
3102 string_copy(commit->author, ident);
3104 /* Parse epoch and timezone */
3105 if (emailend[1] == ' ') {
3106 char *secs = emailend + 2;
3107 char *zone = strchr(secs, ' ');
3108 time_t time = (time_t) atol(secs);
3110 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3114 tz = ('0' - zone[1]) * 60 * 60 * 10;
3115 tz += ('0' - zone[2]) * 60 * 60;
3116 tz += ('0' - zone[3]) * 60;
3117 tz += ('0' - zone[4]) * 60;
3125 gmtime_r(&time, &commit->time);
3133 /* Fill in the commit title if it has not already been set. */
3134 if (commit->title[0])
3137 /* Require titles to start with a non-space character at the
3138 * offset used by git log. */
3139 if (strncmp(line, " ", 4))
3142 /* Well, if the title starts with a whitespace character,
3143 * try to be forgiving. Otherwise we end up with no title. */
3144 while (isspace(*line))
3148 /* FIXME: More graceful handling of titles; append "..." to
3149 * shortened titles, etc. */
3151 string_copy(commit->title, line);
3158 main_enter(struct view *view, struct line *line)
3160 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3162 open_view(view, REQ_VIEW_DIFF, flags);
3167 main_grep(struct view *view, struct line *line)
3169 struct commit *commit = line->data;
3170 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3171 char buf[DATE_COLS + 1];
3174 for (state = S_TITLE; state < S_END; state++) {
3178 case S_TITLE: text = commit->title; break;
3179 case S_AUTHOR: text = commit->author; break;
3181 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3190 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3198 main_select(struct view *view, struct line *line)
3200 struct commit *commit = line->data;
3202 string_copy_rev(view->ref, commit->id);
3203 string_copy_rev(ref_commit, view->ref);
3206 static struct view_ops main_ops = {
3217 * Unicode / UTF-8 handling
3219 * NOTE: Much of the following code for dealing with unicode is derived from
3220 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3221 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3224 /* I've (over)annotated a lot of code snippets because I am not entirely
3225 * confident that the approach taken by this small UTF-8 interface is correct.
3229 unicode_width(unsigned long c)
3232 (c <= 0x115f /* Hangul Jamo */
3235 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
3237 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
3238 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
3239 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
3240 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
3241 || (c >= 0xffe0 && c <= 0xffe6)
3242 || (c >= 0x20000 && c <= 0x2fffd)
3243 || (c >= 0x30000 && c <= 0x3fffd)))
3249 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3250 * Illegal bytes are set one. */
3251 static const unsigned char utf8_bytes[256] = {
3252 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,
3253 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,
3254 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,
3255 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,
3256 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,
3257 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,
3258 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,
3259 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,
3262 /* Decode UTF-8 multi-byte representation into a unicode character. */
3263 static inline unsigned long
3264 utf8_to_unicode(const char *string, size_t length)
3266 unsigned long unicode;
3270 unicode = string[0];
3273 unicode = (string[0] & 0x1f) << 6;
3274 unicode += (string[1] & 0x3f);
3277 unicode = (string[0] & 0x0f) << 12;
3278 unicode += ((string[1] & 0x3f) << 6);
3279 unicode += (string[2] & 0x3f);
3282 unicode = (string[0] & 0x0f) << 18;
3283 unicode += ((string[1] & 0x3f) << 12);
3284 unicode += ((string[2] & 0x3f) << 6);
3285 unicode += (string[3] & 0x3f);
3288 unicode = (string[0] & 0x0f) << 24;
3289 unicode += ((string[1] & 0x3f) << 18);
3290 unicode += ((string[2] & 0x3f) << 12);
3291 unicode += ((string[3] & 0x3f) << 6);
3292 unicode += (string[4] & 0x3f);
3295 unicode = (string[0] & 0x01) << 30;
3296 unicode += ((string[1] & 0x3f) << 24);
3297 unicode += ((string[2] & 0x3f) << 18);
3298 unicode += ((string[3] & 0x3f) << 12);
3299 unicode += ((string[4] & 0x3f) << 6);
3300 unicode += (string[5] & 0x3f);
3303 die("Invalid unicode length");
3306 /* Invalid characters could return the special 0xfffd value but NUL
3307 * should be just as good. */
3308 return unicode > 0xffff ? 0 : unicode;
3311 /* Calculates how much of string can be shown within the given maximum width
3312 * and sets trimmed parameter to non-zero value if all of string could not be
3315 * Additionally, adds to coloffset how many many columns to move to align with
3316 * the expected position. Takes into account how multi-byte and double-width
3317 * characters will effect the cursor position.
3319 * Returns the number of bytes to output from string to satisfy max_width. */
3321 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3323 const char *start = string;
3324 const char *end = strchr(string, '\0');
3330 while (string < end) {
3331 int c = *(unsigned char *) string;
3332 unsigned char bytes = utf8_bytes[c];
3334 unsigned long unicode;
3336 if (string + bytes > end)
3339 /* Change representation to figure out whether
3340 * it is a single- or double-width character. */
3342 unicode = utf8_to_unicode(string, bytes);
3343 /* FIXME: Graceful handling of invalid unicode character. */
3347 ucwidth = unicode_width(unicode);
3349 if (width > max_width) {
3354 /* The column offset collects the differences between the
3355 * number of bytes encoding a character and the number of
3356 * columns will be used for rendering said character.
3358 * So if some character A is encoded in 2 bytes, but will be
3359 * represented on the screen using only 1 byte this will and up
3360 * adding 1 to the multi-byte column offset.
3362 * Assumes that no double-width character can be encoding in
3363 * less than two bytes. */
3364 if (bytes > ucwidth)
3365 mbwidth += bytes - ucwidth;
3370 *coloffset += mbwidth;
3372 return string - start;
3380 /* Whether or not the curses interface has been initialized. */
3381 static bool cursed = FALSE;
3383 /* The status window is used for polling keystrokes. */
3384 static WINDOW *status_win;
3386 static bool status_empty = TRUE;
3388 /* Update status and title window. */
3390 report(const char *msg, ...)
3392 struct view *view = display[current_view];
3397 if (!status_empty || *msg) {
3400 va_start(args, msg);
3402 wmove(status_win, 0, 0);
3404 vwprintw(status_win, msg, args);
3405 status_empty = FALSE;
3407 status_empty = TRUE;
3409 wclrtoeol(status_win);
3410 wrefresh(status_win);
3415 update_view_title(view);
3416 update_display_cursor(view);
3419 /* Controls when nodelay should be in effect when polling user input. */
3421 set_nonblocking_input(bool loading)
3423 static unsigned int loading_views;
3425 if ((loading == FALSE && loading_views-- == 1) ||
3426 (loading == TRUE && loading_views++ == 0))
3427 nodelay(status_win, loading);
3435 /* Initialize the curses library */
3436 if (isatty(STDIN_FILENO)) {
3437 cursed = !!initscr();
3439 /* Leave stdin and stdout alone when acting as a pager. */
3440 FILE *io = fopen("/dev/tty", "r+");
3443 die("Failed to open /dev/tty");
3444 cursed = !!newterm(NULL, io, io);
3448 die("Failed to initialize curses");
3450 nonl(); /* Tell curses not to do NL->CR/NL on output */
3451 cbreak(); /* Take input chars one at a time, no wait for \n */
3452 noecho(); /* Don't echo input */
3453 leaveok(stdscr, TRUE);
3458 getmaxyx(stdscr, y, x);
3459 status_win = newwin(1, 0, y - 1, 0);
3461 die("Failed to create status window");
3463 /* Enable keyboard mapping */
3464 keypad(status_win, TRUE);
3465 wbkgdset(status_win, get_line_attr(LINE_STATUS));
3469 read_prompt(const char *prompt)
3471 enum { READING, STOP, CANCEL } status = READING;
3472 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3475 while (status == READING) {
3481 foreach_view (view, i)
3486 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3487 wclrtoeol(status_win);
3489 /* Refresh, accept single keystroke of input */
3490 key = wgetch(status_win);
3495 status = pos ? STOP : CANCEL;
3513 if (pos >= sizeof(buf)) {
3514 report("Input string too long");
3519 buf[pos++] = (char) key;
3523 /* Clear the status window */
3524 status_empty = FALSE;
3527 if (status == CANCEL)
3536 * Repository references
3539 static struct ref *refs;
3540 static size_t refs_size;
3542 /* Id <-> ref store */
3543 static struct ref ***id_refs;
3544 static size_t id_refs_size;
3546 static struct ref **
3549 struct ref ***tmp_id_refs;
3550 struct ref **ref_list = NULL;
3551 size_t ref_list_size = 0;
3554 for (i = 0; i < id_refs_size; i++)
3555 if (!strcmp(id, id_refs[i][0]->id))
3558 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3562 id_refs = tmp_id_refs;
3564 for (i = 0; i < refs_size; i++) {
3567 if (strcmp(id, refs[i].id))
3570 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3578 if (ref_list_size > 0)
3579 ref_list[ref_list_size - 1]->next = 1;
3580 ref_list[ref_list_size] = &refs[i];
3582 /* XXX: The properties of the commit chains ensures that we can
3583 * safely modify the shared ref. The repo references will
3584 * always be similar for the same id. */
3585 ref_list[ref_list_size]->next = 0;
3590 id_refs[id_refs_size++] = ref_list;
3596 read_ref(char *id, int idlen, char *name, int namelen)
3600 bool remote = FALSE;
3602 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3603 /* Commits referenced by tags has "^{}" appended. */
3604 if (name[namelen - 1] != '}')
3607 while (namelen > 0 && name[namelen] != '^')
3611 namelen -= STRING_SIZE("refs/tags/");
3612 name += STRING_SIZE("refs/tags/");
3614 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
3616 namelen -= STRING_SIZE("refs/remotes/");
3617 name += STRING_SIZE("refs/remotes/");
3619 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3620 namelen -= STRING_SIZE("refs/heads/");
3621 name += STRING_SIZE("refs/heads/");
3623 } else if (!strcmp(name, "HEAD")) {
3627 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3631 ref = &refs[refs_size++];
3632 ref->name = malloc(namelen + 1);
3636 strncpy(ref->name, name, namelen);
3637 ref->name[namelen] = 0;
3639 ref->remote = remote;
3640 string_copy_rev(ref->id, id);
3648 const char *cmd_env = getenv("TIG_LS_REMOTE");
3649 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3651 return read_properties(popen(cmd, "r"), "\t", read_ref);
3655 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3657 if (!strcmp(name, "i18n.commitencoding"))
3658 string_copy(opt_encoding, value);
3664 load_repo_config(void)
3666 return read_properties(popen("git repo-config --list", "r"),
3667 "=", read_repo_config_option);
3671 read_properties(FILE *pipe, const char *separators,
3672 int (*read_property)(char *, int, char *, int))
3674 char buffer[BUFSIZ];
3681 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3686 name = chomp_string(name);
3687 namelen = strcspn(name, separators);
3689 if (name[namelen]) {
3691 value = chomp_string(name + namelen + 1);
3692 valuelen = strlen(value);
3699 state = read_property(name, namelen, value, valuelen);
3702 if (state != ERR && ferror(pipe))
3715 static void __NORETURN
3718 /* XXX: Restore tty modes and let the OS cleanup the rest! */
3724 static void __NORETURN
3725 die(const char *err, ...)
3731 va_start(args, err);
3732 fputs("tig: ", stderr);
3733 vfprintf(stderr, err, args);
3734 fputs("\n", stderr);
3741 main(int argc, char *argv[])
3744 enum request request;
3747 signal(SIGINT, quit);
3749 if (setlocale(LC_ALL, "")) {
3750 string_copy(opt_codeset, nl_langinfo(CODESET));
3753 if (load_options() == ERR)
3754 die("Failed to load user config.");
3756 /* Load the repo config file so options can be overwritten from
3757 * the command line. */
3758 if (load_repo_config() == ERR)
3759 die("Failed to load repo config.");
3761 if (!parse_options(argc, argv))
3764 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3765 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3766 if (opt_iconv == ICONV_NONE)
3767 die("Failed to initialize character set conversion");
3770 if (load_refs() == ERR)
3771 die("Failed to load refs.");
3773 /* Require a git repository unless when running in pager mode. */
3774 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3775 die("Not a git repository");
3777 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3778 view->cmd_env = getenv(view->cmd_env);
3780 request = opt_request;
3784 while (view_driver(display[current_view], request)) {
3788 foreach_view (view, i)
3791 /* Refresh, accept single keystroke of input */
3792 key = wgetch(status_win);
3794 /* wgetch() with nodelay() enabled returns ERR when there's no
3801 request = get_keybinding(display[current_view]->keymap, key);
3803 /* Some low-level request handling. This keeps access to
3804 * status_win restricted. */
3808 char *cmd = read_prompt(":");
3810 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3811 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3812 opt_request = REQ_VIEW_DIFF;
3814 opt_request = REQ_VIEW_PAGER;
3823 case REQ_SEARCH_BACK:
3825 const char *prompt = request == REQ_SEARCH
3827 char *search = read_prompt(prompt);
3830 string_copy(opt_search, search);
3835 case REQ_SCREEN_RESIZE:
3839 getmaxyx(stdscr, height, width);
3841 /* Resize the status view and let the view driver take
3842 * care of resizing the displayed views. */
3843 wresize(status_win, 1, width);
3844 mvwin(status_win, height - 1, 0);
3845 wrefresh(status_win);