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 "unknown-version"
30 #include <sys/types.h>
44 #define __NORETURN __attribute__((__noreturn__))
49 static void __NORETURN die(const char *err, ...);
50 static void report(const char *msg, ...);
51 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
52 static void set_nonblocking_input(bool loading);
53 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
55 #define ABS(x) ((x) >= 0 ? (x) : -(x))
56 #define MIN(x, y) ((x) < (y) ? (x) : (y))
58 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
59 #define STRING_SIZE(x) (sizeof(x) - 1)
61 #define SIZEOF_STR 1024 /* Default string size. */
62 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
63 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
67 #define REVGRAPH_INIT 'I'
68 #define REVGRAPH_MERGE 'M'
69 #define REVGRAPH_BRANCH '+'
70 #define REVGRAPH_COMMIT '*'
71 #define REVGRAPH_LINE '|'
73 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
75 /* This color name can be used to refer to the default term colors. */
76 #define COLOR_DEFAULT (-1)
78 #define ICONV_NONE ((iconv_t) -1)
80 /* The format and size of the date column in the main view. */
81 #define DATE_FORMAT "%Y-%m-%d %H:%M"
82 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
84 #define AUTHOR_COLS 20
86 /* The default interval between line numbers. */
87 #define NUMBER_INTERVAL 1
91 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
93 #define TIG_LS_REMOTE \
94 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
96 #define TIG_DIFF_CMD \
97 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
100 "git log --cc --stat -n100 %s 2>/dev/null"
102 #define TIG_MAIN_CMD \
103 "git log --topo-order --pretty=raw %s 2>/dev/null"
105 #define TIG_TREE_CMD \
108 #define TIG_BLOB_CMD \
109 "git cat-file blob %s"
111 /* XXX: Needs to be defined to the empty string. */
112 #define TIG_HELP_CMD ""
113 #define TIG_PAGER_CMD ""
114 #define TIG_STATUS_CMD ""
116 /* Some ascii-shorthands fitted into the ncurses namespace. */
118 #define KEY_RETURN '\r'
123 char *name; /* Ref name; tag or head names are shortened. */
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int remote:1; /* Is it a remote ref? */
127 unsigned int next:1; /* For ref lists: are there more refs? */
130 static struct ref **get_refs(char *id);
139 set_from_int_map(struct int_map *map, size_t map_size,
140 int *value, const char *name, int namelen)
145 for (i = 0; i < map_size; i++)
146 if (namelen == map[i].namelen &&
147 !strncasecmp(name, map[i].name, namelen)) {
148 *value = map[i].value;
161 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
163 if (srclen > dstlen - 1)
166 strncpy(dst, src, srclen);
170 /* Shorthands for safely copying into a fixed buffer. */
172 #define string_copy(dst, src) \
173 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
175 #define string_ncopy(dst, src, srclen) \
176 string_ncopy_do(dst, sizeof(dst), src, srclen)
178 #define string_copy_rev(dst, src) \
179 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
181 #define string_add(dst, from, src) \
182 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
185 chomp_string(char *name)
189 while (isspace(*name))
192 namelen = strlen(name) - 1;
193 while (namelen > 0 && isspace(name[namelen]))
200 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
203 size_t pos = bufpos ? *bufpos : 0;
206 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
212 return pos >= bufsize ? FALSE : TRUE;
215 #define string_format(buf, fmt, args...) \
216 string_nformat(buf, sizeof(buf), NULL, fmt, args)
218 #define string_format_from(buf, from, fmt, args...) \
219 string_nformat(buf, sizeof(buf), from, fmt, args)
222 string_enum_compare(const char *str1, const char *str2, int len)
226 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
228 /* Diff-Header == DIFF_HEADER */
229 for (i = 0; i < len; i++) {
230 if (toupper(str1[i]) == toupper(str2[i]))
233 if (string_enum_sep(str1[i]) &&
234 string_enum_sep(str2[i]))
237 return str1[i] - str2[i];
245 * NOTE: The following is a slightly modified copy of the git project's shell
246 * quoting routines found in the quote.c file.
248 * Help to copy the thing properly quoted for the shell safety. any single
249 * quote is replaced with '\'', any exclamation point is replaced with '\!',
250 * and the whole thing is enclosed in a
253 * original sq_quote result
254 * name ==> name ==> 'name'
255 * a b ==> a b ==> 'a b'
256 * a'b ==> a'\''b ==> 'a'\''b'
257 * a!b ==> a'\!'b ==> 'a'\!'b'
261 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
265 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
268 while ((c = *src++)) {
269 if (c == '\'' || c == '!') {
280 if (bufsize < SIZEOF_STR)
292 /* XXX: Keep the view request first and in sync with views[]. */ \
293 REQ_GROUP("View switching") \
294 REQ_(VIEW_MAIN, "Show main view"), \
295 REQ_(VIEW_DIFF, "Show diff view"), \
296 REQ_(VIEW_LOG, "Show log view"), \
297 REQ_(VIEW_TREE, "Show tree view"), \
298 REQ_(VIEW_BLOB, "Show blob view"), \
299 REQ_(VIEW_HELP, "Show help page"), \
300 REQ_(VIEW_PAGER, "Show pager view"), \
301 REQ_(VIEW_STATUS, "Show status view"), \
303 REQ_GROUP("View manipulation") \
304 REQ_(ENTER, "Enter current line and scroll"), \
305 REQ_(NEXT, "Move to next"), \
306 REQ_(PREVIOUS, "Move to previous"), \
307 REQ_(VIEW_NEXT, "Move focus to next view"), \
308 REQ_(VIEW_CLOSE, "Close the current view"), \
309 REQ_(QUIT, "Close all views and quit"), \
311 REQ_GROUP("Cursor navigation") \
312 REQ_(MOVE_UP, "Move cursor one line up"), \
313 REQ_(MOVE_DOWN, "Move cursor one line down"), \
314 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
315 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
316 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
317 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
319 REQ_GROUP("Scrolling") \
320 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
321 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
322 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
323 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
325 REQ_GROUP("Searching") \
326 REQ_(SEARCH, "Search the view"), \
327 REQ_(SEARCH_BACK, "Search backwards in the view"), \
328 REQ_(FIND_NEXT, "Find next search match"), \
329 REQ_(FIND_PREV, "Find previous search match"), \
332 REQ_(NONE, "Do nothing"), \
333 REQ_(PROMPT, "Bring up the prompt"), \
334 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
335 REQ_(SCREEN_RESIZE, "Resize the screen"), \
336 REQ_(SHOW_VERSION, "Show version information"), \
337 REQ_(STOP_LOADING, "Stop all loading views"), \
338 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
339 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
342 /* User action requests. */
344 #define REQ_GROUP(help)
345 #define REQ_(req, help) REQ_##req
347 /* Offset all requests to avoid conflicts with ncurses getch values. */
348 REQ_OFFSET = KEY_MAX + 1,
356 struct request_info {
357 enum request request;
363 static struct request_info req_info[] = {
364 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
365 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
372 get_request(const char *name)
374 int namelen = strlen(name);
377 for (i = 0; i < ARRAY_SIZE(req_info); i++)
378 if (req_info[i].namelen == namelen &&
379 !string_enum_compare(req_info[i].name, name, namelen))
380 return req_info[i].request;
390 static const char usage[] =
391 "tig " VERSION " (" __DATE__ ")\n"
393 "Usage: tig [options]\n"
394 " or: tig [options] [--] [git log options]\n"
395 " or: tig [options] log [git log options]\n"
396 " or: tig [options] diff [git diff options]\n"
397 " or: tig [options] show [git show options]\n"
398 " or: tig [options] < [git command output]\n"
401 " -l Start up in log view\n"
402 " -d Start up in diff view\n"
403 " -S Start up in status view\n"
404 " -n[I], --line-number[=I] Show line numbers with given interval\n"
405 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
406 " -- Mark end of tig options\n"
407 " -v, --version Show version and exit\n"
408 " -h, --help Show help message and exit\n";
410 /* Option and state variables. */
411 static bool opt_line_number = FALSE;
412 static bool opt_rev_graph = FALSE;
413 static int opt_num_interval = NUMBER_INTERVAL;
414 static int opt_tab_size = TABSIZE;
415 static enum request opt_request = REQ_VIEW_MAIN;
416 static char opt_cmd[SIZEOF_STR] = "";
417 static char opt_path[SIZEOF_STR] = "";
418 static FILE *opt_pipe = NULL;
419 static char opt_encoding[20] = "UTF-8";
420 static bool opt_utf8 = TRUE;
421 static char opt_codeset[20] = "UTF-8";
422 static iconv_t opt_iconv = ICONV_NONE;
423 static char opt_search[SIZEOF_STR] = "";
424 static char opt_cdup[SIZEOF_STR] = "";
425 static char opt_git_dir[SIZEOF_STR] = "";
433 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
443 int namelen = strlen(name);
447 if (strncmp(opt, name, namelen))
450 if (opt[namelen] == '=')
451 value = opt + namelen + 1;
454 if (!short_name || opt[1] != short_name)
459 va_start(args, type);
460 if (type == OPT_INT) {
461 number = va_arg(args, int *);
463 *number = atoi(value);
470 /* Returns the index of log or diff command or -1 to exit. */
472 parse_options(int argc, char *argv[])
476 for (i = 1; i < argc; i++) {
479 if (!strcmp(opt, "log") ||
480 !strcmp(opt, "diff") ||
481 !strcmp(opt, "show")) {
482 opt_request = opt[0] == 'l'
483 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
487 if (opt[0] && opt[0] != '-')
490 if (!strcmp(opt, "-l")) {
491 opt_request = REQ_VIEW_LOG;
495 if (!strcmp(opt, "-d")) {
496 opt_request = REQ_VIEW_DIFF;
500 if (!strcmp(opt, "-S")) {
501 opt_request = REQ_VIEW_STATUS;
505 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
506 opt_line_number = TRUE;
510 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
511 opt_tab_size = MIN(opt_tab_size, TABSIZE);
515 if (check_option(opt, 'v', "version", OPT_NONE)) {
516 printf("tig version %s\n", VERSION);
520 if (check_option(opt, 'h', "help", OPT_NONE)) {
525 if (!strcmp(opt, "--")) {
530 die("unknown option '%s'\n\n%s", opt, usage);
533 if (!isatty(STDIN_FILENO)) {
534 opt_request = REQ_VIEW_PAGER;
537 } else if (i < argc) {
540 if (opt_request == REQ_VIEW_MAIN)
541 /* XXX: This is vulnerable to the user overriding
542 * options required for the main view parser. */
543 string_copy(opt_cmd, "git log --pretty=raw");
545 string_copy(opt_cmd, "git");
546 buf_size = strlen(opt_cmd);
548 while (buf_size < sizeof(opt_cmd) && i < argc) {
549 opt_cmd[buf_size++] = ' ';
550 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
553 if (buf_size >= sizeof(opt_cmd))
554 die("command too long");
556 opt_cmd[buf_size] = 0;
559 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
567 * Line-oriented content detection.
571 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
572 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
573 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
574 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
575 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
576 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
577 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
578 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
579 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
580 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
581 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
582 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
583 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
585 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
586 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
587 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
588 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
589 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
590 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
591 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
592 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
593 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
594 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
595 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
596 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
597 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
599 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
600 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
601 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
602 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
603 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
604 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
605 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
606 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
607 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
608 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
609 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
610 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
611 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
612 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
613 LINE(STAT_SECTION, "", COLOR_DEFAULT, COLOR_BLUE, A_BOLD), \
614 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
615 LINE(STAT_STAGED, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
616 LINE(STAT_UNSTAGED,"", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
620 #define LINE(type, line, fg, bg, attr) \
627 const char *name; /* Option name. */
628 int namelen; /* Size of option name. */
629 const char *line; /* The start of line to match. */
630 int linelen; /* Size of string to match. */
631 int fg, bg, attr; /* Color and text attributes for the lines. */
634 static struct line_info line_info[] = {
635 #define LINE(type, line, fg, bg, attr) \
636 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
641 static enum line_type
642 get_line_type(char *line)
644 int linelen = strlen(line);
647 for (type = 0; type < ARRAY_SIZE(line_info); type++)
648 /* Case insensitive search matches Signed-off-by lines better. */
649 if (linelen >= line_info[type].linelen &&
650 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
657 get_line_attr(enum line_type type)
659 assert(type < ARRAY_SIZE(line_info));
660 return COLOR_PAIR(type) | line_info[type].attr;
663 static struct line_info *
664 get_line_info(char *name, int namelen)
668 for (type = 0; type < ARRAY_SIZE(line_info); type++)
669 if (namelen == line_info[type].namelen &&
670 !string_enum_compare(line_info[type].name, name, namelen))
671 return &line_info[type];
679 int default_bg = COLOR_BLACK;
680 int default_fg = COLOR_WHITE;
685 if (use_default_colors() != ERR) {
690 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
691 struct line_info *info = &line_info[type];
692 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
693 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
695 init_pair(type, fg, bg);
703 unsigned int selected:1;
705 void *data; /* User data */
715 enum request request;
716 struct keybinding *next;
719 static struct keybinding default_keybindings[] = {
721 { 'm', REQ_VIEW_MAIN },
722 { 'd', REQ_VIEW_DIFF },
723 { 'l', REQ_VIEW_LOG },
724 { 't', REQ_VIEW_TREE },
725 { 'f', REQ_VIEW_BLOB },
726 { 'p', REQ_VIEW_PAGER },
727 { 'h', REQ_VIEW_HELP },
728 { 'S', REQ_VIEW_STATUS },
730 /* View manipulation */
731 { 'q', REQ_VIEW_CLOSE },
732 { KEY_TAB, REQ_VIEW_NEXT },
733 { KEY_RETURN, REQ_ENTER },
734 { KEY_UP, REQ_PREVIOUS },
735 { KEY_DOWN, REQ_NEXT },
737 /* Cursor navigation */
738 { 'k', REQ_MOVE_UP },
739 { 'j', REQ_MOVE_DOWN },
740 { KEY_HOME, REQ_MOVE_FIRST_LINE },
741 { KEY_END, REQ_MOVE_LAST_LINE },
742 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
743 { ' ', REQ_MOVE_PAGE_DOWN },
744 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
745 { 'b', REQ_MOVE_PAGE_UP },
746 { '-', REQ_MOVE_PAGE_UP },
749 { KEY_IC, REQ_SCROLL_LINE_UP },
750 { KEY_DC, REQ_SCROLL_LINE_DOWN },
751 { 'w', REQ_SCROLL_PAGE_UP },
752 { 's', REQ_SCROLL_PAGE_DOWN },
756 { '?', REQ_SEARCH_BACK },
757 { 'n', REQ_FIND_NEXT },
758 { 'N', REQ_FIND_PREV },
762 { 'z', REQ_STOP_LOADING },
763 { 'v', REQ_SHOW_VERSION },
764 { 'r', REQ_SCREEN_REDRAW },
765 { '.', REQ_TOGGLE_LINENO },
766 { 'g', REQ_TOGGLE_REV_GRAPH },
769 /* Using the ncurses SIGWINCH handler. */
770 { KEY_RESIZE, REQ_SCREEN_RESIZE },
773 #define KEYMAP_INFO \
785 #define KEYMAP_(name) KEYMAP_##name
790 static struct int_map keymap_table[] = {
791 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
796 #define set_keymap(map, name) \
797 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
799 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
802 add_keybinding(enum keymap keymap, enum request request, int key)
804 struct keybinding *keybinding;
806 keybinding = calloc(1, sizeof(*keybinding));
808 die("Failed to allocate keybinding");
810 keybinding->alias = key;
811 keybinding->request = request;
812 keybinding->next = keybindings[keymap];
813 keybindings[keymap] = keybinding;
816 /* Looks for a key binding first in the given map, then in the generic map, and
817 * lastly in the default keybindings. */
819 get_keybinding(enum keymap keymap, int key)
821 struct keybinding *kbd;
824 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
825 if (kbd->alias == key)
828 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
829 if (kbd->alias == key)
832 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
833 if (default_keybindings[i].alias == key)
834 return default_keybindings[i].request;
836 return (enum request) key;
845 static struct key key_table[] = {
846 { "Enter", KEY_RETURN },
848 { "Backspace", KEY_BACKSPACE },
850 { "Escape", KEY_ESC },
851 { "Left", KEY_LEFT },
852 { "Right", KEY_RIGHT },
854 { "Down", KEY_DOWN },
855 { "Insert", KEY_IC },
856 { "Delete", KEY_DC },
858 { "Home", KEY_HOME },
860 { "PageUp", KEY_PPAGE },
861 { "PageDown", KEY_NPAGE },
871 { "F10", KEY_F(10) },
872 { "F11", KEY_F(11) },
873 { "F12", KEY_F(12) },
877 get_key_value(const char *name)
881 for (i = 0; i < ARRAY_SIZE(key_table); i++)
882 if (!strcasecmp(key_table[i].name, name))
883 return key_table[i].value;
885 if (strlen(name) == 1 && isprint(*name))
892 get_key(enum request request)
894 static char buf[BUFSIZ];
895 static char key_char[] = "'X'";
902 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
903 struct keybinding *keybinding = &default_keybindings[i];
907 if (keybinding->request != request)
910 for (key = 0; key < ARRAY_SIZE(key_table); key++)
911 if (key_table[key].value == keybinding->alias)
912 seq = key_table[key].name;
915 keybinding->alias < 127 &&
916 isprint(keybinding->alias)) {
917 key_char[1] = (char) keybinding->alias;
924 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
925 return "Too many keybindings!";
934 * User config file handling.
937 static struct int_map color_map[] = {
938 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
950 #define set_color(color, name) \
951 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
953 static struct int_map attr_map[] = {
954 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
964 #define set_attribute(attr, name) \
965 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
967 static int config_lineno;
968 static bool config_errors;
969 static char *config_msg;
971 /* Wants: object fgcolor bgcolor [attr] */
973 option_color_command(int argc, char *argv[])
975 struct line_info *info;
977 if (argc != 3 && argc != 4) {
978 config_msg = "Wrong number of arguments given to color command";
982 info = get_line_info(argv[0], strlen(argv[0]));
984 config_msg = "Unknown color name";
988 if (set_color(&info->fg, argv[1]) == ERR ||
989 set_color(&info->bg, argv[2]) == ERR) {
990 config_msg = "Unknown color";
994 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
995 config_msg = "Unknown attribute";
1002 /* Wants: name = value */
1004 option_set_command(int argc, char *argv[])
1007 config_msg = "Wrong number of arguments given to set command";
1011 if (strcmp(argv[1], "=")) {
1012 config_msg = "No value assigned";
1016 if (!strcmp(argv[0], "show-rev-graph")) {
1017 opt_rev_graph = (!strcmp(argv[2], "1") ||
1018 !strcmp(argv[2], "true") ||
1019 !strcmp(argv[2], "yes"));
1023 if (!strcmp(argv[0], "line-number-interval")) {
1024 opt_num_interval = atoi(argv[2]);
1028 if (!strcmp(argv[0], "tab-size")) {
1029 opt_tab_size = atoi(argv[2]);
1033 if (!strcmp(argv[0], "commit-encoding")) {
1034 char *arg = argv[2];
1035 int delimiter = *arg;
1038 switch (delimiter) {
1041 for (arg++, i = 0; arg[i]; i++)
1042 if (arg[i] == delimiter) {
1047 string_ncopy(opt_encoding, arg, strlen(arg));
1052 config_msg = "Unknown variable name";
1056 /* Wants: mode request key */
1058 option_bind_command(int argc, char *argv[])
1060 enum request request;
1065 config_msg = "Wrong number of arguments given to bind command";
1069 if (set_keymap(&keymap, argv[0]) == ERR) {
1070 config_msg = "Unknown key map";
1074 key = get_key_value(argv[1]);
1076 config_msg = "Unknown key";
1080 request = get_request(argv[2]);
1081 if (request == REQ_UNKNOWN) {
1082 config_msg = "Unknown request name";
1086 add_keybinding(keymap, request, key);
1092 set_option(char *opt, char *value)
1099 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1100 argv[argc++] = value;
1107 while (isspace(*value))
1111 if (!strcmp(opt, "color"))
1112 return option_color_command(argc, argv);
1114 if (!strcmp(opt, "set"))
1115 return option_set_command(argc, argv);
1117 if (!strcmp(opt, "bind"))
1118 return option_bind_command(argc, argv);
1120 config_msg = "Unknown option command";
1125 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1130 config_msg = "Internal error";
1132 /* Check for comment markers, since read_properties() will
1133 * only ensure opt and value are split at first " \t". */
1134 optlen = strcspn(opt, "#");
1138 if (opt[optlen] != 0) {
1139 config_msg = "No option value";
1143 /* Look for comment endings in the value. */
1144 size_t len = strcspn(value, "#");
1146 if (len < valuelen) {
1148 value[valuelen] = 0;
1151 status = set_option(opt, value);
1154 if (status == ERR) {
1155 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1156 config_lineno, (int) optlen, opt, config_msg);
1157 config_errors = TRUE;
1160 /* Always keep going if errors are encountered. */
1167 char *home = getenv("HOME");
1168 char buf[SIZEOF_STR];
1172 config_errors = FALSE;
1174 if (!home || !string_format(buf, "%s/.tigrc", home))
1177 /* It's ok that the file doesn't exist. */
1178 file = fopen(buf, "r");
1182 if (read_properties(file, " \t", read_option) == ERR ||
1183 config_errors == TRUE)
1184 fprintf(stderr, "Errors while loading %s.\n", buf);
1197 /* The display array of active views and the index of the current view. */
1198 static struct view *display[2];
1199 static unsigned int current_view;
1201 /* Reading from the prompt? */
1202 static bool input_mode = FALSE;
1204 #define foreach_displayed_view(view, i) \
1205 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1207 #define displayed_views() (display[1] != NULL ? 2 : 1)
1209 /* Current head and commit ID */
1210 static char ref_blob[SIZEOF_REF] = "";
1211 static char ref_commit[SIZEOF_REF] = "HEAD";
1212 static char ref_head[SIZEOF_REF] = "HEAD";
1215 const char *name; /* View name */
1216 const char *cmd_fmt; /* Default command line format */
1217 const char *cmd_env; /* Command line set via environment */
1218 const char *id; /* Points to either of ref_{head,commit,blob} */
1220 struct view_ops *ops; /* View operations */
1222 enum keymap keymap; /* What keymap does this view have */
1224 char cmd[SIZEOF_STR]; /* Command buffer */
1225 char ref[SIZEOF_REF]; /* Hovered commit reference */
1226 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1228 int height, width; /* The width and height of the main window */
1229 WINDOW *win; /* The main window */
1230 WINDOW *title; /* The title window living below the main window */
1233 unsigned long offset; /* Offset of the window top */
1234 unsigned long lineno; /* Current line number */
1237 char grep[SIZEOF_STR]; /* Search string */
1238 regex_t *regex; /* Pre-compiled regex */
1240 /* If non-NULL, points to the view that opened this view. If this view
1241 * is closed tig will switch back to the parent view. */
1242 struct view *parent;
1245 unsigned long lines; /* Total number of lines */
1246 struct line *line; /* Line index */
1247 unsigned long line_size;/* Total number of allocated lines */
1248 unsigned int digits; /* Number of digits in the lines member. */
1256 /* What type of content being displayed. Used in the title bar. */
1258 /* Open and reads in all view content. */
1259 bool (*open)(struct view *view);
1260 /* Read one line; updates view->line. */
1261 bool (*read)(struct view *view, char *data);
1262 /* Draw one line; @lineno must be < view->height. */
1263 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1264 /* Depending on view, change display based on current line. */
1265 bool (*enter)(struct view *view, struct line *line);
1266 /* Search for regex in a line. */
1267 bool (*grep)(struct view *view, struct line *line);
1269 void (*select)(struct view *view, struct line *line);
1272 static struct view_ops pager_ops;
1273 static struct view_ops main_ops;
1274 static struct view_ops tree_ops;
1275 static struct view_ops blob_ops;
1276 static struct view_ops help_ops;
1277 static struct view_ops status_ops;
1279 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1280 { name, cmd, #env, ref, ops, map}
1282 #define VIEW_(id, name, ops, ref) \
1283 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1286 static struct view views[] = {
1287 VIEW_(MAIN, "main", &main_ops, ref_head),
1288 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1289 VIEW_(LOG, "log", &pager_ops, ref_head),
1290 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1291 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1292 VIEW_(HELP, "help", &help_ops, ""),
1293 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1294 VIEW_(STATUS, "status", &status_ops, ""),
1297 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1299 #define foreach_view(view, i) \
1300 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1302 #define view_is_displayed(view) \
1303 (view == display[0] || view == display[1])
1306 draw_view_line(struct view *view, unsigned int lineno)
1309 bool selected = (view->offset + lineno == view->lineno);
1312 assert(view_is_displayed(view));
1314 if (view->offset + lineno >= view->lines)
1317 line = &view->line[view->offset + lineno];
1320 line->selected = TRUE;
1321 view->ops->select(view, line);
1322 } else if (line->selected) {
1323 line->selected = FALSE;
1324 wmove(view->win, lineno, 0);
1325 wclrtoeol(view->win);
1328 scrollok(view->win, FALSE);
1329 draw_ok = view->ops->draw(view, line, lineno, selected);
1330 scrollok(view->win, TRUE);
1336 redraw_view_from(struct view *view, int lineno)
1338 assert(0 <= lineno && lineno < view->height);
1340 for (; lineno < view->height; lineno++) {
1341 if (!draw_view_line(view, lineno))
1345 redrawwin(view->win);
1347 wnoutrefresh(view->win);
1349 wrefresh(view->win);
1353 redraw_view(struct view *view)
1356 redraw_view_from(view, 0);
1361 update_view_title(struct view *view)
1363 char buf[SIZEOF_STR];
1364 char state[SIZEOF_STR];
1365 size_t bufpos = 0, statelen = 0;
1367 assert(view_is_displayed(view));
1369 if (view->lines || view->pipe) {
1370 unsigned int view_lines = view->offset + view->height;
1371 unsigned int lines = view->lines
1372 ? MIN(view_lines, view->lines) * 100 / view->lines
1375 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1382 time_t secs = time(NULL) - view->start_time;
1384 /* Three git seconds are a long time ... */
1386 string_format_from(state, &statelen, " %lds", secs);
1390 string_format_from(buf, &bufpos, "[%s]", view->name);
1391 if (*view->ref && bufpos < view->width) {
1392 size_t refsize = strlen(view->ref);
1393 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1395 if (minsize < view->width)
1396 refsize = view->width - minsize + 7;
1397 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1400 if (statelen && bufpos < view->width) {
1401 string_format_from(buf, &bufpos, " %s", state);
1404 if (view == display[current_view])
1405 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1407 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1409 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1410 wclrtoeol(view->title);
1411 wmove(view->title, 0, view->width - 1);
1414 wnoutrefresh(view->title);
1416 wrefresh(view->title);
1420 resize_display(void)
1423 struct view *base = display[0];
1424 struct view *view = display[1] ? display[1] : display[0];
1426 /* Setup window dimensions */
1428 getmaxyx(stdscr, base->height, base->width);
1430 /* Make room for the status window. */
1434 /* Horizontal split. */
1435 view->width = base->width;
1436 view->height = SCALE_SPLIT_VIEW(base->height);
1437 base->height -= view->height;
1439 /* Make room for the title bar. */
1443 /* Make room for the title bar. */
1448 foreach_displayed_view (view, i) {
1450 view->win = newwin(view->height, 0, offset, 0);
1452 die("Failed to create %s view", view->name);
1454 scrollok(view->win, TRUE);
1456 view->title = newwin(1, 0, offset + view->height, 0);
1458 die("Failed to create title window");
1461 wresize(view->win, view->height, view->width);
1462 mvwin(view->win, offset, 0);
1463 mvwin(view->title, offset + view->height, 0);
1466 offset += view->height + 1;
1471 redraw_display(void)
1476 foreach_displayed_view (view, i) {
1478 update_view_title(view);
1483 update_display_cursor(struct view *view)
1485 /* Move the cursor to the right-most column of the cursor line.
1487 * XXX: This could turn out to be a bit expensive, but it ensures that
1488 * the cursor does not jump around. */
1490 wmove(view->win, view->lineno - view->offset, view->width - 1);
1491 wrefresh(view->win);
1499 /* Scrolling backend */
1501 do_scroll_view(struct view *view, int lines)
1503 bool redraw_current_line = FALSE;
1505 /* The rendering expects the new offset. */
1506 view->offset += lines;
1508 assert(0 <= view->offset && view->offset < view->lines);
1511 /* Move current line into the view. */
1512 if (view->lineno < view->offset) {
1513 view->lineno = view->offset;
1514 redraw_current_line = TRUE;
1515 } else if (view->lineno >= view->offset + view->height) {
1516 view->lineno = view->offset + view->height - 1;
1517 redraw_current_line = TRUE;
1520 assert(view->offset <= view->lineno && view->lineno < view->lines);
1522 /* Redraw the whole screen if scrolling is pointless. */
1523 if (view->height < ABS(lines)) {
1527 int line = lines > 0 ? view->height - lines : 0;
1528 int end = line + ABS(lines);
1530 wscrl(view->win, lines);
1532 for (; line < end; line++) {
1533 if (!draw_view_line(view, line))
1537 if (redraw_current_line)
1538 draw_view_line(view, view->lineno - view->offset);
1541 redrawwin(view->win);
1542 wrefresh(view->win);
1546 /* Scroll frontend */
1548 scroll_view(struct view *view, enum request request)
1552 assert(view_is_displayed(view));
1555 case REQ_SCROLL_PAGE_DOWN:
1556 lines = view->height;
1557 case REQ_SCROLL_LINE_DOWN:
1558 if (view->offset + lines > view->lines)
1559 lines = view->lines - view->offset;
1561 if (lines == 0 || view->offset + view->height >= view->lines) {
1562 report("Cannot scroll beyond the last line");
1567 case REQ_SCROLL_PAGE_UP:
1568 lines = view->height;
1569 case REQ_SCROLL_LINE_UP:
1570 if (lines > view->offset)
1571 lines = view->offset;
1574 report("Cannot scroll beyond the first line");
1582 die("request %d not handled in switch", request);
1585 do_scroll_view(view, lines);
1590 move_view(struct view *view, enum request request)
1592 int scroll_steps = 0;
1596 case REQ_MOVE_FIRST_LINE:
1597 steps = -view->lineno;
1600 case REQ_MOVE_LAST_LINE:
1601 steps = view->lines - view->lineno - 1;
1604 case REQ_MOVE_PAGE_UP:
1605 steps = view->height > view->lineno
1606 ? -view->lineno : -view->height;
1609 case REQ_MOVE_PAGE_DOWN:
1610 steps = view->lineno + view->height >= view->lines
1611 ? view->lines - view->lineno - 1 : view->height;
1623 die("request %d not handled in switch", request);
1626 if (steps <= 0 && view->lineno == 0) {
1627 report("Cannot move beyond the first line");
1630 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1631 report("Cannot move beyond the last line");
1635 /* Move the current line */
1636 view->lineno += steps;
1637 assert(0 <= view->lineno && view->lineno < view->lines);
1639 /* Check whether the view needs to be scrolled */
1640 if (view->lineno < view->offset ||
1641 view->lineno >= view->offset + view->height) {
1642 scroll_steps = steps;
1643 if (steps < 0 && -steps > view->offset) {
1644 scroll_steps = -view->offset;
1646 } else if (steps > 0) {
1647 if (view->lineno == view->lines - 1 &&
1648 view->lines > view->height) {
1649 scroll_steps = view->lines - view->offset - 1;
1650 if (scroll_steps >= view->height)
1651 scroll_steps -= view->height - 1;
1656 if (!view_is_displayed(view)) {
1657 view->offset += scroll_steps;
1658 assert(0 <= view->offset && view->offset < view->lines);
1659 view->ops->select(view, &view->line[view->lineno]);
1663 /* Repaint the old "current" line if we be scrolling */
1664 if (ABS(steps) < view->height)
1665 draw_view_line(view, view->lineno - steps - view->offset);
1668 do_scroll_view(view, scroll_steps);
1672 /* Draw the current line */
1673 draw_view_line(view, view->lineno - view->offset);
1675 redrawwin(view->win);
1676 wrefresh(view->win);
1685 static void search_view(struct view *view, enum request request);
1688 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1690 assert(view_is_displayed(view));
1692 if (!view->ops->grep(view, line))
1695 if (lineno - view->offset >= view->height) {
1696 view->offset = lineno;
1697 view->lineno = lineno;
1701 unsigned long old_lineno = view->lineno - view->offset;
1703 view->lineno = lineno;
1704 draw_view_line(view, old_lineno);
1706 draw_view_line(view, view->lineno - view->offset);
1707 redrawwin(view->win);
1708 wrefresh(view->win);
1711 report("Line %ld matches '%s'", lineno + 1, view->grep);
1716 find_next(struct view *view, enum request request)
1718 unsigned long lineno = view->lineno;
1723 report("No previous search");
1725 search_view(view, request);
1735 case REQ_SEARCH_BACK:
1744 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1745 lineno += direction;
1747 /* Note, lineno is unsigned long so will wrap around in which case it
1748 * will become bigger than view->lines. */
1749 for (; lineno < view->lines; lineno += direction) {
1750 struct line *line = &view->line[lineno];
1752 if (find_next_line(view, lineno, line))
1756 report("No match found for '%s'", view->grep);
1760 search_view(struct view *view, enum request request)
1765 regfree(view->regex);
1768 view->regex = calloc(1, sizeof(*view->regex));
1773 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1774 if (regex_err != 0) {
1775 char buf[SIZEOF_STR] = "unknown error";
1777 regerror(regex_err, view->regex, buf, sizeof(buf));
1778 report("Search failed: %s", buf);
1782 string_copy(view->grep, opt_search);
1784 find_next(view, request);
1788 * Incremental updating
1792 end_update(struct view *view)
1796 set_nonblocking_input(FALSE);
1797 if (view->pipe == stdin)
1805 begin_update(struct view *view)
1811 string_copy(view->cmd, opt_cmd);
1813 /* When running random commands, initially show the
1814 * command in the title. However, it maybe later be
1815 * overwritten if a commit line is selected. */
1816 string_copy(view->ref, view->cmd);
1818 } else if (view == VIEW(REQ_VIEW_TREE)) {
1819 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1820 char path[SIZEOF_STR];
1822 if (strcmp(view->vid, view->id))
1823 opt_path[0] = path[0] = 0;
1824 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1827 if (!string_format(view->cmd, format, view->id, path))
1831 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1832 const char *id = view->id;
1834 if (!string_format(view->cmd, format, id, id, id, id, id))
1837 /* Put the current ref_* value to the view title ref
1838 * member. This is needed by the blob view. Most other
1839 * views sets it automatically after loading because the
1840 * first line is a commit line. */
1841 string_copy_rev(view->ref, view->id);
1844 /* Special case for the pager view. */
1846 view->pipe = opt_pipe;
1849 view->pipe = popen(view->cmd, "r");
1855 set_nonblocking_input(TRUE);
1860 string_copy_rev(view->vid, view->id);
1865 for (i = 0; i < view->lines; i++)
1866 if (view->line[i].data)
1867 free(view->line[i].data);
1873 view->start_time = time(NULL);
1878 static struct line *
1879 realloc_lines(struct view *view, size_t line_size)
1881 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1887 view->line_size = line_size;
1892 update_view(struct view *view)
1894 char in_buffer[BUFSIZ];
1895 char out_buffer[BUFSIZ * 2];
1897 /* The number of lines to read. If too low it will cause too much
1898 * redrawing (and possible flickering), if too high responsiveness
1900 unsigned long lines = view->height;
1901 int redraw_from = -1;
1906 /* Only redraw if lines are visible. */
1907 if (view->offset + view->height >= view->lines)
1908 redraw_from = view->lines - view->offset;
1910 /* FIXME: This is probably not perfect for backgrounded views. */
1911 if (!realloc_lines(view, view->lines + lines))
1914 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1915 size_t linelen = strlen(line);
1918 line[linelen - 1] = 0;
1920 if (opt_iconv != ICONV_NONE) {
1922 size_t inlen = linelen;
1924 char *outbuf = out_buffer;
1925 size_t outlen = sizeof(out_buffer);
1929 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1930 if (ret != (size_t) -1) {
1932 linelen = strlen(out_buffer);
1936 if (!view->ops->read(view, line))
1946 lines = view->lines;
1947 for (digits = 0; lines; digits++)
1950 /* Keep the displayed view in sync with line number scaling. */
1951 if (digits != view->digits) {
1952 view->digits = digits;
1957 if (!view_is_displayed(view))
1960 if (view == VIEW(REQ_VIEW_TREE)) {
1961 /* Clear the view and redraw everything since the tree sorting
1962 * might have rearranged things. */
1965 } else if (redraw_from >= 0) {
1966 /* If this is an incremental update, redraw the previous line
1967 * since for commits some members could have changed when
1968 * loading the main view. */
1969 if (redraw_from > 0)
1972 /* Since revision graph visualization requires knowledge
1973 * about the parent commit, it causes a further one-off
1974 * needed to be redrawn for incremental updates. */
1975 if (redraw_from > 0 && opt_rev_graph)
1978 /* Incrementally draw avoids flickering. */
1979 redraw_view_from(view, redraw_from);
1982 /* Update the title _after_ the redraw so that if the redraw picks up a
1983 * commit reference in view->ref it'll be available here. */
1984 update_view_title(view);
1987 if (ferror(view->pipe)) {
1988 report("Failed to read: %s", strerror(errno));
1991 } else if (feof(view->pipe)) {
1999 report("Allocation failure");
2002 view->ops->read(view, NULL);
2007 static struct line *
2008 add_line_data(struct view *view, void *data, enum line_type type)
2010 struct line *line = &view->line[view->lines++];
2012 memset(line, 0, sizeof(*line));
2019 static struct line *
2020 add_line_text(struct view *view, char *data, enum line_type type)
2023 data = strdup(data);
2025 return data ? add_line_data(view, data, type) : NULL;
2034 OPEN_DEFAULT = 0, /* Use default view switching. */
2035 OPEN_SPLIT = 1, /* Split current view. */
2036 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2037 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2041 open_view(struct view *prev, enum request request, enum open_flags flags)
2043 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2044 bool split = !!(flags & OPEN_SPLIT);
2045 bool reload = !!(flags & OPEN_RELOAD);
2046 struct view *view = VIEW(request);
2047 int nviews = displayed_views();
2048 struct view *base_view = display[0];
2050 if (view == prev && nviews == 1 && !reload) {
2051 report("Already in %s view", view->name);
2055 if (view->ops->open) {
2056 if (!view->ops->open(view)) {
2057 report("Failed to load %s view", view->name);
2061 } else if ((reload || strcmp(view->vid, view->id)) &&
2062 !begin_update(view)) {
2063 report("Failed to load %s view", view->name);
2072 /* Maximize the current view. */
2073 memset(display, 0, sizeof(display));
2075 display[current_view] = view;
2078 /* Resize the view when switching between split- and full-screen,
2079 * or when switching between two different full-screen views. */
2080 if (nviews != displayed_views() ||
2081 (nviews == 1 && base_view != display[0]))
2084 if (split && prev->lineno - prev->offset >= prev->height) {
2085 /* Take the title line into account. */
2086 int lines = prev->lineno - prev->offset - prev->height + 1;
2088 /* Scroll the view that was split if the current line is
2089 * outside the new limited view. */
2090 do_scroll_view(prev, lines);
2093 if (prev && view != prev) {
2094 if (split && !backgrounded) {
2095 /* "Blur" the previous view. */
2096 update_view_title(prev);
2099 view->parent = prev;
2102 if (view->pipe && view->lines == 0) {
2103 /* Clear the old view and let the incremental updating refill
2112 /* If the view is backgrounded the above calls to report()
2113 * won't redraw the view title. */
2115 update_view_title(view);
2120 * User request switch noodle
2124 view_driver(struct view *view, enum request request)
2131 case REQ_MOVE_PAGE_UP:
2132 case REQ_MOVE_PAGE_DOWN:
2133 case REQ_MOVE_FIRST_LINE:
2134 case REQ_MOVE_LAST_LINE:
2135 move_view(view, request);
2138 case REQ_SCROLL_LINE_DOWN:
2139 case REQ_SCROLL_LINE_UP:
2140 case REQ_SCROLL_PAGE_DOWN:
2141 case REQ_SCROLL_PAGE_UP:
2142 scroll_view(view, request);
2147 report("No file chosen, press %s to open tree view",
2148 get_key(REQ_VIEW_TREE));
2151 open_view(view, request, OPEN_DEFAULT);
2154 case REQ_VIEW_PAGER:
2155 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2156 report("No pager content, press %s to run command from prompt",
2157 get_key(REQ_PROMPT));
2160 open_view(view, request, OPEN_DEFAULT);
2168 case REQ_VIEW_STATUS:
2169 open_view(view, request, OPEN_DEFAULT);
2174 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2176 if ((view == VIEW(REQ_VIEW_DIFF) &&
2177 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2178 (view == VIEW(REQ_VIEW_BLOB) &&
2179 view->parent == VIEW(REQ_VIEW_TREE))) {
2182 view = view->parent;
2183 line = view->lineno;
2184 move_view(view, request);
2185 if (view_is_displayed(view))
2186 update_view_title(view);
2187 if (line == view->lineno)
2190 move_view(view, request);
2197 report("Nothing to enter");
2200 return view->ops->enter(view, &view->line[view->lineno]);
2204 int nviews = displayed_views();
2205 int next_view = (current_view + 1) % nviews;
2207 if (next_view == current_view) {
2208 report("Only one view is displayed");
2212 current_view = next_view;
2213 /* Blur out the title of the previous view. */
2214 update_view_title(view);
2218 case REQ_TOGGLE_LINENO:
2219 opt_line_number = !opt_line_number;
2223 case REQ_TOGGLE_REV_GRAPH:
2224 opt_rev_graph = !opt_rev_graph;
2229 /* Always reload^Wrerun commands from the prompt. */
2230 open_view(view, opt_request, OPEN_RELOAD);
2234 case REQ_SEARCH_BACK:
2235 search_view(view, request);
2240 find_next(view, request);
2243 case REQ_STOP_LOADING:
2244 for (i = 0; i < ARRAY_SIZE(views); i++) {
2247 report("Stopped loading the %s view", view->name),
2252 case REQ_SHOW_VERSION:
2253 report("tig-%s (built %s)", VERSION, __DATE__);
2256 case REQ_SCREEN_RESIZE:
2259 case REQ_SCREEN_REDRAW:
2267 case REQ_VIEW_CLOSE:
2268 /* XXX: Mark closed views by letting view->parent point to the
2269 * view itself. Parents to closed view should never be
2272 view->parent->parent != view->parent) {
2273 memset(display, 0, sizeof(display));
2275 display[current_view] = view->parent;
2276 view->parent = view;
2286 /* An unknown key will show most commonly used commands. */
2287 report("Unknown key, press 'h' for help");
2300 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2302 char *text = line->data;
2303 enum line_type type = line->type;
2304 int textlen = strlen(text);
2307 wmove(view->win, lineno, 0);
2311 wchgat(view->win, -1, 0, type, NULL);
2314 attr = get_line_attr(type);
2315 wattrset(view->win, attr);
2317 if (opt_line_number || opt_tab_size < TABSIZE) {
2318 static char spaces[] = " ";
2319 int col_offset = 0, col = 0;
2321 if (opt_line_number) {
2322 unsigned long real_lineno = view->offset + lineno + 1;
2324 if (real_lineno == 1 ||
2325 (real_lineno % opt_num_interval) == 0) {
2326 wprintw(view->win, "%.*d", view->digits, real_lineno);
2329 waddnstr(view->win, spaces,
2330 MIN(view->digits, STRING_SIZE(spaces)));
2332 waddstr(view->win, ": ");
2333 col_offset = view->digits + 2;
2336 while (text && col_offset + col < view->width) {
2337 int cols_max = view->width - col_offset - col;
2341 if (*text == '\t') {
2343 assert(sizeof(spaces) > TABSIZE);
2345 cols = opt_tab_size - (col % opt_tab_size);
2348 text = strchr(text, '\t');
2349 cols = line ? text - pos : strlen(pos);
2352 waddnstr(view->win, pos, MIN(cols, cols_max));
2357 int col = 0, pos = 0;
2359 for (; pos < textlen && col < view->width; pos++, col++)
2360 if (text[pos] == '\t')
2361 col += TABSIZE - (col % TABSIZE) - 1;
2363 waddnstr(view->win, text, pos);
2370 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2372 char refbuf[SIZEOF_STR];
2376 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2379 pipe = popen(refbuf, "r");
2383 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2384 ref = chomp_string(ref);
2390 /* This is the only fatal call, since it can "corrupt" the buffer. */
2391 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2398 add_pager_refs(struct view *view, struct line *line)
2400 char buf[SIZEOF_STR];
2401 char *commit_id = line->data + STRING_SIZE("commit ");
2403 size_t bufpos = 0, refpos = 0;
2404 const char *sep = "Refs: ";
2405 bool is_tag = FALSE;
2407 assert(line->type == LINE_COMMIT);
2409 refs = get_refs(commit_id);
2411 if (view == VIEW(REQ_VIEW_DIFF))
2412 goto try_add_describe_ref;
2417 struct ref *ref = refs[refpos];
2418 char *fmt = ref->tag ? "%s[%s]" :
2419 ref->remote ? "%s<%s>" : "%s%s";
2421 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2426 } while (refs[refpos++]->next);
2428 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2429 try_add_describe_ref:
2430 /* Add <tag>-g<commit_id> "fake" reference. */
2431 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2438 if (!realloc_lines(view, view->line_size + 1))
2441 add_line_text(view, buf, LINE_PP_REFS);
2445 pager_read(struct view *view, char *data)
2452 line = add_line_text(view, data, get_line_type(data));
2456 if (line->type == LINE_COMMIT &&
2457 (view == VIEW(REQ_VIEW_DIFF) ||
2458 view == VIEW(REQ_VIEW_LOG)))
2459 add_pager_refs(view, line);
2465 pager_enter(struct view *view, struct line *line)
2469 if (line->type == LINE_COMMIT &&
2470 (view == VIEW(REQ_VIEW_LOG) ||
2471 view == VIEW(REQ_VIEW_PAGER))) {
2472 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2476 /* Always scroll the view even if it was split. That way
2477 * you can use Enter to scroll through the log view and
2478 * split open each commit diff. */
2479 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2481 /* FIXME: A minor workaround. Scrolling the view will call report("")
2482 * but if we are scrolling a non-current view this won't properly
2483 * update the view title. */
2485 update_view_title(view);
2491 pager_grep(struct view *view, struct line *line)
2494 char *text = line->data;
2499 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2506 pager_select(struct view *view, struct line *line)
2508 if (line->type == LINE_COMMIT) {
2509 char *text = line->data + STRING_SIZE("commit ");
2511 if (view != VIEW(REQ_VIEW_PAGER))
2512 string_copy_rev(view->ref, text);
2513 string_copy_rev(ref_commit, text);
2517 static struct view_ops pager_ops = {
2533 help_open(struct view *view)
2536 int lines = ARRAY_SIZE(req_info) + 2;
2539 if (view->lines > 0)
2542 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2543 if (!req_info[i].request)
2546 view->line = calloc(lines, sizeof(*view->line));
2550 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2552 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2555 if (!req_info[i].request) {
2556 add_line_text(view, "", LINE_DEFAULT);
2557 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2561 key = get_key(req_info[i].request);
2562 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2565 add_line_text(view, buf, LINE_DEFAULT);
2571 static struct view_ops help_ops = {
2586 /* Parse output from git-ls-tree(1):
2588 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2589 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2590 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2591 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2594 #define SIZEOF_TREE_ATTR \
2595 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2597 #define TREE_UP_FORMAT "040000 tree %s\t.."
2600 tree_compare_entry(enum line_type type1, char *name1,
2601 enum line_type type2, char *name2)
2603 if (type1 != type2) {
2604 if (type1 == LINE_TREE_DIR)
2609 return strcmp(name1, name2);
2613 tree_read(struct view *view, char *text)
2615 size_t textlen = text ? strlen(text) : 0;
2616 char buf[SIZEOF_STR];
2618 enum line_type type;
2619 bool first_read = view->lines == 0;
2621 if (textlen <= SIZEOF_TREE_ATTR)
2624 type = text[STRING_SIZE("100644 ")] == 't'
2625 ? LINE_TREE_DIR : LINE_TREE_FILE;
2628 /* Add path info line */
2629 if (!string_format(buf, "Directory path /%s", opt_path) ||
2630 !realloc_lines(view, view->line_size + 1) ||
2631 !add_line_text(view, buf, LINE_DEFAULT))
2634 /* Insert "link" to parent directory. */
2636 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2637 !realloc_lines(view, view->line_size + 1) ||
2638 !add_line_text(view, buf, LINE_TREE_DIR))
2643 /* Strip the path part ... */
2645 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2646 size_t striplen = strlen(opt_path);
2647 char *path = text + SIZEOF_TREE_ATTR;
2649 if (pathlen > striplen)
2650 memmove(path, path + striplen,
2651 pathlen - striplen + 1);
2654 /* Skip "Directory ..." and ".." line. */
2655 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2656 struct line *line = &view->line[pos];
2657 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2658 char *path2 = text + SIZEOF_TREE_ATTR;
2659 int cmp = tree_compare_entry(line->type, path1, type, path2);
2664 text = strdup(text);
2668 if (view->lines > pos)
2669 memmove(&view->line[pos + 1], &view->line[pos],
2670 (view->lines - pos) * sizeof(*line));
2672 line = &view->line[pos];
2679 if (!add_line_text(view, text, type))
2682 /* Move the current line to the first tree entry. */
2690 tree_enter(struct view *view, struct line *line)
2692 enum open_flags flags;
2693 enum request request;
2695 switch (line->type) {
2697 /* Depending on whether it is a subdir or parent (updir?) link
2698 * mangle the path buffer. */
2699 if (line == &view->line[1] && *opt_path) {
2700 size_t path_len = strlen(opt_path);
2701 char *dirsep = opt_path + path_len - 1;
2703 while (dirsep > opt_path && dirsep[-1] != '/')
2709 size_t pathlen = strlen(opt_path);
2710 size_t origlen = pathlen;
2711 char *data = line->data;
2712 char *basename = data + SIZEOF_TREE_ATTR;
2714 if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2715 opt_path[origlen] = 0;
2720 /* Trees and subtrees share the same ID, so they are not not
2721 * unique like blobs. */
2722 flags = OPEN_RELOAD;
2723 request = REQ_VIEW_TREE;
2726 case LINE_TREE_FILE:
2727 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2728 request = REQ_VIEW_BLOB;
2735 open_view(view, request, flags);
2741 tree_select(struct view *view, struct line *line)
2743 char *text = line->data + STRING_SIZE("100644 blob ");
2745 if (line->type == LINE_TREE_FILE) {
2746 string_copy_rev(ref_blob, text);
2748 } else if (line->type != LINE_TREE_DIR) {
2752 string_copy_rev(view->ref, text);
2755 static struct view_ops tree_ops = {
2766 blob_read(struct view *view, char *line)
2768 return add_line_text(view, line, LINE_DEFAULT);
2771 static struct view_ops blob_ops = {
2790 char rev[SIZEOF_REV];
2794 char rev[SIZEOF_REV];
2796 char name[SIZEOF_STR];
2799 /* Get fields from the diff line:
2800 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2803 status_get_diff(struct status *file, char *buf, size_t bufsize)
2805 char *old_mode = buf + 1;
2806 char *new_mode = buf + 8;
2807 char *old_rev = buf + 15;
2808 char *new_rev = buf + 56;
2809 char *status = buf + 97;
2811 if (bufsize != 99 ||
2812 old_mode[-1] != ':' ||
2813 new_mode[-1] != ' ' ||
2814 old_rev[-1] != ' ' ||
2815 new_rev[-1] != ' ' ||
2819 file->status = *status;
2821 string_copy_rev(file->old.rev, old_rev);
2822 string_copy_rev(file->new.rev, new_rev);
2824 file->old.mode = strtoul(old_mode, NULL, 8);
2825 file->new.mode = strtoul(new_mode, NULL, 8);
2833 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2835 struct status *file = NULL;
2836 char buf[SIZEOF_STR * 4];
2840 pipe = popen(cmd, "r");
2844 add_line_data(view, NULL, type);
2846 while (!feof(pipe) && !ferror(pipe)) {
2850 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2853 bufsize += readsize;
2855 /* Process while we have NUL chars. */
2856 while ((sep = memchr(buf, 0, bufsize))) {
2857 size_t sepsize = sep - buf + 1;
2860 if (!realloc_lines(view, view->line_size + 1))
2863 file = calloc(1, sizeof(*file));
2867 add_line_data(view, file, type);
2870 /* Parse diff info part. */
2874 } else if (!file->status) {
2875 if (!status_get_diff(file, buf, sepsize))
2879 memmove(buf, sep + 1, bufsize);
2881 sep = memchr(buf, 0, bufsize);
2884 sepsize = sep - buf + 1;
2887 /* git-ls-files just delivers a NUL separated
2888 * list of file names similar to the second half
2889 * of the git-diff-* output. */
2890 string_ncopy(file->name, buf, sepsize);
2892 memmove(buf, sep + 1, bufsize);
2903 if (!view->line[view->lines - 1].data)
2904 add_line_data(view, NULL, LINE_STAT_NONE);
2910 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
2911 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
2912 #define STATUS_LIST_OTHER_CMD \
2913 "git ls-files -z --others --exclude-per-directory=.gitignore"
2915 /* First parse staged info using git-diff-index(1), then parse unstaged
2916 * info using git-diff-files(1), and finally untracked files using
2917 * git-ls-files(1). */
2919 status_open(struct view *view)
2921 struct stat statbuf;
2922 char exclude[SIZEOF_STR];
2923 char cmd[SIZEOF_STR];
2926 for (i = 0; i < view->lines; i++)
2927 free(view->line[i].data);
2929 view->lines = view->line_size = 0;
2932 if (!realloc_lines(view, view->line_size + 6))
2935 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
2938 string_copy(cmd, STATUS_LIST_OTHER_CMD);
2940 if (stat(exclude, &statbuf) >= 0) {
2941 size_t cmdsize = strlen(cmd);
2943 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
2944 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
2948 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
2949 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
2950 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
2957 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2959 struct status *status = line->data;
2961 wmove(view->win, lineno, 0);
2964 wattrset(view->win, get_line_attr(LINE_CURSOR));
2965 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
2967 } else if (!status && line->type != LINE_STAT_NONE) {
2968 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
2969 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
2972 wattrset(view->win, get_line_attr(line->type));
2978 switch (line->type) {
2979 case LINE_STAT_STAGED:
2980 text = "Changes to be committed:";
2983 case LINE_STAT_UNSTAGED:
2984 text = "Changed but not updated:";
2987 case LINE_STAT_UNTRACKED:
2988 text = "Untracked files:";
2991 case LINE_STAT_NONE:
2992 text = " (no files)";
2999 waddstr(view->win, text);
3003 waddch(view->win, status->status);
3005 wattrset(view->win, A_NORMAL);
3006 wmove(view->win, lineno, 4);
3007 waddstr(view->win, status->name);
3013 status_enter(struct view *view, struct line *line)
3015 struct status *status = line->data;
3016 char cmd[SIZEOF_STR];
3017 char buf[SIZEOF_STR];
3027 line->type != LINE_STAT_UNTRACKED &&
3028 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3031 switch (line->type) {
3032 case LINE_STAT_STAGED:
3033 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3039 string_add(cmd, cmdsize, "git update-index -z --index-info");
3042 case LINE_STAT_UNSTAGED:
3043 case LINE_STAT_UNTRACKED:
3044 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3047 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3054 pipe = popen(cmd, "w");
3058 while (!ferror(pipe) && written < bufsize) {
3059 written += fwrite(buf + written, 1, bufsize - written, pipe);
3064 if (written != bufsize)
3067 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3072 status_select(struct view *view, struct line *line)
3076 switch (line->type) {
3077 case LINE_STAT_STAGED:
3078 text = "Press Enter to unstage file for commit";
3081 case LINE_STAT_UNSTAGED:
3082 text = "Press Enter to stage file for commit ";
3085 case LINE_STAT_UNTRACKED:
3086 text = "Press Enter to stage file for addition";
3089 case LINE_STAT_NONE:
3096 string_ncopy(view->ref, text, strlen(text));
3100 status_grep(struct view *view, struct line *line)
3102 struct status *status = line->data;
3103 enum { S_STATUS, S_NAME, S_END } state;
3110 for (state = S_STATUS; state < S_END; state++) {
3114 case S_NAME: text = status->name; break;
3116 buf[0] = status->status;
3124 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3131 static struct view_ops status_ops = {
3147 char id[SIZEOF_REV]; /* SHA1 ID. */
3148 char title[128]; /* First line of the commit message. */
3149 char author[75]; /* Author of the commit. */
3150 struct tm time; /* Date from the author ident. */
3151 struct ref **refs; /* Repository references. */
3152 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3153 size_t graph_size; /* The width of the graph array. */
3156 /* Size of rev graph with no "padding" columns */
3157 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3160 struct rev_graph *prev, *next, *parents;
3161 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3163 struct commit *commit;
3167 /* Parents of the commit being visualized. */
3168 static struct rev_graph graph_parents[4];
3170 /* The current stack of revisions on the graph. */
3171 static struct rev_graph graph_stacks[4] = {
3172 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3173 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3174 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3175 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3179 graph_parent_is_merge(struct rev_graph *graph)
3181 return graph->parents->size > 1;
3185 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3187 struct commit *commit = graph->commit;
3189 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3190 commit->graph[commit->graph_size++] = symbol;
3194 done_rev_graph(struct rev_graph *graph)
3196 if (graph_parent_is_merge(graph) &&
3197 graph->pos < graph->size - 1 &&
3198 graph->next->size == graph->size + graph->parents->size - 1) {
3199 size_t i = graph->pos + graph->parents->size - 1;
3201 graph->commit->graph_size = i * 2;
3202 while (i < graph->next->size - 1) {
3203 append_to_rev_graph(graph, ' ');
3204 append_to_rev_graph(graph, '\\');
3209 graph->size = graph->pos = 0;
3210 graph->commit = NULL;
3211 memset(graph->parents, 0, sizeof(*graph->parents));
3215 push_rev_graph(struct rev_graph *graph, char *parent)
3219 /* "Collapse" duplicate parents lines.
3221 * FIXME: This needs to also update update the drawn graph but
3222 * for now it just serves as a method for pruning graph lines. */
3223 for (i = 0; i < graph->size; i++)
3224 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3227 if (graph->size < SIZEOF_REVITEMS) {
3228 string_copy_rev(graph->rev[graph->size++], parent);
3233 get_rev_graph_symbol(struct rev_graph *graph)
3237 if (graph->parents->size == 0)
3238 symbol = REVGRAPH_INIT;
3239 else if (graph_parent_is_merge(graph))
3240 symbol = REVGRAPH_MERGE;
3241 else if (graph->pos >= graph->size)
3242 symbol = REVGRAPH_BRANCH;
3244 symbol = REVGRAPH_COMMIT;
3250 draw_rev_graph(struct rev_graph *graph)
3253 chtype separator, line;
3255 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3256 static struct rev_filler fillers[] = {
3257 { ' ', REVGRAPH_LINE },
3262 chtype symbol = get_rev_graph_symbol(graph);
3263 struct rev_filler *filler;
3266 filler = &fillers[DEFAULT];
3268 for (i = 0; i < graph->pos; i++) {
3269 append_to_rev_graph(graph, filler->line);
3270 if (graph_parent_is_merge(graph->prev) &&
3271 graph->prev->pos == i)
3272 filler = &fillers[RSHARP];
3274 append_to_rev_graph(graph, filler->separator);
3277 /* Place the symbol for this revision. */
3278 append_to_rev_graph(graph, symbol);
3280 if (graph->prev->size > graph->size)
3281 filler = &fillers[RDIAG];
3283 filler = &fillers[DEFAULT];
3287 for (; i < graph->size; i++) {
3288 append_to_rev_graph(graph, filler->separator);
3289 append_to_rev_graph(graph, filler->line);
3290 if (graph_parent_is_merge(graph->prev) &&
3291 i < graph->prev->pos + graph->parents->size)
3292 filler = &fillers[RSHARP];
3293 if (graph->prev->size > graph->size)
3294 filler = &fillers[LDIAG];
3297 if (graph->prev->size > graph->size) {
3298 append_to_rev_graph(graph, filler->separator);
3299 if (filler->line != ' ')
3300 append_to_rev_graph(graph, filler->line);
3304 /* Prepare the next rev graph */
3306 prepare_rev_graph(struct rev_graph *graph)
3310 /* First, traverse all lines of revisions up to the active one. */
3311 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3312 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3315 push_rev_graph(graph->next, graph->rev[graph->pos]);
3318 /* Interleave the new revision parent(s). */
3319 for (i = 0; i < graph->parents->size; i++)
3320 push_rev_graph(graph->next, graph->parents->rev[i]);
3322 /* Lastly, put any remaining revisions. */
3323 for (i = graph->pos + 1; i < graph->size; i++)
3324 push_rev_graph(graph->next, graph->rev[i]);
3328 update_rev_graph(struct rev_graph *graph)
3330 /* If this is the finalizing update ... */
3332 prepare_rev_graph(graph);
3334 /* Graph visualization needs a one rev look-ahead,
3335 * so the first update doesn't visualize anything. */
3336 if (!graph->prev->commit)
3339 draw_rev_graph(graph->prev);
3340 done_rev_graph(graph->prev->prev);
3349 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3351 char buf[DATE_COLS + 1];
3352 struct commit *commit = line->data;
3353 enum line_type type;
3359 if (!*commit->author)
3362 wmove(view->win, lineno, col);
3366 wattrset(view->win, get_line_attr(type));
3367 wchgat(view->win, -1, 0, type, NULL);
3370 type = LINE_MAIN_COMMIT;
3371 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3374 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3375 waddnstr(view->win, buf, timelen);
3376 waddstr(view->win, " ");
3379 wmove(view->win, lineno, col);
3380 if (type != LINE_CURSOR)
3381 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3384 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3386 authorlen = strlen(commit->author);
3387 if (authorlen > AUTHOR_COLS - 2) {
3388 authorlen = AUTHOR_COLS - 2;
3394 waddnstr(view->win, commit->author, authorlen);
3395 if (type != LINE_CURSOR)
3396 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3397 waddch(view->win, '~');
3399 waddstr(view->win, commit->author);
3403 if (type != LINE_CURSOR)
3404 wattrset(view->win, A_NORMAL);
3406 if (opt_rev_graph && commit->graph_size) {
3409 wmove(view->win, lineno, col);
3410 /* Using waddch() instead of waddnstr() ensures that
3411 * they'll be rendered correctly for the cursor line. */
3412 for (i = 0; i < commit->graph_size; i++)
3413 waddch(view->win, commit->graph[i]);
3415 waddch(view->win, ' ');
3416 col += commit->graph_size + 1;
3419 wmove(view->win, lineno, col);
3425 if (type == LINE_CURSOR)
3427 else if (commit->refs[i]->tag)
3428 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3429 else if (commit->refs[i]->remote)
3430 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3432 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3433 waddstr(view->win, "[");
3434 waddstr(view->win, commit->refs[i]->name);
3435 waddstr(view->win, "]");
3436 if (type != LINE_CURSOR)
3437 wattrset(view->win, A_NORMAL);
3438 waddstr(view->win, " ");
3439 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3440 } while (commit->refs[i++]->next);
3443 if (type != LINE_CURSOR)
3444 wattrset(view->win, get_line_attr(type));
3447 int titlelen = strlen(commit->title);
3449 if (col + titlelen > view->width)
3450 titlelen = view->width - col;
3452 waddnstr(view->win, commit->title, titlelen);
3458 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3460 main_read(struct view *view, char *line)
3462 static struct rev_graph *graph = graph_stacks;
3463 enum line_type type;
3464 struct commit *commit;
3467 update_rev_graph(graph);
3471 type = get_line_type(line);
3472 if (type == LINE_COMMIT) {
3473 commit = calloc(1, sizeof(struct commit));
3477 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3478 commit->refs = get_refs(commit->id);
3479 graph->commit = commit;
3480 add_line_data(view, commit, LINE_MAIN_COMMIT);
3486 commit = view->line[view->lines - 1].data;
3490 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3495 /* Parse author lines where the name may be empty:
3496 * author <email@address.tld> 1138474660 +0100
3498 char *ident = line + STRING_SIZE("author ");
3499 char *nameend = strchr(ident, '<');
3500 char *emailend = strchr(ident, '>');
3502 if (!nameend || !emailend)
3505 update_rev_graph(graph);
3506 graph = graph->next;
3508 *nameend = *emailend = 0;
3509 ident = chomp_string(ident);
3511 ident = chomp_string(nameend + 1);
3516 string_ncopy(commit->author, ident, strlen(ident));
3518 /* Parse epoch and timezone */
3519 if (emailend[1] == ' ') {
3520 char *secs = emailend + 2;
3521 char *zone = strchr(secs, ' ');
3522 time_t time = (time_t) atol(secs);
3524 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3528 tz = ('0' - zone[1]) * 60 * 60 * 10;
3529 tz += ('0' - zone[2]) * 60 * 60;
3530 tz += ('0' - zone[3]) * 60;
3531 tz += ('0' - zone[4]) * 60;
3539 gmtime_r(&time, &commit->time);
3544 /* Fill in the commit title if it has not already been set. */
3545 if (commit->title[0])
3548 /* Require titles to start with a non-space character at the
3549 * offset used by git log. */
3550 if (strncmp(line, " ", 4))
3553 /* Well, if the title starts with a whitespace character,
3554 * try to be forgiving. Otherwise we end up with no title. */
3555 while (isspace(*line))
3559 /* FIXME: More graceful handling of titles; append "..." to
3560 * shortened titles, etc. */
3562 string_ncopy(commit->title, line, strlen(line));
3569 main_enter(struct view *view, struct line *line)
3571 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3573 open_view(view, REQ_VIEW_DIFF, flags);
3578 main_grep(struct view *view, struct line *line)
3580 struct commit *commit = line->data;
3581 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3582 char buf[DATE_COLS + 1];
3585 for (state = S_TITLE; state < S_END; state++) {
3589 case S_TITLE: text = commit->title; break;
3590 case S_AUTHOR: text = commit->author; break;
3592 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3601 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3609 main_select(struct view *view, struct line *line)
3611 struct commit *commit = line->data;
3613 string_copy_rev(view->ref, commit->id);
3614 string_copy_rev(ref_commit, view->ref);
3617 static struct view_ops main_ops = {
3629 * Unicode / UTF-8 handling
3631 * NOTE: Much of the following code for dealing with unicode is derived from
3632 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3633 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3636 /* I've (over)annotated a lot of code snippets because I am not entirely
3637 * confident that the approach taken by this small UTF-8 interface is correct.
3641 unicode_width(unsigned long c)
3644 (c <= 0x115f /* Hangul Jamo */
3647 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
3649 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
3650 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
3651 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
3652 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
3653 || (c >= 0xffe0 && c <= 0xffe6)
3654 || (c >= 0x20000 && c <= 0x2fffd)
3655 || (c >= 0x30000 && c <= 0x3fffd)))
3661 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3662 * Illegal bytes are set one. */
3663 static const unsigned char utf8_bytes[256] = {
3664 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,
3665 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,
3666 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,
3667 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,
3668 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,
3669 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,
3670 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,
3671 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,
3674 /* Decode UTF-8 multi-byte representation into a unicode character. */
3675 static inline unsigned long
3676 utf8_to_unicode(const char *string, size_t length)
3678 unsigned long unicode;
3682 unicode = string[0];
3685 unicode = (string[0] & 0x1f) << 6;
3686 unicode += (string[1] & 0x3f);
3689 unicode = (string[0] & 0x0f) << 12;
3690 unicode += ((string[1] & 0x3f) << 6);
3691 unicode += (string[2] & 0x3f);
3694 unicode = (string[0] & 0x0f) << 18;
3695 unicode += ((string[1] & 0x3f) << 12);
3696 unicode += ((string[2] & 0x3f) << 6);
3697 unicode += (string[3] & 0x3f);
3700 unicode = (string[0] & 0x0f) << 24;
3701 unicode += ((string[1] & 0x3f) << 18);
3702 unicode += ((string[2] & 0x3f) << 12);
3703 unicode += ((string[3] & 0x3f) << 6);
3704 unicode += (string[4] & 0x3f);
3707 unicode = (string[0] & 0x01) << 30;
3708 unicode += ((string[1] & 0x3f) << 24);
3709 unicode += ((string[2] & 0x3f) << 18);
3710 unicode += ((string[3] & 0x3f) << 12);
3711 unicode += ((string[4] & 0x3f) << 6);
3712 unicode += (string[5] & 0x3f);
3715 die("Invalid unicode length");
3718 /* Invalid characters could return the special 0xfffd value but NUL
3719 * should be just as good. */
3720 return unicode > 0xffff ? 0 : unicode;
3723 /* Calculates how much of string can be shown within the given maximum width
3724 * and sets trimmed parameter to non-zero value if all of string could not be
3727 * Additionally, adds to coloffset how many many columns to move to align with
3728 * the expected position. Takes into account how multi-byte and double-width
3729 * characters will effect the cursor position.
3731 * Returns the number of bytes to output from string to satisfy max_width. */
3733 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3735 const char *start = string;
3736 const char *end = strchr(string, '\0');
3742 while (string < end) {
3743 int c = *(unsigned char *) string;
3744 unsigned char bytes = utf8_bytes[c];
3746 unsigned long unicode;
3748 if (string + bytes > end)
3751 /* Change representation to figure out whether
3752 * it is a single- or double-width character. */
3754 unicode = utf8_to_unicode(string, bytes);
3755 /* FIXME: Graceful handling of invalid unicode character. */
3759 ucwidth = unicode_width(unicode);
3761 if (width > max_width) {
3766 /* The column offset collects the differences between the
3767 * number of bytes encoding a character and the number of
3768 * columns will be used for rendering said character.
3770 * So if some character A is encoded in 2 bytes, but will be
3771 * represented on the screen using only 1 byte this will and up
3772 * adding 1 to the multi-byte column offset.
3774 * Assumes that no double-width character can be encoding in
3775 * less than two bytes. */
3776 if (bytes > ucwidth)
3777 mbwidth += bytes - ucwidth;
3782 *coloffset += mbwidth;
3784 return string - start;
3792 /* Whether or not the curses interface has been initialized. */
3793 static bool cursed = FALSE;
3795 /* The status window is used for polling keystrokes. */
3796 static WINDOW *status_win;
3798 static bool status_empty = TRUE;
3800 /* Update status and title window. */
3802 report(const char *msg, ...)
3804 struct view *view = display[current_view];
3809 if (!status_empty || *msg) {
3812 va_start(args, msg);
3814 wmove(status_win, 0, 0);
3816 vwprintw(status_win, msg, args);
3817 status_empty = FALSE;
3819 status_empty = TRUE;
3821 wclrtoeol(status_win);
3822 wrefresh(status_win);
3827 update_view_title(view);
3828 update_display_cursor(view);
3831 /* Controls when nodelay should be in effect when polling user input. */
3833 set_nonblocking_input(bool loading)
3835 static unsigned int loading_views;
3837 if ((loading == FALSE && loading_views-- == 1) ||
3838 (loading == TRUE && loading_views++ == 0))
3839 nodelay(status_win, loading);
3847 /* Initialize the curses library */
3848 if (isatty(STDIN_FILENO)) {
3849 cursed = !!initscr();
3851 /* Leave stdin and stdout alone when acting as a pager. */
3852 FILE *io = fopen("/dev/tty", "r+");
3855 die("Failed to open /dev/tty");
3856 cursed = !!newterm(NULL, io, io);
3860 die("Failed to initialize curses");
3862 nonl(); /* Tell curses not to do NL->CR/NL on output */
3863 cbreak(); /* Take input chars one at a time, no wait for \n */
3864 noecho(); /* Don't echo input */
3865 leaveok(stdscr, TRUE);
3870 getmaxyx(stdscr, y, x);
3871 status_win = newwin(1, 0, y - 1, 0);
3873 die("Failed to create status window");
3875 /* Enable keyboard mapping */
3876 keypad(status_win, TRUE);
3877 wbkgdset(status_win, get_line_attr(LINE_STATUS));
3881 read_prompt(const char *prompt)
3883 enum { READING, STOP, CANCEL } status = READING;
3884 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3887 while (status == READING) {
3893 foreach_view (view, i)
3898 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3899 wclrtoeol(status_win);
3901 /* Refresh, accept single keystroke of input */
3902 key = wgetch(status_win);
3907 status = pos ? STOP : CANCEL;
3925 if (pos >= sizeof(buf)) {
3926 report("Input string too long");
3931 buf[pos++] = (char) key;
3935 /* Clear the status window */
3936 status_empty = FALSE;
3939 if (status == CANCEL)
3948 * Repository references
3951 static struct ref *refs;
3952 static size_t refs_size;
3954 /* Id <-> ref store */
3955 static struct ref ***id_refs;
3956 static size_t id_refs_size;
3958 static struct ref **
3961 struct ref ***tmp_id_refs;
3962 struct ref **ref_list = NULL;
3963 size_t ref_list_size = 0;
3966 for (i = 0; i < id_refs_size; i++)
3967 if (!strcmp(id, id_refs[i][0]->id))
3970 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3974 id_refs = tmp_id_refs;
3976 for (i = 0; i < refs_size; i++) {
3979 if (strcmp(id, refs[i].id))
3982 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3990 if (ref_list_size > 0)
3991 ref_list[ref_list_size - 1]->next = 1;
3992 ref_list[ref_list_size] = &refs[i];
3994 /* XXX: The properties of the commit chains ensures that we can
3995 * safely modify the shared ref. The repo references will
3996 * always be similar for the same id. */
3997 ref_list[ref_list_size]->next = 0;
4002 id_refs[id_refs_size++] = ref_list;
4008 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4012 bool remote = FALSE;
4014 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4015 /* Commits referenced by tags has "^{}" appended. */
4016 if (name[namelen - 1] != '}')
4019 while (namelen > 0 && name[namelen] != '^')
4023 namelen -= STRING_SIZE("refs/tags/");
4024 name += STRING_SIZE("refs/tags/");
4026 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4028 namelen -= STRING_SIZE("refs/remotes/");
4029 name += STRING_SIZE("refs/remotes/");
4031 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4032 namelen -= STRING_SIZE("refs/heads/");
4033 name += STRING_SIZE("refs/heads/");
4035 } else if (!strcmp(name, "HEAD")) {
4039 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4043 ref = &refs[refs_size++];
4044 ref->name = malloc(namelen + 1);
4048 strncpy(ref->name, name, namelen);
4049 ref->name[namelen] = 0;
4051 ref->remote = remote;
4052 string_copy_rev(ref->id, id);
4060 const char *cmd_env = getenv("TIG_LS_REMOTE");
4061 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4063 return read_properties(popen(cmd, "r"), "\t", read_ref);
4067 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4069 if (!strcmp(name, "i18n.commitencoding"))
4070 string_ncopy(opt_encoding, value, valuelen);
4076 load_repo_config(void)
4078 return read_properties(popen("git repo-config --list", "r"),
4079 "=", read_repo_config_option);
4083 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4085 if (!opt_git_dir[0])
4086 string_ncopy(opt_git_dir, name, namelen);
4088 string_ncopy(opt_cdup, name, namelen);
4092 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4093 * must be the last one! */
4095 load_repo_info(void)
4097 return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
4098 "=", read_repo_info);
4102 read_properties(FILE *pipe, const char *separators,
4103 int (*read_property)(char *, size_t, char *, size_t))
4105 char buffer[BUFSIZ];
4112 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4117 name = chomp_string(name);
4118 namelen = strcspn(name, separators);
4120 if (name[namelen]) {
4122 value = chomp_string(name + namelen + 1);
4123 valuelen = strlen(value);
4130 state = read_property(name, namelen, value, valuelen);
4133 if (state != ERR && ferror(pipe))
4146 static void __NORETURN
4149 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4155 static void __NORETURN
4156 die(const char *err, ...)
4162 va_start(args, err);
4163 fputs("tig: ", stderr);
4164 vfprintf(stderr, err, args);
4165 fputs("\n", stderr);
4172 main(int argc, char *argv[])
4175 enum request request;
4178 signal(SIGINT, quit);
4180 if (setlocale(LC_ALL, "")) {
4181 char *codeset = nl_langinfo(CODESET);
4183 string_ncopy(opt_codeset, codeset, strlen(codeset));
4186 if (load_repo_info() == ERR)
4187 die("Failed to load repo info.");
4189 /* Require a git repository unless when running in pager mode. */
4190 if (!opt_git_dir[0])
4191 die("Not a git repository");
4193 if (load_options() == ERR)
4194 die("Failed to load user config.");
4196 /* Load the repo config file so options can be overwritten from
4197 * the command line. */
4198 if (load_repo_config() == ERR)
4199 die("Failed to load repo config.");
4201 if (!parse_options(argc, argv))
4204 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4205 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4206 if (opt_iconv == ICONV_NONE)
4207 die("Failed to initialize character set conversion");
4210 if (load_refs() == ERR)
4211 die("Failed to load refs.");
4213 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4214 view->cmd_env = getenv(view->cmd_env);
4216 request = opt_request;
4220 while (view_driver(display[current_view], request)) {
4224 foreach_view (view, i)
4227 /* Refresh, accept single keystroke of input */
4228 key = wgetch(status_win);
4230 /* wgetch() with nodelay() enabled returns ERR when there's no
4237 request = get_keybinding(display[current_view]->keymap, key);
4239 /* Some low-level request handling. This keeps access to
4240 * status_win restricted. */
4244 char *cmd = read_prompt(":");
4246 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4247 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4248 opt_request = REQ_VIEW_DIFF;
4250 opt_request = REQ_VIEW_PAGER;
4259 case REQ_SEARCH_BACK:
4261 const char *prompt = request == REQ_SEARCH
4263 char *search = read_prompt(prompt);
4266 string_ncopy(opt_search, search, strlen(search));
4271 case REQ_SCREEN_RESIZE:
4275 getmaxyx(stdscr, height, width);
4277 /* Resize the status view and let the view driver take
4278 * care of resizing the displayed views. */
4279 wresize(status_win, 1, width);
4280 mvwin(status_win, height - 1, 0);
4281 wrefresh(status_win);