1 /* Copyright (c) 2006-2007 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.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
48 #define __NORETURN __attribute__((__noreturn__))
53 static void __NORETURN die(const char *err, ...);
54 static void warn(const char *msg, ...);
55 static void report(const char *msg, ...);
56 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
57 static void set_nonblocking_input(bool loading);
58 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
60 #define ABS(x) ((x) >= 0 ? (x) : -(x))
61 #define MIN(x, y) ((x) < (y) ? (x) : (y))
63 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
64 #define STRING_SIZE(x) (sizeof(x) - 1)
66 #define SIZEOF_STR 1024 /* Default string size. */
67 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
68 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
72 #define REVGRAPH_INIT 'I'
73 #define REVGRAPH_MERGE 'M'
74 #define REVGRAPH_BRANCH '+'
75 #define REVGRAPH_COMMIT '*'
76 #define REVGRAPH_BOUND '^'
77 #define REVGRAPH_LINE '|'
79 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
81 /* This color name can be used to refer to the default term colors. */
82 #define COLOR_DEFAULT (-1)
84 #define ICONV_NONE ((iconv_t) -1)
86 #define ICONV_CONST /* nothing */
89 /* The format and size of the date column in the main view. */
90 #define DATE_FORMAT "%Y-%m-%d %H:%M"
91 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
93 #define AUTHOR_COLS 20
95 /* The default interval between line numbers. */
96 #define NUMBER_INTERVAL 1
100 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
103 #define GIT_CONFIG "git config"
106 #define TIG_LS_REMOTE \
107 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
109 #define TIG_DIFF_CMD \
110 "git show --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
112 #define TIG_LOG_CMD \
113 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
115 #define TIG_MAIN_CMD \
116 "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
118 #define TIG_TREE_CMD \
121 #define TIG_BLOB_CMD \
122 "git cat-file blob %s"
124 /* XXX: Needs to be defined to the empty string. */
125 #define TIG_HELP_CMD ""
126 #define TIG_PAGER_CMD ""
127 #define TIG_STATUS_CMD ""
128 #define TIG_STAGE_CMD ""
130 /* Some ascii-shorthands fitted into the ncurses namespace. */
132 #define KEY_RETURN '\r'
137 char *name; /* Ref name; tag or head names are shortened. */
138 char id[SIZEOF_REV]; /* Commit SHA1 ID */
139 unsigned int tag:1; /* Is it a tag? */
140 unsigned int remote:1; /* Is it a remote ref? */
141 unsigned int next:1; /* For ref lists: are there more refs? */
144 static struct ref **get_refs(char *id);
153 set_from_int_map(struct int_map *map, size_t map_size,
154 int *value, const char *name, int namelen)
159 for (i = 0; i < map_size; i++)
160 if (namelen == map[i].namelen &&
161 !strncasecmp(name, map[i].name, namelen)) {
162 *value = map[i].value;
175 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
177 if (srclen > dstlen - 1)
180 strncpy(dst, src, srclen);
184 /* Shorthands for safely copying into a fixed buffer. */
186 #define string_copy(dst, src) \
187 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
189 #define string_ncopy(dst, src, srclen) \
190 string_ncopy_do(dst, sizeof(dst), src, srclen)
192 #define string_copy_rev(dst, src) \
193 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
195 #define string_add(dst, from, src) \
196 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
199 chomp_string(char *name)
203 while (isspace(*name))
206 namelen = strlen(name) - 1;
207 while (namelen > 0 && isspace(name[namelen]))
214 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
217 size_t pos = bufpos ? *bufpos : 0;
220 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
226 return pos >= bufsize ? FALSE : TRUE;
229 #define string_format(buf, fmt, args...) \
230 string_nformat(buf, sizeof(buf), NULL, fmt, args)
232 #define string_format_from(buf, from, fmt, args...) \
233 string_nformat(buf, sizeof(buf), from, fmt, args)
236 string_enum_compare(const char *str1, const char *str2, int len)
240 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
242 /* Diff-Header == DIFF_HEADER */
243 for (i = 0; i < len; i++) {
244 if (toupper(str1[i]) == toupper(str2[i]))
247 if (string_enum_sep(str1[i]) &&
248 string_enum_sep(str2[i]))
251 return str1[i] - str2[i];
259 * NOTE: The following is a slightly modified copy of the git project's shell
260 * quoting routines found in the quote.c file.
262 * Help to copy the thing properly quoted for the shell safety. any single
263 * quote is replaced with '\'', any exclamation point is replaced with '\!',
264 * and the whole thing is enclosed in a
267 * original sq_quote result
268 * name ==> name ==> 'name'
269 * a b ==> a b ==> 'a b'
270 * a'b ==> a'\''b ==> 'a'\''b'
271 * a!b ==> a'\!'b ==> 'a'\!'b'
275 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
279 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
282 while ((c = *src++)) {
283 if (c == '\'' || c == '!') {
294 if (bufsize < SIZEOF_STR)
306 /* XXX: Keep the view request first and in sync with views[]. */ \
307 REQ_GROUP("View switching") \
308 REQ_(VIEW_MAIN, "Show main view"), \
309 REQ_(VIEW_DIFF, "Show diff view"), \
310 REQ_(VIEW_LOG, "Show log view"), \
311 REQ_(VIEW_TREE, "Show tree view"), \
312 REQ_(VIEW_BLOB, "Show blob view"), \
313 REQ_(VIEW_HELP, "Show help page"), \
314 REQ_(VIEW_PAGER, "Show pager view"), \
315 REQ_(VIEW_STATUS, "Show status view"), \
316 REQ_(VIEW_STAGE, "Show stage view"), \
318 REQ_GROUP("View manipulation") \
319 REQ_(ENTER, "Enter current line and scroll"), \
320 REQ_(NEXT, "Move to next"), \
321 REQ_(PREVIOUS, "Move to previous"), \
322 REQ_(VIEW_NEXT, "Move focus to next view"), \
323 REQ_(REFRESH, "Reload and refresh"), \
324 REQ_(VIEW_CLOSE, "Close the current view"), \
325 REQ_(QUIT, "Close all views and quit"), \
327 REQ_GROUP("Cursor navigation") \
328 REQ_(MOVE_UP, "Move cursor one line up"), \
329 REQ_(MOVE_DOWN, "Move cursor one line down"), \
330 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
331 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
332 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
333 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
335 REQ_GROUP("Scrolling") \
336 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
337 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
338 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
339 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
341 REQ_GROUP("Searching") \
342 REQ_(SEARCH, "Search the view"), \
343 REQ_(SEARCH_BACK, "Search backwards in the view"), \
344 REQ_(FIND_NEXT, "Find next search match"), \
345 REQ_(FIND_PREV, "Find previous search match"), \
348 REQ_(PROMPT, "Bring up the prompt"), \
349 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
350 REQ_(SCREEN_RESIZE, "Resize the screen"), \
351 REQ_(SHOW_VERSION, "Show version information"), \
352 REQ_(STOP_LOADING, "Stop all loading views"), \
353 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
354 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
355 REQ_(STATUS_UPDATE, "Update file status"), \
356 REQ_(STATUS_MERGE, "Merge file using external tool"), \
357 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
358 REQ_(EDIT, "Open in editor"), \
359 REQ_(NONE, "Do nothing")
362 /* User action requests. */
364 #define REQ_GROUP(help)
365 #define REQ_(req, help) REQ_##req
367 /* Offset all requests to avoid conflicts with ncurses getch values. */
368 REQ_OFFSET = KEY_MAX + 1,
375 struct request_info {
376 enum request request;
382 static struct request_info req_info[] = {
383 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
384 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
391 get_request(const char *name)
393 int namelen = strlen(name);
396 for (i = 0; i < ARRAY_SIZE(req_info); i++)
397 if (req_info[i].namelen == namelen &&
398 !string_enum_compare(req_info[i].name, name, namelen))
399 return req_info[i].request;
409 static const char usage[] =
410 "tig " TIG_VERSION " (" __DATE__ ")\n"
412 "Usage: tig [options] [revs] [--] [paths]\n"
413 " or: tig show [options] [revs] [--] [paths]\n"
415 " or: tig < [git command output]\n"
418 " -v, --version Show version and exit\n"
419 " -h, --help Show help message and exit\n";
421 /* Option and state variables. */
422 static bool opt_line_number = FALSE;
423 static bool opt_rev_graph = FALSE;
424 static int opt_num_interval = NUMBER_INTERVAL;
425 static int opt_tab_size = TABSIZE;
426 static enum request opt_request = REQ_VIEW_MAIN;
427 static char opt_cmd[SIZEOF_STR] = "";
428 static char opt_path[SIZEOF_STR] = "";
429 static FILE *opt_pipe = NULL;
430 static char opt_encoding[20] = "UTF-8";
431 static bool opt_utf8 = TRUE;
432 static char opt_codeset[20] = "UTF-8";
433 static iconv_t opt_iconv = ICONV_NONE;
434 static char opt_search[SIZEOF_STR] = "";
435 static char opt_cdup[SIZEOF_STR] = "";
436 static char opt_git_dir[SIZEOF_STR] = "";
437 static char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
438 static char opt_editor[SIZEOF_STR] = "";
446 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
456 int namelen = strlen(name);
460 if (strncmp(opt, name, namelen))
463 if (opt[namelen] == '=')
464 value = opt + namelen + 1;
467 if (!short_name || opt[1] != short_name)
472 va_start(args, type);
473 if (type == OPT_INT) {
474 number = va_arg(args, int *);
476 *number = atoi(value);
483 /* Returns the index of log or diff command or -1 to exit. */
485 parse_options(int argc, char *argv[])
489 char *subcommand = NULL;
492 for (i = 1; i < argc; i++) {
495 if (!strcmp(opt, "log") ||
496 !strcmp(opt, "diff")) {
498 opt_request = opt[0] == 'l'
499 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
500 warn("`tig %s' has been deprecated", opt);
504 if (!strcmp(opt, "show")) {
506 opt_request = REQ_VIEW_DIFF;
510 if (!strcmp(opt, "status")) {
512 opt_request = REQ_VIEW_STATUS;
516 if (opt[0] && opt[0] != '-')
519 if (!strcmp(opt, "--")) {
524 if (check_option(opt, 'v', "version", OPT_NONE)) {
525 printf("tig version %s\n", TIG_VERSION);
529 if (check_option(opt, 'h', "help", OPT_NONE)) {
534 if (!strcmp(opt, "-S")) {
535 warn("`%s' has been deprecated; use `tig status' instead", opt);
536 opt_request = REQ_VIEW_STATUS;
540 if (!strcmp(opt, "-l")) {
541 opt_request = REQ_VIEW_LOG;
542 } else if (!strcmp(opt, "-d")) {
543 opt_request = REQ_VIEW_DIFF;
544 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
545 opt_line_number = TRUE;
546 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
547 opt_tab_size = MIN(opt_tab_size, TABSIZE);
549 if (altargc >= ARRAY_SIZE(altargv))
550 die("maximum number of arguments exceeded");
551 altargv[altargc++] = opt;
555 warn("`%s' has been deprecated", opt);
558 /* Check that no 'alt' arguments occured before a subcommand. */
559 if (subcommand && i < argc && altargc > 0)
560 die("unknown arguments before `%s'", argv[i]);
562 if (!isatty(STDIN_FILENO)) {
563 opt_request = REQ_VIEW_PAGER;
566 } else if (opt_request == REQ_VIEW_STATUS) {
568 warn("ignoring arguments after `%s'", argv[i]);
570 } else if (i < argc || altargc > 0) {
574 if (opt_request == REQ_VIEW_MAIN)
575 /* XXX: This is vulnerable to the user overriding
576 * options required for the main view parser. */
577 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
579 string_copy(opt_cmd, "git");
580 buf_size = strlen(opt_cmd);
582 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
583 opt_cmd[buf_size++] = ' ';
584 buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
587 while (buf_size < sizeof(opt_cmd) && i < argc) {
588 opt_cmd[buf_size++] = ' ';
589 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
592 if (buf_size >= sizeof(opt_cmd))
593 die("command too long");
595 opt_cmd[buf_size] = 0;
598 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
606 * Line-oriented content detection.
610 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
611 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
612 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
613 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
614 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
615 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
616 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
619 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
620 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
621 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
622 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
623 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
624 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
625 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
626 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
627 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
628 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
629 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
630 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
631 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
632 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
633 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
634 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
635 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
637 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
638 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
639 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
640 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
641 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
642 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
643 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
644 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
645 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
646 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
647 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
648 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
649 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
650 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
651 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
652 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
653 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
654 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
655 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
656 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
657 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
660 #define LINE(type, line, fg, bg, attr) \
667 const char *name; /* Option name. */
668 int namelen; /* Size of option name. */
669 const char *line; /* The start of line to match. */
670 int linelen; /* Size of string to match. */
671 int fg, bg, attr; /* Color and text attributes for the lines. */
674 static struct line_info line_info[] = {
675 #define LINE(type, line, fg, bg, attr) \
676 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
681 static enum line_type
682 get_line_type(char *line)
684 int linelen = strlen(line);
687 for (type = 0; type < ARRAY_SIZE(line_info); type++)
688 /* Case insensitive search matches Signed-off-by lines better. */
689 if (linelen >= line_info[type].linelen &&
690 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
697 get_line_attr(enum line_type type)
699 assert(type < ARRAY_SIZE(line_info));
700 return COLOR_PAIR(type) | line_info[type].attr;
703 static struct line_info *
704 get_line_info(char *name, int namelen)
708 for (type = 0; type < ARRAY_SIZE(line_info); type++)
709 if (namelen == line_info[type].namelen &&
710 !string_enum_compare(line_info[type].name, name, namelen))
711 return &line_info[type];
719 int default_bg = line_info[LINE_DEFAULT].bg;
720 int default_fg = line_info[LINE_DEFAULT].fg;
725 if (assume_default_colors(default_fg, default_bg) == ERR) {
726 default_bg = COLOR_BLACK;
727 default_fg = COLOR_WHITE;
730 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
731 struct line_info *info = &line_info[type];
732 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
733 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
735 init_pair(type, fg, bg);
743 unsigned int selected:1;
745 void *data; /* User data */
755 enum request request;
756 struct keybinding *next;
759 static struct keybinding default_keybindings[] = {
761 { 'm', REQ_VIEW_MAIN },
762 { 'd', REQ_VIEW_DIFF },
763 { 'l', REQ_VIEW_LOG },
764 { 't', REQ_VIEW_TREE },
765 { 'f', REQ_VIEW_BLOB },
766 { 'p', REQ_VIEW_PAGER },
767 { 'h', REQ_VIEW_HELP },
768 { 'S', REQ_VIEW_STATUS },
769 { 'c', REQ_VIEW_STAGE },
771 /* View manipulation */
772 { 'q', REQ_VIEW_CLOSE },
773 { KEY_TAB, REQ_VIEW_NEXT },
774 { KEY_RETURN, REQ_ENTER },
775 { KEY_UP, REQ_PREVIOUS },
776 { KEY_DOWN, REQ_NEXT },
777 { 'R', REQ_REFRESH },
779 /* Cursor navigation */
780 { 'k', REQ_MOVE_UP },
781 { 'j', REQ_MOVE_DOWN },
782 { KEY_HOME, REQ_MOVE_FIRST_LINE },
783 { KEY_END, REQ_MOVE_LAST_LINE },
784 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
785 { ' ', REQ_MOVE_PAGE_DOWN },
786 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
787 { 'b', REQ_MOVE_PAGE_UP },
788 { '-', REQ_MOVE_PAGE_UP },
791 { KEY_IC, REQ_SCROLL_LINE_UP },
792 { KEY_DC, REQ_SCROLL_LINE_DOWN },
793 { 'w', REQ_SCROLL_PAGE_UP },
794 { 's', REQ_SCROLL_PAGE_DOWN },
798 { '?', REQ_SEARCH_BACK },
799 { 'n', REQ_FIND_NEXT },
800 { 'N', REQ_FIND_PREV },
804 { 'z', REQ_STOP_LOADING },
805 { 'v', REQ_SHOW_VERSION },
806 { 'r', REQ_SCREEN_REDRAW },
807 { '.', REQ_TOGGLE_LINENO },
808 { 'g', REQ_TOGGLE_REV_GRAPH },
810 { 'u', REQ_STATUS_UPDATE },
811 { 'M', REQ_STATUS_MERGE },
812 { ',', REQ_TREE_PARENT },
815 /* Using the ncurses SIGWINCH handler. */
816 { KEY_RESIZE, REQ_SCREEN_RESIZE },
819 #define KEYMAP_INFO \
832 #define KEYMAP_(name) KEYMAP_##name
837 static struct int_map keymap_table[] = {
838 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
843 #define set_keymap(map, name) \
844 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
846 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
849 add_keybinding(enum keymap keymap, enum request request, int key)
851 struct keybinding *keybinding;
853 keybinding = calloc(1, sizeof(*keybinding));
855 die("Failed to allocate keybinding");
857 keybinding->alias = key;
858 keybinding->request = request;
859 keybinding->next = keybindings[keymap];
860 keybindings[keymap] = keybinding;
863 /* Looks for a key binding first in the given map, then in the generic map, and
864 * lastly in the default keybindings. */
866 get_keybinding(enum keymap keymap, int key)
868 struct keybinding *kbd;
871 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
872 if (kbd->alias == key)
875 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
876 if (kbd->alias == key)
879 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
880 if (default_keybindings[i].alias == key)
881 return default_keybindings[i].request;
883 return (enum request) key;
892 static struct key key_table[] = {
893 { "Enter", KEY_RETURN },
895 { "Backspace", KEY_BACKSPACE },
897 { "Escape", KEY_ESC },
898 { "Left", KEY_LEFT },
899 { "Right", KEY_RIGHT },
901 { "Down", KEY_DOWN },
902 { "Insert", KEY_IC },
903 { "Delete", KEY_DC },
905 { "Home", KEY_HOME },
907 { "PageUp", KEY_PPAGE },
908 { "PageDown", KEY_NPAGE },
918 { "F10", KEY_F(10) },
919 { "F11", KEY_F(11) },
920 { "F12", KEY_F(12) },
924 get_key_value(const char *name)
928 for (i = 0; i < ARRAY_SIZE(key_table); i++)
929 if (!strcasecmp(key_table[i].name, name))
930 return key_table[i].value;
932 if (strlen(name) == 1 && isprint(*name))
939 get_key_name(int key_value)
941 static char key_char[] = "'X'";
945 for (key = 0; key < ARRAY_SIZE(key_table); key++)
946 if (key_table[key].value == key_value)
947 seq = key_table[key].name;
951 isprint(key_value)) {
952 key_char[1] = (char) key_value;
956 return seq ? seq : "'?'";
960 get_key(enum request request)
962 static char buf[BUFSIZ];
969 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
970 struct keybinding *keybinding = &default_keybindings[i];
972 if (keybinding->request != request)
975 if (!string_format_from(buf, &pos, "%s%s", sep,
976 get_key_name(keybinding->alias)))
977 return "Too many keybindings!";
987 char cmd[SIZEOF_STR];
990 static struct run_request *run_request;
991 static size_t run_requests;
994 add_run_request(enum keymap keymap, int key, int argc, char **argv)
996 struct run_request *tmp;
997 struct run_request req = { keymap, key };
1000 for (bufpos = 0; argc > 0; argc--, argv++)
1001 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1004 req.cmd[bufpos - 1] = 0;
1006 tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1011 run_request[run_requests++] = req;
1013 return REQ_NONE + run_requests;
1016 static struct run_request *
1017 get_run_request(enum request request)
1019 if (request <= REQ_NONE)
1021 return &run_request[request - REQ_NONE - 1];
1025 add_builtin_run_requests(void)
1032 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1033 { KEYMAP_GENERIC, 'G', { "git gc" } },
1037 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1040 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1041 if (req != REQ_NONE)
1042 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1047 * User config file handling.
1050 static struct int_map color_map[] = {
1051 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1063 #define set_color(color, name) \
1064 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1066 static struct int_map attr_map[] = {
1067 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1074 ATTR_MAP(UNDERLINE),
1077 #define set_attribute(attr, name) \
1078 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1080 static int config_lineno;
1081 static bool config_errors;
1082 static char *config_msg;
1084 /* Wants: object fgcolor bgcolor [attr] */
1086 option_color_command(int argc, char *argv[])
1088 struct line_info *info;
1090 if (argc != 3 && argc != 4) {
1091 config_msg = "Wrong number of arguments given to color command";
1095 info = get_line_info(argv[0], strlen(argv[0]));
1097 config_msg = "Unknown color name";
1101 if (set_color(&info->fg, argv[1]) == ERR ||
1102 set_color(&info->bg, argv[2]) == ERR) {
1103 config_msg = "Unknown color";
1107 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1108 config_msg = "Unknown attribute";
1115 /* Wants: name = value */
1117 option_set_command(int argc, char *argv[])
1120 config_msg = "Wrong number of arguments given to set command";
1124 if (strcmp(argv[1], "=")) {
1125 config_msg = "No value assigned";
1129 if (!strcmp(argv[0], "show-rev-graph")) {
1130 opt_rev_graph = (!strcmp(argv[2], "1") ||
1131 !strcmp(argv[2], "true") ||
1132 !strcmp(argv[2], "yes"));
1136 if (!strcmp(argv[0], "line-number-interval")) {
1137 opt_num_interval = atoi(argv[2]);
1141 if (!strcmp(argv[0], "tab-size")) {
1142 opt_tab_size = atoi(argv[2]);
1146 if (!strcmp(argv[0], "commit-encoding")) {
1147 char *arg = argv[2];
1148 int delimiter = *arg;
1151 switch (delimiter) {
1154 for (arg++, i = 0; arg[i]; i++)
1155 if (arg[i] == delimiter) {
1160 string_ncopy(opt_encoding, arg, strlen(arg));
1165 config_msg = "Unknown variable name";
1169 /* Wants: mode request key */
1171 option_bind_command(int argc, char *argv[])
1173 enum request request;
1178 config_msg = "Wrong number of arguments given to bind command";
1182 if (set_keymap(&keymap, argv[0]) == ERR) {
1183 config_msg = "Unknown key map";
1187 key = get_key_value(argv[1]);
1189 config_msg = "Unknown key";
1193 request = get_request(argv[2]);
1194 if (request == REQ_NONE) {
1195 const char *obsolete[] = { "cherry-pick" };
1196 size_t namelen = strlen(argv[2]);
1199 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1200 if (namelen == strlen(obsolete[i]) &&
1201 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1202 config_msg = "Obsolete request name";
1207 if (request == REQ_NONE && *argv[2]++ == '!')
1208 request = add_run_request(keymap, key, argc - 2, argv + 2);
1209 if (request == REQ_NONE) {
1210 config_msg = "Unknown request name";
1214 add_keybinding(keymap, request, key);
1220 set_option(char *opt, char *value)
1227 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1228 argv[argc++] = value;
1231 /* Nothing more to tokenize or last available token. */
1232 if (!*value || argc >= ARRAY_SIZE(argv))
1236 while (isspace(*value))
1240 if (!strcmp(opt, "color"))
1241 return option_color_command(argc, argv);
1243 if (!strcmp(opt, "set"))
1244 return option_set_command(argc, argv);
1246 if (!strcmp(opt, "bind"))
1247 return option_bind_command(argc, argv);
1249 config_msg = "Unknown option command";
1254 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1259 config_msg = "Internal error";
1261 /* Check for comment markers, since read_properties() will
1262 * only ensure opt and value are split at first " \t". */
1263 optlen = strcspn(opt, "#");
1267 if (opt[optlen] != 0) {
1268 config_msg = "No option value";
1272 /* Look for comment endings in the value. */
1273 size_t len = strcspn(value, "#");
1275 if (len < valuelen) {
1277 value[valuelen] = 0;
1280 status = set_option(opt, value);
1283 if (status == ERR) {
1284 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1285 config_lineno, (int) optlen, opt, config_msg);
1286 config_errors = TRUE;
1289 /* Always keep going if errors are encountered. */
1294 load_option_file(const char *path)
1298 /* It's ok that the file doesn't exist. */
1299 file = fopen(path, "r");
1304 config_errors = FALSE;
1306 if (read_properties(file, " \t", read_option) == ERR ||
1307 config_errors == TRUE)
1308 fprintf(stderr, "Errors while loading %s.\n", path);
1314 char *home = getenv("HOME");
1315 char *tigrc_user = getenv("TIGRC_USER");
1316 char *tigrc_system = getenv("TIGRC_SYSTEM");
1317 char buf[SIZEOF_STR];
1319 add_builtin_run_requests();
1321 if (!tigrc_system) {
1322 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1326 load_option_file(tigrc_system);
1329 if (!home || !string_format(buf, "%s/.tigrc", home))
1333 load_option_file(tigrc_user);
1346 /* The display array of active views and the index of the current view. */
1347 static struct view *display[2];
1348 static unsigned int current_view;
1350 /* Reading from the prompt? */
1351 static bool input_mode = FALSE;
1353 #define foreach_displayed_view(view, i) \
1354 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1356 #define displayed_views() (display[1] != NULL ? 2 : 1)
1358 /* Current head and commit ID */
1359 static char ref_blob[SIZEOF_REF] = "";
1360 static char ref_commit[SIZEOF_REF] = "HEAD";
1361 static char ref_head[SIZEOF_REF] = "HEAD";
1364 const char *name; /* View name */
1365 const char *cmd_fmt; /* Default command line format */
1366 const char *cmd_env; /* Command line set via environment */
1367 const char *id; /* Points to either of ref_{head,commit,blob} */
1369 struct view_ops *ops; /* View operations */
1371 enum keymap keymap; /* What keymap does this view have */
1373 char cmd[SIZEOF_STR]; /* Command buffer */
1374 char ref[SIZEOF_REF]; /* Hovered commit reference */
1375 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1377 int height, width; /* The width and height of the main window */
1378 WINDOW *win; /* The main window */
1379 WINDOW *title; /* The title window living below the main window */
1382 unsigned long offset; /* Offset of the window top */
1383 unsigned long lineno; /* Current line number */
1386 char grep[SIZEOF_STR]; /* Search string */
1387 regex_t *regex; /* Pre-compiled regex */
1389 /* If non-NULL, points to the view that opened this view. If this view
1390 * is closed tig will switch back to the parent view. */
1391 struct view *parent;
1394 unsigned long lines; /* Total number of lines */
1395 struct line *line; /* Line index */
1396 unsigned long line_size;/* Total number of allocated lines */
1397 unsigned int digits; /* Number of digits in the lines member. */
1405 /* What type of content being displayed. Used in the title bar. */
1407 /* Open and reads in all view content. */
1408 bool (*open)(struct view *view);
1409 /* Read one line; updates view->line. */
1410 bool (*read)(struct view *view, char *data);
1411 /* Draw one line; @lineno must be < view->height. */
1412 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1413 /* Depending on view handle a special requests. */
1414 enum request (*request)(struct view *view, enum request request, struct line *line);
1415 /* Search for regex in a line. */
1416 bool (*grep)(struct view *view, struct line *line);
1418 void (*select)(struct view *view, struct line *line);
1421 static struct view_ops pager_ops;
1422 static struct view_ops main_ops;
1423 static struct view_ops tree_ops;
1424 static struct view_ops blob_ops;
1425 static struct view_ops help_ops;
1426 static struct view_ops status_ops;
1427 static struct view_ops stage_ops;
1429 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1430 { name, cmd, #env, ref, ops, map}
1432 #define VIEW_(id, name, ops, ref) \
1433 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1436 static struct view views[] = {
1437 VIEW_(MAIN, "main", &main_ops, ref_head),
1438 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1439 VIEW_(LOG, "log", &pager_ops, ref_head),
1440 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1441 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1442 VIEW_(HELP, "help", &help_ops, ""),
1443 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1444 VIEW_(STATUS, "status", &status_ops, ""),
1445 VIEW_(STAGE, "stage", &stage_ops, ""),
1448 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1450 #define foreach_view(view, i) \
1451 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1453 #define view_is_displayed(view) \
1454 (view == display[0] || view == display[1])
1457 draw_view_line(struct view *view, unsigned int lineno)
1460 bool selected = (view->offset + lineno == view->lineno);
1463 assert(view_is_displayed(view));
1465 if (view->offset + lineno >= view->lines)
1468 line = &view->line[view->offset + lineno];
1471 line->selected = TRUE;
1472 view->ops->select(view, line);
1473 } else if (line->selected) {
1474 line->selected = FALSE;
1475 wmove(view->win, lineno, 0);
1476 wclrtoeol(view->win);
1479 scrollok(view->win, FALSE);
1480 draw_ok = view->ops->draw(view, line, lineno, selected);
1481 scrollok(view->win, TRUE);
1487 redraw_view_from(struct view *view, int lineno)
1489 assert(0 <= lineno && lineno < view->height);
1491 for (; lineno < view->height; lineno++) {
1492 if (!draw_view_line(view, lineno))
1496 redrawwin(view->win);
1498 wnoutrefresh(view->win);
1500 wrefresh(view->win);
1504 redraw_view(struct view *view)
1507 redraw_view_from(view, 0);
1512 update_view_title(struct view *view)
1514 char buf[SIZEOF_STR];
1515 char state[SIZEOF_STR];
1516 size_t bufpos = 0, statelen = 0;
1518 assert(view_is_displayed(view));
1520 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1521 unsigned int view_lines = view->offset + view->height;
1522 unsigned int lines = view->lines
1523 ? MIN(view_lines, view->lines) * 100 / view->lines
1526 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1533 time_t secs = time(NULL) - view->start_time;
1535 /* Three git seconds are a long time ... */
1537 string_format_from(state, &statelen, " %lds", secs);
1541 string_format_from(buf, &bufpos, "[%s]", view->name);
1542 if (*view->ref && bufpos < view->width) {
1543 size_t refsize = strlen(view->ref);
1544 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1546 if (minsize < view->width)
1547 refsize = view->width - minsize + 7;
1548 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1551 if (statelen && bufpos < view->width) {
1552 string_format_from(buf, &bufpos, " %s", state);
1555 if (view == display[current_view])
1556 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1558 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1560 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1561 wclrtoeol(view->title);
1562 wmove(view->title, 0, view->width - 1);
1565 wnoutrefresh(view->title);
1567 wrefresh(view->title);
1571 resize_display(void)
1574 struct view *base = display[0];
1575 struct view *view = display[1] ? display[1] : display[0];
1577 /* Setup window dimensions */
1579 getmaxyx(stdscr, base->height, base->width);
1581 /* Make room for the status window. */
1585 /* Horizontal split. */
1586 view->width = base->width;
1587 view->height = SCALE_SPLIT_VIEW(base->height);
1588 base->height -= view->height;
1590 /* Make room for the title bar. */
1594 /* Make room for the title bar. */
1599 foreach_displayed_view (view, i) {
1601 view->win = newwin(view->height, 0, offset, 0);
1603 die("Failed to create %s view", view->name);
1605 scrollok(view->win, TRUE);
1607 view->title = newwin(1, 0, offset + view->height, 0);
1609 die("Failed to create title window");
1612 wresize(view->win, view->height, view->width);
1613 mvwin(view->win, offset, 0);
1614 mvwin(view->title, offset + view->height, 0);
1617 offset += view->height + 1;
1622 redraw_display(void)
1627 foreach_displayed_view (view, i) {
1629 update_view_title(view);
1634 update_display_cursor(struct view *view)
1636 /* Move the cursor to the right-most column of the cursor line.
1638 * XXX: This could turn out to be a bit expensive, but it ensures that
1639 * the cursor does not jump around. */
1641 wmove(view->win, view->lineno - view->offset, view->width - 1);
1642 wrefresh(view->win);
1650 /* Scrolling backend */
1652 do_scroll_view(struct view *view, int lines)
1654 bool redraw_current_line = FALSE;
1656 /* The rendering expects the new offset. */
1657 view->offset += lines;
1659 assert(0 <= view->offset && view->offset < view->lines);
1662 /* Move current line into the view. */
1663 if (view->lineno < view->offset) {
1664 view->lineno = view->offset;
1665 redraw_current_line = TRUE;
1666 } else if (view->lineno >= view->offset + view->height) {
1667 view->lineno = view->offset + view->height - 1;
1668 redraw_current_line = TRUE;
1671 assert(view->offset <= view->lineno && view->lineno < view->lines);
1673 /* Redraw the whole screen if scrolling is pointless. */
1674 if (view->height < ABS(lines)) {
1678 int line = lines > 0 ? view->height - lines : 0;
1679 int end = line + ABS(lines);
1681 wscrl(view->win, lines);
1683 for (; line < end; line++) {
1684 if (!draw_view_line(view, line))
1688 if (redraw_current_line)
1689 draw_view_line(view, view->lineno - view->offset);
1692 redrawwin(view->win);
1693 wrefresh(view->win);
1697 /* Scroll frontend */
1699 scroll_view(struct view *view, enum request request)
1703 assert(view_is_displayed(view));
1706 case REQ_SCROLL_PAGE_DOWN:
1707 lines = view->height;
1708 case REQ_SCROLL_LINE_DOWN:
1709 if (view->offset + lines > view->lines)
1710 lines = view->lines - view->offset;
1712 if (lines == 0 || view->offset + view->height >= view->lines) {
1713 report("Cannot scroll beyond the last line");
1718 case REQ_SCROLL_PAGE_UP:
1719 lines = view->height;
1720 case REQ_SCROLL_LINE_UP:
1721 if (lines > view->offset)
1722 lines = view->offset;
1725 report("Cannot scroll beyond the first line");
1733 die("request %d not handled in switch", request);
1736 do_scroll_view(view, lines);
1741 move_view(struct view *view, enum request request)
1743 int scroll_steps = 0;
1747 case REQ_MOVE_FIRST_LINE:
1748 steps = -view->lineno;
1751 case REQ_MOVE_LAST_LINE:
1752 steps = view->lines - view->lineno - 1;
1755 case REQ_MOVE_PAGE_UP:
1756 steps = view->height > view->lineno
1757 ? -view->lineno : -view->height;
1760 case REQ_MOVE_PAGE_DOWN:
1761 steps = view->lineno + view->height >= view->lines
1762 ? view->lines - view->lineno - 1 : view->height;
1774 die("request %d not handled in switch", request);
1777 if (steps <= 0 && view->lineno == 0) {
1778 report("Cannot move beyond the first line");
1781 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1782 report("Cannot move beyond the last line");
1786 /* Move the current line */
1787 view->lineno += steps;
1788 assert(0 <= view->lineno && view->lineno < view->lines);
1790 /* Check whether the view needs to be scrolled */
1791 if (view->lineno < view->offset ||
1792 view->lineno >= view->offset + view->height) {
1793 scroll_steps = steps;
1794 if (steps < 0 && -steps > view->offset) {
1795 scroll_steps = -view->offset;
1797 } else if (steps > 0) {
1798 if (view->lineno == view->lines - 1 &&
1799 view->lines > view->height) {
1800 scroll_steps = view->lines - view->offset - 1;
1801 if (scroll_steps >= view->height)
1802 scroll_steps -= view->height - 1;
1807 if (!view_is_displayed(view)) {
1808 view->offset += scroll_steps;
1809 assert(0 <= view->offset && view->offset < view->lines);
1810 view->ops->select(view, &view->line[view->lineno]);
1814 /* Repaint the old "current" line if we be scrolling */
1815 if (ABS(steps) < view->height)
1816 draw_view_line(view, view->lineno - steps - view->offset);
1819 do_scroll_view(view, scroll_steps);
1823 /* Draw the current line */
1824 draw_view_line(view, view->lineno - view->offset);
1826 redrawwin(view->win);
1827 wrefresh(view->win);
1836 static void search_view(struct view *view, enum request request);
1839 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1841 assert(view_is_displayed(view));
1843 if (!view->ops->grep(view, line))
1846 if (lineno - view->offset >= view->height) {
1847 view->offset = lineno;
1848 view->lineno = lineno;
1852 unsigned long old_lineno = view->lineno - view->offset;
1854 view->lineno = lineno;
1855 draw_view_line(view, old_lineno);
1857 draw_view_line(view, view->lineno - view->offset);
1858 redrawwin(view->win);
1859 wrefresh(view->win);
1862 report("Line %ld matches '%s'", lineno + 1, view->grep);
1867 find_next(struct view *view, enum request request)
1869 unsigned long lineno = view->lineno;
1874 report("No previous search");
1876 search_view(view, request);
1886 case REQ_SEARCH_BACK:
1895 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1896 lineno += direction;
1898 /* Note, lineno is unsigned long so will wrap around in which case it
1899 * will become bigger than view->lines. */
1900 for (; lineno < view->lines; lineno += direction) {
1901 struct line *line = &view->line[lineno];
1903 if (find_next_line(view, lineno, line))
1907 report("No match found for '%s'", view->grep);
1911 search_view(struct view *view, enum request request)
1916 regfree(view->regex);
1919 view->regex = calloc(1, sizeof(*view->regex));
1924 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1925 if (regex_err != 0) {
1926 char buf[SIZEOF_STR] = "unknown error";
1928 regerror(regex_err, view->regex, buf, sizeof(buf));
1929 report("Search failed: %s", buf);
1933 string_copy(view->grep, opt_search);
1935 find_next(view, request);
1939 * Incremental updating
1943 end_update(struct view *view)
1947 set_nonblocking_input(FALSE);
1948 if (view->pipe == stdin)
1956 begin_update(struct view *view)
1962 string_copy(view->cmd, opt_cmd);
1964 /* When running random commands, initially show the
1965 * command in the title. However, it maybe later be
1966 * overwritten if a commit line is selected. */
1967 if (view == VIEW(REQ_VIEW_PAGER))
1968 string_copy(view->ref, view->cmd);
1972 } else if (view == VIEW(REQ_VIEW_TREE)) {
1973 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1974 char path[SIZEOF_STR];
1976 if (strcmp(view->vid, view->id))
1977 opt_path[0] = path[0] = 0;
1978 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1981 if (!string_format(view->cmd, format, view->id, path))
1985 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1986 const char *id = view->id;
1988 if (!string_format(view->cmd, format, id, id, id, id, id))
1991 /* Put the current ref_* value to the view title ref
1992 * member. This is needed by the blob view. Most other
1993 * views sets it automatically after loading because the
1994 * first line is a commit line. */
1995 string_copy_rev(view->ref, view->id);
1998 /* Special case for the pager view. */
2000 view->pipe = opt_pipe;
2003 view->pipe = popen(view->cmd, "r");
2009 set_nonblocking_input(TRUE);
2014 string_copy_rev(view->vid, view->id);
2019 for (i = 0; i < view->lines; i++)
2020 if (view->line[i].data)
2021 free(view->line[i].data);
2027 view->start_time = time(NULL);
2032 static struct line *
2033 realloc_lines(struct view *view, size_t line_size)
2035 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2041 view->line_size = line_size;
2046 update_view(struct view *view)
2048 char in_buffer[BUFSIZ];
2049 char out_buffer[BUFSIZ * 2];
2051 /* The number of lines to read. If too low it will cause too much
2052 * redrawing (and possible flickering), if too high responsiveness
2054 unsigned long lines = view->height;
2055 int redraw_from = -1;
2060 /* Only redraw if lines are visible. */
2061 if (view->offset + view->height >= view->lines)
2062 redraw_from = view->lines - view->offset;
2064 /* FIXME: This is probably not perfect for backgrounded views. */
2065 if (!realloc_lines(view, view->lines + lines))
2068 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2069 size_t linelen = strlen(line);
2072 line[linelen - 1] = 0;
2074 if (opt_iconv != ICONV_NONE) {
2075 ICONV_CONST char *inbuf = line;
2076 size_t inlen = linelen;
2078 char *outbuf = out_buffer;
2079 size_t outlen = sizeof(out_buffer);
2083 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2084 if (ret != (size_t) -1) {
2086 linelen = strlen(out_buffer);
2090 if (!view->ops->read(view, line))
2100 lines = view->lines;
2101 for (digits = 0; lines; digits++)
2104 /* Keep the displayed view in sync with line number scaling. */
2105 if (digits != view->digits) {
2106 view->digits = digits;
2111 if (!view_is_displayed(view))
2114 if (view == VIEW(REQ_VIEW_TREE)) {
2115 /* Clear the view and redraw everything since the tree sorting
2116 * might have rearranged things. */
2119 } else if (redraw_from >= 0) {
2120 /* If this is an incremental update, redraw the previous line
2121 * since for commits some members could have changed when
2122 * loading the main view. */
2123 if (redraw_from > 0)
2126 /* Since revision graph visualization requires knowledge
2127 * about the parent commit, it causes a further one-off
2128 * needed to be redrawn for incremental updates. */
2129 if (redraw_from > 0 && opt_rev_graph)
2132 /* Incrementally draw avoids flickering. */
2133 redraw_view_from(view, redraw_from);
2136 /* Update the title _after_ the redraw so that if the redraw picks up a
2137 * commit reference in view->ref it'll be available here. */
2138 update_view_title(view);
2141 if (ferror(view->pipe)) {
2142 report("Failed to read: %s", strerror(errno));
2145 } else if (feof(view->pipe)) {
2153 report("Allocation failure");
2156 view->ops->read(view, NULL);
2161 static struct line *
2162 add_line_data(struct view *view, void *data, enum line_type type)
2164 struct line *line = &view->line[view->lines++];
2166 memset(line, 0, sizeof(*line));
2173 static struct line *
2174 add_line_text(struct view *view, char *data, enum line_type type)
2177 data = strdup(data);
2179 return data ? add_line_data(view, data, type) : NULL;
2188 OPEN_DEFAULT = 0, /* Use default view switching. */
2189 OPEN_SPLIT = 1, /* Split current view. */
2190 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2191 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2195 open_view(struct view *prev, enum request request, enum open_flags flags)
2197 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2198 bool split = !!(flags & OPEN_SPLIT);
2199 bool reload = !!(flags & OPEN_RELOAD);
2200 struct view *view = VIEW(request);
2201 int nviews = displayed_views();
2202 struct view *base_view = display[0];
2204 if (view == prev && nviews == 1 && !reload) {
2205 report("Already in %s view", view->name);
2209 if (view->ops->open) {
2210 if (!view->ops->open(view)) {
2211 report("Failed to load %s view", view->name);
2215 } else if ((reload || strcmp(view->vid, view->id)) &&
2216 !begin_update(view)) {
2217 report("Failed to load %s view", view->name);
2226 /* Maximize the current view. */
2227 memset(display, 0, sizeof(display));
2229 display[current_view] = view;
2232 /* Resize the view when switching between split- and full-screen,
2233 * or when switching between two different full-screen views. */
2234 if (nviews != displayed_views() ||
2235 (nviews == 1 && base_view != display[0]))
2238 if (split && prev->lineno - prev->offset >= prev->height) {
2239 /* Take the title line into account. */
2240 int lines = prev->lineno - prev->offset - prev->height + 1;
2242 /* Scroll the view that was split if the current line is
2243 * outside the new limited view. */
2244 do_scroll_view(prev, lines);
2247 if (prev && view != prev) {
2248 if (split && !backgrounded) {
2249 /* "Blur" the previous view. */
2250 update_view_title(prev);
2253 view->parent = prev;
2256 if (view->pipe && view->lines == 0) {
2257 /* Clear the old view and let the incremental updating refill
2266 /* If the view is backgrounded the above calls to report()
2267 * won't redraw the view title. */
2269 update_view_title(view);
2273 open_external_viewer(const char *cmd)
2275 def_prog_mode(); /* save current tty modes */
2276 endwin(); /* restore original tty modes */
2278 fprintf(stderr, "Press Enter to continue");
2285 open_mergetool(const char *file)
2287 char cmd[SIZEOF_STR];
2288 char file_sq[SIZEOF_STR];
2290 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2291 string_format(cmd, "git mergetool %s", file_sq)) {
2292 open_external_viewer(cmd);
2297 open_editor(bool from_root, const char *file)
2299 char cmd[SIZEOF_STR];
2300 char file_sq[SIZEOF_STR];
2302 char *prefix = from_root ? opt_cdup : "";
2304 editor = getenv("GIT_EDITOR");
2305 if (!editor && *opt_editor)
2306 editor = opt_editor;
2308 editor = getenv("VISUAL");
2310 editor = getenv("EDITOR");
2314 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2315 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2316 open_external_viewer(cmd);
2321 open_run_request(enum request request)
2323 struct run_request *req = get_run_request(request);
2324 char buf[SIZEOF_STR * 2];
2329 report("Unknown run request");
2337 char *next = strstr(cmd, "%(");
2338 int len = next - cmd;
2345 } else if (!strncmp(next, "%(head)", 7)) {
2348 } else if (!strncmp(next, "%(commit)", 9)) {
2351 } else if (!strncmp(next, "%(blob)", 7)) {
2355 report("Unknown replacement in run request: `%s`", req->cmd);
2359 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2363 next = strchr(next, ')') + 1;
2367 open_external_viewer(buf);
2371 * User request switch noodle
2375 view_driver(struct view *view, enum request request)
2379 if (request == REQ_NONE) {
2384 if (request > REQ_NONE) {
2385 open_run_request(request);
2389 if (view && view->lines) {
2390 request = view->ops->request(view, request, &view->line[view->lineno]);
2391 if (request == REQ_NONE)
2398 case REQ_MOVE_PAGE_UP:
2399 case REQ_MOVE_PAGE_DOWN:
2400 case REQ_MOVE_FIRST_LINE:
2401 case REQ_MOVE_LAST_LINE:
2402 move_view(view, request);
2405 case REQ_SCROLL_LINE_DOWN:
2406 case REQ_SCROLL_LINE_UP:
2407 case REQ_SCROLL_PAGE_DOWN:
2408 case REQ_SCROLL_PAGE_UP:
2409 scroll_view(view, request);
2414 report("No file chosen, press %s to open tree view",
2415 get_key(REQ_VIEW_TREE));
2418 open_view(view, request, OPEN_DEFAULT);
2421 case REQ_VIEW_PAGER:
2422 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2423 report("No pager content, press %s to run command from prompt",
2424 get_key(REQ_PROMPT));
2427 open_view(view, request, OPEN_DEFAULT);
2430 case REQ_VIEW_STAGE:
2431 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2432 report("No stage content, press %s to open the status view and choose file",
2433 get_key(REQ_VIEW_STATUS));
2436 open_view(view, request, OPEN_DEFAULT);
2439 case REQ_VIEW_STATUS:
2440 if (opt_is_inside_work_tree == FALSE) {
2441 report("The status view requires a working tree");
2444 open_view(view, request, OPEN_DEFAULT);
2452 open_view(view, request, OPEN_DEFAULT);
2457 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2459 if ((view == VIEW(REQ_VIEW_DIFF) &&
2460 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2461 (view == VIEW(REQ_VIEW_STAGE) &&
2462 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2463 (view == VIEW(REQ_VIEW_BLOB) &&
2464 view->parent == VIEW(REQ_VIEW_TREE))) {
2467 view = view->parent;
2468 line = view->lineno;
2469 move_view(view, request);
2470 if (view_is_displayed(view))
2471 update_view_title(view);
2472 if (line != view->lineno)
2473 view->ops->request(view, REQ_ENTER,
2474 &view->line[view->lineno]);
2477 move_view(view, request);
2483 int nviews = displayed_views();
2484 int next_view = (current_view + 1) % nviews;
2486 if (next_view == current_view) {
2487 report("Only one view is displayed");
2491 current_view = next_view;
2492 /* Blur out the title of the previous view. */
2493 update_view_title(view);
2498 report("Refreshing is not yet supported for the %s view", view->name);
2501 case REQ_TOGGLE_LINENO:
2502 opt_line_number = !opt_line_number;
2506 case REQ_TOGGLE_REV_GRAPH:
2507 opt_rev_graph = !opt_rev_graph;
2512 /* Always reload^Wrerun commands from the prompt. */
2513 open_view(view, opt_request, OPEN_RELOAD);
2517 case REQ_SEARCH_BACK:
2518 search_view(view, request);
2523 find_next(view, request);
2526 case REQ_STOP_LOADING:
2527 for (i = 0; i < ARRAY_SIZE(views); i++) {
2530 report("Stopped loading the %s view", view->name),
2535 case REQ_SHOW_VERSION:
2536 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2539 case REQ_SCREEN_RESIZE:
2542 case REQ_SCREEN_REDRAW:
2547 report("Nothing to edit");
2552 report("Nothing to enter");
2556 case REQ_VIEW_CLOSE:
2557 /* XXX: Mark closed views by letting view->parent point to the
2558 * view itself. Parents to closed view should never be
2561 view->parent->parent != view->parent) {
2562 memset(display, 0, sizeof(display));
2564 display[current_view] = view->parent;
2565 view->parent = view;
2575 /* An unknown key will show most commonly used commands. */
2576 report("Unknown key, press 'h' for help");
2589 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2591 char *text = line->data;
2592 enum line_type type = line->type;
2593 int textlen = strlen(text);
2596 wmove(view->win, lineno, 0);
2600 wchgat(view->win, -1, 0, type, NULL);
2603 attr = get_line_attr(type);
2604 wattrset(view->win, attr);
2606 if (opt_line_number || opt_tab_size < TABSIZE) {
2607 static char spaces[] = " ";
2608 int col_offset = 0, col = 0;
2610 if (opt_line_number) {
2611 unsigned long real_lineno = view->offset + lineno + 1;
2613 if (real_lineno == 1 ||
2614 (real_lineno % opt_num_interval) == 0) {
2615 wprintw(view->win, "%.*d", view->digits, real_lineno);
2618 waddnstr(view->win, spaces,
2619 MIN(view->digits, STRING_SIZE(spaces)));
2621 waddstr(view->win, ": ");
2622 col_offset = view->digits + 2;
2625 while (text && col_offset + col < view->width) {
2626 int cols_max = view->width - col_offset - col;
2630 if (*text == '\t') {
2632 assert(sizeof(spaces) > TABSIZE);
2634 cols = opt_tab_size - (col % opt_tab_size);
2637 text = strchr(text, '\t');
2638 cols = line ? text - pos : strlen(pos);
2641 waddnstr(view->win, pos, MIN(cols, cols_max));
2646 int col = 0, pos = 0;
2648 for (; pos < textlen && col < view->width; pos++, col++)
2649 if (text[pos] == '\t')
2650 col += TABSIZE - (col % TABSIZE) - 1;
2652 waddnstr(view->win, text, pos);
2659 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2661 char refbuf[SIZEOF_STR];
2665 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2668 pipe = popen(refbuf, "r");
2672 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2673 ref = chomp_string(ref);
2679 /* This is the only fatal call, since it can "corrupt" the buffer. */
2680 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2687 add_pager_refs(struct view *view, struct line *line)
2689 char buf[SIZEOF_STR];
2690 char *commit_id = line->data + STRING_SIZE("commit ");
2692 size_t bufpos = 0, refpos = 0;
2693 const char *sep = "Refs: ";
2694 bool is_tag = FALSE;
2696 assert(line->type == LINE_COMMIT);
2698 refs = get_refs(commit_id);
2700 if (view == VIEW(REQ_VIEW_DIFF))
2701 goto try_add_describe_ref;
2706 struct ref *ref = refs[refpos];
2707 char *fmt = ref->tag ? "%s[%s]" :
2708 ref->remote ? "%s<%s>" : "%s%s";
2710 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2715 } while (refs[refpos++]->next);
2717 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2718 try_add_describe_ref:
2719 /* Add <tag>-g<commit_id> "fake" reference. */
2720 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2727 if (!realloc_lines(view, view->line_size + 1))
2730 add_line_text(view, buf, LINE_PP_REFS);
2734 pager_read(struct view *view, char *data)
2741 line = add_line_text(view, data, get_line_type(data));
2745 if (line->type == LINE_COMMIT &&
2746 (view == VIEW(REQ_VIEW_DIFF) ||
2747 view == VIEW(REQ_VIEW_LOG)))
2748 add_pager_refs(view, line);
2754 pager_request(struct view *view, enum request request, struct line *line)
2758 if (request != REQ_ENTER)
2761 if (line->type == LINE_COMMIT &&
2762 (view == VIEW(REQ_VIEW_LOG) ||
2763 view == VIEW(REQ_VIEW_PAGER))) {
2764 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2768 /* Always scroll the view even if it was split. That way
2769 * you can use Enter to scroll through the log view and
2770 * split open each commit diff. */
2771 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2773 /* FIXME: A minor workaround. Scrolling the view will call report("")
2774 * but if we are scrolling a non-current view this won't properly
2775 * update the view title. */
2777 update_view_title(view);
2783 pager_grep(struct view *view, struct line *line)
2786 char *text = line->data;
2791 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2798 pager_select(struct view *view, struct line *line)
2800 if (line->type == LINE_COMMIT) {
2801 char *text = line->data + STRING_SIZE("commit ");
2803 if (view != VIEW(REQ_VIEW_PAGER))
2804 string_copy_rev(view->ref, text);
2805 string_copy_rev(ref_commit, text);
2809 static struct view_ops pager_ops = {
2825 help_open(struct view *view)
2828 int lines = ARRAY_SIZE(req_info) + 2;
2831 if (view->lines > 0)
2834 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2835 if (!req_info[i].request)
2838 lines += run_requests + 1;
2840 view->line = calloc(lines, sizeof(*view->line));
2844 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2846 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2849 if (req_info[i].request == REQ_NONE)
2852 if (!req_info[i].request) {
2853 add_line_text(view, "", LINE_DEFAULT);
2854 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2858 key = get_key(req_info[i].request);
2860 key = "(no key defined)";
2862 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2865 add_line_text(view, buf, LINE_DEFAULT);
2869 add_line_text(view, "", LINE_DEFAULT);
2870 add_line_text(view, "External commands:", LINE_DEFAULT);
2873 for (i = 0; i < run_requests; i++) {
2874 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2880 key = get_key_name(req->key);
2882 key = "(no key defined)";
2884 if (!string_format(buf, " %-10s %-14s `%s`",
2885 keymap_table[req->keymap].name,
2889 add_line_text(view, buf, LINE_DEFAULT);
2895 static struct view_ops help_ops = {
2910 struct tree_stack_entry {
2911 struct tree_stack_entry *prev; /* Entry below this in the stack */
2912 unsigned long lineno; /* Line number to restore */
2913 char *name; /* Position of name in opt_path */
2916 /* The top of the path stack. */
2917 static struct tree_stack_entry *tree_stack = NULL;
2918 unsigned long tree_lineno = 0;
2921 pop_tree_stack_entry(void)
2923 struct tree_stack_entry *entry = tree_stack;
2925 tree_lineno = entry->lineno;
2927 tree_stack = entry->prev;
2932 push_tree_stack_entry(char *name, unsigned long lineno)
2934 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2935 size_t pathlen = strlen(opt_path);
2940 entry->prev = tree_stack;
2941 entry->name = opt_path + pathlen;
2944 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2945 pop_tree_stack_entry();
2949 /* Move the current line to the first tree entry. */
2951 entry->lineno = lineno;
2954 /* Parse output from git-ls-tree(1):
2956 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2957 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2958 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2959 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2962 #define SIZEOF_TREE_ATTR \
2963 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2965 #define TREE_UP_FORMAT "040000 tree %s\t.."
2968 tree_compare_entry(enum line_type type1, char *name1,
2969 enum line_type type2, char *name2)
2971 if (type1 != type2) {
2972 if (type1 == LINE_TREE_DIR)
2977 return strcmp(name1, name2);
2981 tree_read(struct view *view, char *text)
2983 size_t textlen = text ? strlen(text) : 0;
2984 char buf[SIZEOF_STR];
2986 enum line_type type;
2987 bool first_read = view->lines == 0;
2989 if (textlen <= SIZEOF_TREE_ATTR)
2992 type = text[STRING_SIZE("100644 ")] == 't'
2993 ? LINE_TREE_DIR : LINE_TREE_FILE;
2996 /* Add path info line */
2997 if (!string_format(buf, "Directory path /%s", opt_path) ||
2998 !realloc_lines(view, view->line_size + 1) ||
2999 !add_line_text(view, buf, LINE_DEFAULT))
3002 /* Insert "link" to parent directory. */
3004 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3005 !realloc_lines(view, view->line_size + 1) ||
3006 !add_line_text(view, buf, LINE_TREE_DIR))
3011 /* Strip the path part ... */
3013 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3014 size_t striplen = strlen(opt_path);
3015 char *path = text + SIZEOF_TREE_ATTR;
3017 if (pathlen > striplen)
3018 memmove(path, path + striplen,
3019 pathlen - striplen + 1);
3022 /* Skip "Directory ..." and ".." line. */
3023 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3024 struct line *line = &view->line[pos];
3025 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3026 char *path2 = text + SIZEOF_TREE_ATTR;
3027 int cmp = tree_compare_entry(line->type, path1, type, path2);
3032 text = strdup(text);
3036 if (view->lines > pos)
3037 memmove(&view->line[pos + 1], &view->line[pos],
3038 (view->lines - pos) * sizeof(*line));
3040 line = &view->line[pos];
3047 if (!add_line_text(view, text, type))
3050 if (tree_lineno > view->lineno) {
3051 view->lineno = tree_lineno;
3059 tree_request(struct view *view, enum request request, struct line *line)
3061 enum open_flags flags;
3063 if (request == REQ_TREE_PARENT) {
3066 request = REQ_ENTER;
3067 line = &view->line[1];
3069 /* quit view if at top of tree */
3070 return REQ_VIEW_CLOSE;
3073 if (request != REQ_ENTER)
3076 /* Cleanup the stack if the tree view is at a different tree. */
3077 while (!*opt_path && tree_stack)
3078 pop_tree_stack_entry();
3080 switch (line->type) {
3082 /* Depending on whether it is a subdir or parent (updir?) link
3083 * mangle the path buffer. */
3084 if (line == &view->line[1] && *opt_path) {
3085 pop_tree_stack_entry();
3088 char *data = line->data;
3089 char *basename = data + SIZEOF_TREE_ATTR;
3091 push_tree_stack_entry(basename, view->lineno);
3094 /* Trees and subtrees share the same ID, so they are not not
3095 * unique like blobs. */
3096 flags = OPEN_RELOAD;
3097 request = REQ_VIEW_TREE;
3100 case LINE_TREE_FILE:
3101 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3102 request = REQ_VIEW_BLOB;
3109 open_view(view, request, flags);
3110 if (request == REQ_VIEW_TREE) {
3111 view->lineno = tree_lineno;
3118 tree_select(struct view *view, struct line *line)
3120 char *text = line->data + STRING_SIZE("100644 blob ");
3122 if (line->type == LINE_TREE_FILE) {
3123 string_copy_rev(ref_blob, text);
3125 } else if (line->type != LINE_TREE_DIR) {
3129 string_copy_rev(view->ref, text);
3132 static struct view_ops tree_ops = {
3143 blob_read(struct view *view, char *line)
3145 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3148 static struct view_ops blob_ops = {
3167 char rev[SIZEOF_REV];
3171 char rev[SIZEOF_REV];
3173 char name[SIZEOF_STR];
3176 static struct status stage_status;
3177 static enum line_type stage_line_type;
3179 /* Get fields from the diff line:
3180 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3183 status_get_diff(struct status *file, char *buf, size_t bufsize)
3185 char *old_mode = buf + 1;
3186 char *new_mode = buf + 8;
3187 char *old_rev = buf + 15;
3188 char *new_rev = buf + 56;
3189 char *status = buf + 97;
3191 if (bufsize != 99 ||
3192 old_mode[-1] != ':' ||
3193 new_mode[-1] != ' ' ||
3194 old_rev[-1] != ' ' ||
3195 new_rev[-1] != ' ' ||
3199 file->status = *status;
3201 string_copy_rev(file->old.rev, old_rev);
3202 string_copy_rev(file->new.rev, new_rev);
3204 file->old.mode = strtoul(old_mode, NULL, 8);
3205 file->new.mode = strtoul(new_mode, NULL, 8);
3213 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3215 struct status *file = NULL;
3216 struct status *unmerged = NULL;
3217 char buf[SIZEOF_STR * 4];
3221 pipe = popen(cmd, "r");
3225 add_line_data(view, NULL, type);
3227 while (!feof(pipe) && !ferror(pipe)) {
3231 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3234 bufsize += readsize;
3236 /* Process while we have NUL chars. */
3237 while ((sep = memchr(buf, 0, bufsize))) {
3238 size_t sepsize = sep - buf + 1;
3241 if (!realloc_lines(view, view->line_size + 1))
3244 file = calloc(1, sizeof(*file));
3248 add_line_data(view, file, type);
3251 /* Parse diff info part. */
3255 } else if (!file->status) {
3256 if (!status_get_diff(file, buf, sepsize))
3260 memmove(buf, sep + 1, bufsize);
3262 sep = memchr(buf, 0, bufsize);
3265 sepsize = sep - buf + 1;
3267 /* Collapse all 'M'odified entries that
3268 * follow a associated 'U'nmerged entry.
3270 if (file->status == 'U') {
3273 } else if (unmerged) {
3274 int collapse = !strcmp(buf, unmerged->name);
3285 /* git-ls-files just delivers a NUL separated
3286 * list of file names similar to the second half
3287 * of the git-diff-* output. */
3288 string_ncopy(file->name, buf, sepsize);
3290 memmove(buf, sep + 1, bufsize);
3301 if (!view->line[view->lines - 1].data)
3302 add_line_data(view, NULL, LINE_STAT_NONE);
3308 /* Don't show unmerged entries in the staged section. */
3309 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3310 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3311 #define STATUS_LIST_OTHER_CMD \
3312 "git ls-files -z --others --exclude-per-directory=.gitignore"
3314 #define STATUS_DIFF_INDEX_SHOW_CMD \
3315 "git diff-index --root --patch-with-stat --find-copies-harder -C --cached HEAD -- %s 2>/dev/null"
3317 #define STATUS_DIFF_FILES_SHOW_CMD \
3318 "git diff-files --root --patch-with-stat --find-copies-harder -C -- %s 2>/dev/null"
3320 /* First parse staged info using git-diff-index(1), then parse unstaged
3321 * info using git-diff-files(1), and finally untracked files using
3322 * git-ls-files(1). */
3324 status_open(struct view *view)
3326 struct stat statbuf;
3327 char exclude[SIZEOF_STR];
3328 char cmd[SIZEOF_STR];
3329 unsigned long prev_lineno = view->lineno;
3332 for (i = 0; i < view->lines; i++)
3333 free(view->line[i].data);
3335 view->lines = view->line_size = view->lineno = 0;
3338 if (!realloc_lines(view, view->line_size + 6))
3341 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3344 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3346 if (stat(exclude, &statbuf) >= 0) {
3347 size_t cmdsize = strlen(cmd);
3349 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3350 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3354 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3355 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3356 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3359 /* If all went well restore the previous line number to stay in
3361 if (prev_lineno < view->lines)
3362 view->lineno = prev_lineno;
3364 view->lineno = view->lines - 1;
3370 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3372 struct status *status = line->data;
3374 wmove(view->win, lineno, 0);
3377 wattrset(view->win, get_line_attr(LINE_CURSOR));
3378 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3380 } else if (!status && line->type != LINE_STAT_NONE) {
3381 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3382 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3385 wattrset(view->win, get_line_attr(line->type));
3391 switch (line->type) {
3392 case LINE_STAT_STAGED:
3393 text = "Changes to be committed:";
3396 case LINE_STAT_UNSTAGED:
3397 text = "Changed but not updated:";
3400 case LINE_STAT_UNTRACKED:
3401 text = "Untracked files:";
3404 case LINE_STAT_NONE:
3405 text = " (no files)";
3412 waddstr(view->win, text);
3416 waddch(view->win, status->status);
3418 wattrset(view->win, A_NORMAL);
3419 wmove(view->win, lineno, 4);
3420 waddstr(view->win, status->name);
3426 status_enter(struct view *view, struct line *line)
3428 struct status *status = line->data;
3429 char path[SIZEOF_STR] = "";
3433 if (line->type == LINE_STAT_NONE ||
3434 (!status && line[1].type == LINE_STAT_NONE)) {
3435 report("No file to diff");
3439 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3443 line->type != LINE_STAT_UNTRACKED &&
3444 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3447 switch (line->type) {
3448 case LINE_STAT_STAGED:
3449 if (!string_format_from(opt_cmd, &cmdsize,
3450 STATUS_DIFF_INDEX_SHOW_CMD, path))
3453 info = "Staged changes to %s";
3455 info = "Staged changes";
3458 case LINE_STAT_UNSTAGED:
3459 if (!string_format_from(opt_cmd, &cmdsize,
3460 STATUS_DIFF_FILES_SHOW_CMD, path))
3463 info = "Unstaged changes to %s";
3465 info = "Unstaged changes";
3468 case LINE_STAT_UNTRACKED:
3474 report("No file to show");
3478 opt_pipe = fopen(status->name, "r");
3479 info = "Untracked file %s";
3483 die("line type %d not handled in switch", line->type);
3486 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3487 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3489 stage_status = *status;
3491 memset(&stage_status, 0, sizeof(stage_status));
3494 stage_line_type = line->type;
3495 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3503 status_update_file(struct view *view, struct status *status, enum line_type type)
3505 char cmd[SIZEOF_STR];
3506 char buf[SIZEOF_STR];
3513 type != LINE_STAT_UNTRACKED &&
3514 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3518 case LINE_STAT_STAGED:
3519 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3525 string_add(cmd, cmdsize, "git update-index -z --index-info");
3528 case LINE_STAT_UNSTAGED:
3529 case LINE_STAT_UNTRACKED:
3530 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3533 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3537 die("line type %d not handled in switch", type);
3540 pipe = popen(cmd, "w");
3544 while (!ferror(pipe) && written < bufsize) {
3545 written += fwrite(buf + written, 1, bufsize - written, pipe);
3550 if (written != bufsize)
3557 status_update(struct view *view)
3559 struct line *line = &view->line[view->lineno];
3561 assert(view->lines);
3564 while (++line < view->line + view->lines && line->data) {
3565 if (!status_update_file(view, line->data, line->type))
3566 report("Failed to update file status");
3569 if (!line[-1].data) {
3570 report("Nothing to update");
3574 } else if (!status_update_file(view, line->data, line->type)) {
3575 report("Failed to update file status");
3580 status_request(struct view *view, enum request request, struct line *line)
3582 struct status *status = line->data;
3585 case REQ_STATUS_UPDATE:
3586 status_update(view);
3589 case REQ_STATUS_MERGE:
3590 if (!status || status->status != 'U') {
3591 report("Merging only possible for files with unmerged status ('U').");
3594 open_mergetool(status->name);
3601 open_editor(status->status != '?', status->name);
3605 /* After returning the status view has been split to
3606 * show the stage view. No further reloading is
3608 status_enter(view, line);
3612 /* Simply reload the view. */
3619 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3625 status_select(struct view *view, struct line *line)
3627 struct status *status = line->data;
3628 char file[SIZEOF_STR] = "all files";
3632 if (status && !string_format(file, "'%s'", status->name))
3635 if (!status && line[1].type == LINE_STAT_NONE)
3638 switch (line->type) {
3639 case LINE_STAT_STAGED:
3640 text = "Press %s to unstage %s for commit";
3643 case LINE_STAT_UNSTAGED:
3644 text = "Press %s to stage %s for commit";
3647 case LINE_STAT_UNTRACKED:
3648 text = "Press %s to stage %s for addition";
3651 case LINE_STAT_NONE:
3652 text = "Nothing to update";
3656 die("line type %d not handled in switch", line->type);
3659 if (status && status->status == 'U') {
3660 text = "Press %s to resolve conflict in %s";
3661 key = get_key(REQ_STATUS_MERGE);
3664 key = get_key(REQ_STATUS_UPDATE);
3667 string_format(view->ref, text, key, file);
3671 status_grep(struct view *view, struct line *line)
3673 struct status *status = line->data;
3674 enum { S_STATUS, S_NAME, S_END } state;
3681 for (state = S_STATUS; state < S_END; state++) {
3685 case S_NAME: text = status->name; break;
3687 buf[0] = status->status;
3695 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3702 static struct view_ops status_ops = {
3714 stage_diff_line(FILE *pipe, struct line *line)
3716 char *buf = line->data;
3717 size_t bufsize = strlen(buf);
3720 while (!ferror(pipe) && written < bufsize) {
3721 written += fwrite(buf + written, 1, bufsize - written, pipe);
3726 return written == bufsize;
3729 static struct line *
3730 stage_diff_hdr(struct view *view, struct line *line)
3732 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3733 struct line *diff_hdr;
3735 if (line->type == LINE_DIFF_CHUNK)
3736 diff_hdr = line - 1;
3738 diff_hdr = view->line + 1;
3740 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3741 if (diff_hdr->type == LINE_DIFF_HEADER)
3744 diff_hdr += diff_hdr_dir;
3751 stage_update_chunk(struct view *view, struct line *line)
3753 char cmd[SIZEOF_STR];
3755 struct line *diff_hdr, *diff_chunk, *diff_end;
3758 diff_hdr = stage_diff_hdr(view, line);
3763 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3766 if (!string_format_from(cmd, &cmdsize,
3767 "git apply --cached %s - && "
3768 "git update-index -q --unmerged --refresh 2>/dev/null",
3769 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3772 pipe = popen(cmd, "w");
3776 diff_end = view->line + view->lines;
3777 if (line->type != LINE_DIFF_CHUNK) {
3778 diff_chunk = diff_hdr;
3781 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3782 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3783 diff_chunk->type == LINE_DIFF_HEADER)
3784 diff_end = diff_chunk;
3788 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3789 switch (diff_hdr->type) {
3790 case LINE_DIFF_HEADER:
3791 case LINE_DIFF_INDEX:
3801 if (!stage_diff_line(pipe, diff_hdr++)) {
3808 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3813 if (diff_chunk != diff_end)
3820 stage_update(struct view *view, struct line *line)
3822 if (stage_line_type != LINE_STAT_UNTRACKED &&
3823 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3824 if (!stage_update_chunk(view, line)) {
3825 report("Failed to apply chunk");
3829 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3830 report("Failed to update file");
3834 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3836 view = VIEW(REQ_VIEW_STATUS);
3837 if (view_is_displayed(view))
3838 status_enter(view, &view->line[view->lineno]);
3842 stage_request(struct view *view, enum request request, struct line *line)
3845 case REQ_STATUS_UPDATE:
3846 stage_update(view, line);
3850 if (!stage_status.name[0])
3853 open_editor(stage_status.status != '?', stage_status.name);
3857 pager_request(view, request, line);
3867 static struct view_ops stage_ops = {
3883 char id[SIZEOF_REV]; /* SHA1 ID. */
3884 char title[128]; /* First line of the commit message. */
3885 char author[75]; /* Author of the commit. */
3886 struct tm time; /* Date from the author ident. */
3887 struct ref **refs; /* Repository references. */
3888 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3889 size_t graph_size; /* The width of the graph array. */
3892 /* Size of rev graph with no "padding" columns */
3893 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3896 struct rev_graph *prev, *next, *parents;
3897 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3899 struct commit *commit;
3901 unsigned int boundary:1;
3904 /* Parents of the commit being visualized. */
3905 static struct rev_graph graph_parents[4];
3907 /* The current stack of revisions on the graph. */
3908 static struct rev_graph graph_stacks[4] = {
3909 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3910 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3911 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3912 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3916 graph_parent_is_merge(struct rev_graph *graph)
3918 return graph->parents->size > 1;
3922 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3924 struct commit *commit = graph->commit;
3926 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3927 commit->graph[commit->graph_size++] = symbol;
3931 done_rev_graph(struct rev_graph *graph)
3933 if (graph_parent_is_merge(graph) &&
3934 graph->pos < graph->size - 1 &&
3935 graph->next->size == graph->size + graph->parents->size - 1) {
3936 size_t i = graph->pos + graph->parents->size - 1;
3938 graph->commit->graph_size = i * 2;
3939 while (i < graph->next->size - 1) {
3940 append_to_rev_graph(graph, ' ');
3941 append_to_rev_graph(graph, '\\');
3946 graph->size = graph->pos = 0;
3947 graph->commit = NULL;
3948 memset(graph->parents, 0, sizeof(*graph->parents));
3952 push_rev_graph(struct rev_graph *graph, char *parent)
3956 /* "Collapse" duplicate parents lines.
3958 * FIXME: This needs to also update update the drawn graph but
3959 * for now it just serves as a method for pruning graph lines. */
3960 for (i = 0; i < graph->size; i++)
3961 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3964 if (graph->size < SIZEOF_REVITEMS) {
3965 string_copy_rev(graph->rev[graph->size++], parent);
3970 get_rev_graph_symbol(struct rev_graph *graph)
3974 if (graph->boundary)
3975 symbol = REVGRAPH_BOUND;
3976 else if (graph->parents->size == 0)
3977 symbol = REVGRAPH_INIT;
3978 else if (graph_parent_is_merge(graph))
3979 symbol = REVGRAPH_MERGE;
3980 else if (graph->pos >= graph->size)
3981 symbol = REVGRAPH_BRANCH;
3983 symbol = REVGRAPH_COMMIT;
3989 draw_rev_graph(struct rev_graph *graph)
3992 chtype separator, line;
3994 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3995 static struct rev_filler fillers[] = {
3996 { ' ', REVGRAPH_LINE },
4001 chtype symbol = get_rev_graph_symbol(graph);
4002 struct rev_filler *filler;
4005 filler = &fillers[DEFAULT];
4007 for (i = 0; i < graph->pos; i++) {
4008 append_to_rev_graph(graph, filler->line);
4009 if (graph_parent_is_merge(graph->prev) &&
4010 graph->prev->pos == i)
4011 filler = &fillers[RSHARP];
4013 append_to_rev_graph(graph, filler->separator);
4016 /* Place the symbol for this revision. */
4017 append_to_rev_graph(graph, symbol);
4019 if (graph->prev->size > graph->size)
4020 filler = &fillers[RDIAG];
4022 filler = &fillers[DEFAULT];
4026 for (; i < graph->size; i++) {
4027 append_to_rev_graph(graph, filler->separator);
4028 append_to_rev_graph(graph, filler->line);
4029 if (graph_parent_is_merge(graph->prev) &&
4030 i < graph->prev->pos + graph->parents->size)
4031 filler = &fillers[RSHARP];
4032 if (graph->prev->size > graph->size)
4033 filler = &fillers[LDIAG];
4036 if (graph->prev->size > graph->size) {
4037 append_to_rev_graph(graph, filler->separator);
4038 if (filler->line != ' ')
4039 append_to_rev_graph(graph, filler->line);
4043 /* Prepare the next rev graph */
4045 prepare_rev_graph(struct rev_graph *graph)
4049 /* First, traverse all lines of revisions up to the active one. */
4050 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4051 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4054 push_rev_graph(graph->next, graph->rev[graph->pos]);
4057 /* Interleave the new revision parent(s). */
4058 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4059 push_rev_graph(graph->next, graph->parents->rev[i]);
4061 /* Lastly, put any remaining revisions. */
4062 for (i = graph->pos + 1; i < graph->size; i++)
4063 push_rev_graph(graph->next, graph->rev[i]);
4067 update_rev_graph(struct rev_graph *graph)
4069 /* If this is the finalizing update ... */
4071 prepare_rev_graph(graph);
4073 /* Graph visualization needs a one rev look-ahead,
4074 * so the first update doesn't visualize anything. */
4075 if (!graph->prev->commit)
4078 draw_rev_graph(graph->prev);
4079 done_rev_graph(graph->prev->prev);
4088 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4090 char buf[DATE_COLS + 1];
4091 struct commit *commit = line->data;
4092 enum line_type type;
4098 if (!*commit->author)
4101 wmove(view->win, lineno, col);
4105 wattrset(view->win, get_line_attr(type));
4106 wchgat(view->win, -1, 0, type, NULL);
4109 type = LINE_MAIN_COMMIT;
4110 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4113 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4114 waddnstr(view->win, buf, timelen);
4115 waddstr(view->win, " ");
4118 wmove(view->win, lineno, col);
4119 if (type != LINE_CURSOR)
4120 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4123 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
4125 authorlen = strlen(commit->author);
4126 if (authorlen > AUTHOR_COLS - 2) {
4127 authorlen = AUTHOR_COLS - 2;
4133 waddnstr(view->win, commit->author, authorlen);
4134 if (type != LINE_CURSOR)
4135 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
4136 waddch(view->win, '~');
4138 waddstr(view->win, commit->author);
4143 if (opt_rev_graph && commit->graph_size) {
4146 if (type != LINE_CURSOR)
4147 wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4148 wmove(view->win, lineno, col);
4149 /* Using waddch() instead of waddnstr() ensures that
4150 * they'll be rendered correctly for the cursor line. */
4151 for (i = 0; i < commit->graph_size; i++)
4152 waddch(view->win, commit->graph[i]);
4154 waddch(view->win, ' ');
4155 col += commit->graph_size + 1;
4157 if (type != LINE_CURSOR)
4158 wattrset(view->win, A_NORMAL);
4160 wmove(view->win, lineno, col);
4166 if (type == LINE_CURSOR)
4168 else if (commit->refs[i]->tag)
4169 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4170 else if (commit->refs[i]->remote)
4171 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4173 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4174 waddstr(view->win, "[");
4175 waddstr(view->win, commit->refs[i]->name);
4176 waddstr(view->win, "]");
4177 if (type != LINE_CURSOR)
4178 wattrset(view->win, A_NORMAL);
4179 waddstr(view->win, " ");
4180 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
4181 } while (commit->refs[i++]->next);
4184 if (type != LINE_CURSOR)
4185 wattrset(view->win, get_line_attr(type));
4188 int titlelen = strlen(commit->title);
4190 if (col + titlelen > view->width)
4191 titlelen = view->width - col;
4193 waddnstr(view->win, commit->title, titlelen);
4199 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4201 main_read(struct view *view, char *line)
4203 static struct rev_graph *graph = graph_stacks;
4204 enum line_type type;
4205 struct commit *commit;
4208 update_rev_graph(graph);
4212 type = get_line_type(line);
4213 if (type == LINE_COMMIT) {
4214 commit = calloc(1, sizeof(struct commit));
4218 line += STRING_SIZE("commit ");
4220 graph->boundary = 1;
4224 string_copy_rev(commit->id, line);
4225 commit->refs = get_refs(commit->id);
4226 graph->commit = commit;
4227 add_line_data(view, commit, LINE_MAIN_COMMIT);
4233 commit = view->line[view->lines - 1].data;
4237 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4242 /* Parse author lines where the name may be empty:
4243 * author <email@address.tld> 1138474660 +0100
4245 char *ident = line + STRING_SIZE("author ");
4246 char *nameend = strchr(ident, '<');
4247 char *emailend = strchr(ident, '>');
4249 if (!nameend || !emailend)
4252 update_rev_graph(graph);
4253 graph = graph->next;
4255 *nameend = *emailend = 0;
4256 ident = chomp_string(ident);
4258 ident = chomp_string(nameend + 1);
4263 string_ncopy(commit->author, ident, strlen(ident));
4265 /* Parse epoch and timezone */
4266 if (emailend[1] == ' ') {
4267 char *secs = emailend + 2;
4268 char *zone = strchr(secs, ' ');
4269 time_t time = (time_t) atol(secs);
4271 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4275 tz = ('0' - zone[1]) * 60 * 60 * 10;
4276 tz += ('0' - zone[2]) * 60 * 60;
4277 tz += ('0' - zone[3]) * 60;
4278 tz += ('0' - zone[4]) * 60;
4286 gmtime_r(&time, &commit->time);
4291 /* Fill in the commit title if it has not already been set. */
4292 if (commit->title[0])
4295 /* Require titles to start with a non-space character at the
4296 * offset used by git log. */
4297 if (strncmp(line, " ", 4))
4300 /* Well, if the title starts with a whitespace character,
4301 * try to be forgiving. Otherwise we end up with no title. */
4302 while (isspace(*line))
4306 /* FIXME: More graceful handling of titles; append "..." to
4307 * shortened titles, etc. */
4309 string_ncopy(commit->title, line, strlen(line));
4316 main_request(struct view *view, enum request request, struct line *line)
4318 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4320 if (request == REQ_ENTER)
4321 open_view(view, REQ_VIEW_DIFF, flags);
4329 main_grep(struct view *view, struct line *line)
4331 struct commit *commit = line->data;
4332 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4333 char buf[DATE_COLS + 1];
4336 for (state = S_TITLE; state < S_END; state++) {
4340 case S_TITLE: text = commit->title; break;
4341 case S_AUTHOR: text = commit->author; break;
4343 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4352 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4360 main_select(struct view *view, struct line *line)
4362 struct commit *commit = line->data;
4364 string_copy_rev(view->ref, commit->id);
4365 string_copy_rev(ref_commit, view->ref);
4368 static struct view_ops main_ops = {
4380 * Unicode / UTF-8 handling
4382 * NOTE: Much of the following code for dealing with unicode is derived from
4383 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4384 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4387 /* I've (over)annotated a lot of code snippets because I am not entirely
4388 * confident that the approach taken by this small UTF-8 interface is correct.
4392 unicode_width(unsigned long c)
4395 (c <= 0x115f /* Hangul Jamo */
4398 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4400 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4401 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4402 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4403 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4404 || (c >= 0xffe0 && c <= 0xffe6)
4405 || (c >= 0x20000 && c <= 0x2fffd)
4406 || (c >= 0x30000 && c <= 0x3fffd)))
4412 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4413 * Illegal bytes are set one. */
4414 static const unsigned char utf8_bytes[256] = {
4415 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,
4416 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,
4417 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,
4418 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,
4419 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,
4420 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,
4421 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,
4422 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,
4425 /* Decode UTF-8 multi-byte representation into a unicode character. */
4426 static inline unsigned long
4427 utf8_to_unicode(const char *string, size_t length)
4429 unsigned long unicode;
4433 unicode = string[0];
4436 unicode = (string[0] & 0x1f) << 6;
4437 unicode += (string[1] & 0x3f);
4440 unicode = (string[0] & 0x0f) << 12;
4441 unicode += ((string[1] & 0x3f) << 6);
4442 unicode += (string[2] & 0x3f);
4445 unicode = (string[0] & 0x0f) << 18;
4446 unicode += ((string[1] & 0x3f) << 12);
4447 unicode += ((string[2] & 0x3f) << 6);
4448 unicode += (string[3] & 0x3f);
4451 unicode = (string[0] & 0x0f) << 24;
4452 unicode += ((string[1] & 0x3f) << 18);
4453 unicode += ((string[2] & 0x3f) << 12);
4454 unicode += ((string[3] & 0x3f) << 6);
4455 unicode += (string[4] & 0x3f);
4458 unicode = (string[0] & 0x01) << 30;
4459 unicode += ((string[1] & 0x3f) << 24);
4460 unicode += ((string[2] & 0x3f) << 18);
4461 unicode += ((string[3] & 0x3f) << 12);
4462 unicode += ((string[4] & 0x3f) << 6);
4463 unicode += (string[5] & 0x3f);
4466 die("Invalid unicode length");
4469 /* Invalid characters could return the special 0xfffd value but NUL
4470 * should be just as good. */
4471 return unicode > 0xffff ? 0 : unicode;
4474 /* Calculates how much of string can be shown within the given maximum width
4475 * and sets trimmed parameter to non-zero value if all of string could not be
4478 * Additionally, adds to coloffset how many many columns to move to align with
4479 * the expected position. Takes into account how multi-byte and double-width
4480 * characters will effect the cursor position.
4482 * Returns the number of bytes to output from string to satisfy max_width. */
4484 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4486 const char *start = string;
4487 const char *end = strchr(string, '\0');
4493 while (string < end) {
4494 int c = *(unsigned char *) string;
4495 unsigned char bytes = utf8_bytes[c];
4497 unsigned long unicode;
4499 if (string + bytes > end)
4502 /* Change representation to figure out whether
4503 * it is a single- or double-width character. */
4505 unicode = utf8_to_unicode(string, bytes);
4506 /* FIXME: Graceful handling of invalid unicode character. */
4510 ucwidth = unicode_width(unicode);
4512 if (width > max_width) {
4517 /* The column offset collects the differences between the
4518 * number of bytes encoding a character and the number of
4519 * columns will be used for rendering said character.
4521 * So if some character A is encoded in 2 bytes, but will be
4522 * represented on the screen using only 1 byte this will and up
4523 * adding 1 to the multi-byte column offset.
4525 * Assumes that no double-width character can be encoding in
4526 * less than two bytes. */
4527 if (bytes > ucwidth)
4528 mbwidth += bytes - ucwidth;
4533 *coloffset += mbwidth;
4535 return string - start;
4543 /* Whether or not the curses interface has been initialized. */
4544 static bool cursed = FALSE;
4546 /* The status window is used for polling keystrokes. */
4547 static WINDOW *status_win;
4549 static bool status_empty = TRUE;
4551 /* Update status and title window. */
4553 report(const char *msg, ...)
4555 struct view *view = display[current_view];
4561 char buf[SIZEOF_STR];
4564 va_start(args, msg);
4565 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4566 buf[sizeof(buf) - 1] = 0;
4567 buf[sizeof(buf) - 2] = '.';
4568 buf[sizeof(buf) - 3] = '.';
4569 buf[sizeof(buf) - 4] = '.';
4575 if (!status_empty || *msg) {
4578 va_start(args, msg);
4580 wmove(status_win, 0, 0);
4582 vwprintw(status_win, msg, args);
4583 status_empty = FALSE;
4585 status_empty = TRUE;
4587 wclrtoeol(status_win);
4588 wrefresh(status_win);
4593 update_view_title(view);
4594 update_display_cursor(view);
4597 /* Controls when nodelay should be in effect when polling user input. */
4599 set_nonblocking_input(bool loading)
4601 static unsigned int loading_views;
4603 if ((loading == FALSE && loading_views-- == 1) ||
4604 (loading == TRUE && loading_views++ == 0))
4605 nodelay(status_win, loading);
4613 /* Initialize the curses library */
4614 if (isatty(STDIN_FILENO)) {
4615 cursed = !!initscr();
4617 /* Leave stdin and stdout alone when acting as a pager. */
4618 FILE *io = fopen("/dev/tty", "r+");
4621 die("Failed to open /dev/tty");
4622 cursed = !!newterm(NULL, io, io);
4626 die("Failed to initialize curses");
4628 nonl(); /* Tell curses not to do NL->CR/NL on output */
4629 cbreak(); /* Take input chars one at a time, no wait for \n */
4630 noecho(); /* Don't echo input */
4631 leaveok(stdscr, TRUE);
4636 getmaxyx(stdscr, y, x);
4637 status_win = newwin(1, 0, y - 1, 0);
4639 die("Failed to create status window");
4641 /* Enable keyboard mapping */
4642 keypad(status_win, TRUE);
4643 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4647 read_prompt(const char *prompt)
4649 enum { READING, STOP, CANCEL } status = READING;
4650 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4653 while (status == READING) {
4659 foreach_view (view, i)
4664 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4665 wclrtoeol(status_win);
4667 /* Refresh, accept single keystroke of input */
4668 key = wgetch(status_win);
4673 status = pos ? STOP : CANCEL;
4691 if (pos >= sizeof(buf)) {
4692 report("Input string too long");
4697 buf[pos++] = (char) key;
4701 /* Clear the status window */
4702 status_empty = FALSE;
4705 if (status == CANCEL)
4714 * Repository references
4717 static struct ref *refs;
4718 static size_t refs_size;
4720 /* Id <-> ref store */
4721 static struct ref ***id_refs;
4722 static size_t id_refs_size;
4724 static struct ref **
4727 struct ref ***tmp_id_refs;
4728 struct ref **ref_list = NULL;
4729 size_t ref_list_size = 0;
4732 for (i = 0; i < id_refs_size; i++)
4733 if (!strcmp(id, id_refs[i][0]->id))
4736 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4740 id_refs = tmp_id_refs;
4742 for (i = 0; i < refs_size; i++) {
4745 if (strcmp(id, refs[i].id))
4748 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4756 if (ref_list_size > 0)
4757 ref_list[ref_list_size - 1]->next = 1;
4758 ref_list[ref_list_size] = &refs[i];
4760 /* XXX: The properties of the commit chains ensures that we can
4761 * safely modify the shared ref. The repo references will
4762 * always be similar for the same id. */
4763 ref_list[ref_list_size]->next = 0;
4768 id_refs[id_refs_size++] = ref_list;
4774 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4778 bool remote = FALSE;
4780 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4781 /* Commits referenced by tags has "^{}" appended. */
4782 if (name[namelen - 1] != '}')
4785 while (namelen > 0 && name[namelen] != '^')
4789 namelen -= STRING_SIZE("refs/tags/");
4790 name += STRING_SIZE("refs/tags/");
4792 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4794 namelen -= STRING_SIZE("refs/remotes/");
4795 name += STRING_SIZE("refs/remotes/");
4797 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4798 namelen -= STRING_SIZE("refs/heads/");
4799 name += STRING_SIZE("refs/heads/");
4801 } else if (!strcmp(name, "HEAD")) {
4805 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4809 ref = &refs[refs_size++];
4810 ref->name = malloc(namelen + 1);
4814 strncpy(ref->name, name, namelen);
4815 ref->name[namelen] = 0;
4817 ref->remote = remote;
4818 string_copy_rev(ref->id, id);
4826 const char *cmd_env = getenv("TIG_LS_REMOTE");
4827 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4829 return read_properties(popen(cmd, "r"), "\t", read_ref);
4833 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4835 if (!strcmp(name, "i18n.commitencoding"))
4836 string_ncopy(opt_encoding, value, valuelen);
4838 if (!strcmp(name, "core.editor"))
4839 string_ncopy(opt_editor, value, valuelen);
4845 load_repo_config(void)
4847 return read_properties(popen(GIT_CONFIG " --list", "r"),
4848 "=", read_repo_config_option);
4852 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4854 if (!opt_git_dir[0]) {
4855 string_ncopy(opt_git_dir, name, namelen);
4857 } else if (opt_is_inside_work_tree == -1) {
4858 /* This can be 3 different values depending on the
4859 * version of git being used. If git-rev-parse does not
4860 * understand --is-inside-work-tree it will simply echo
4861 * the option else either "true" or "false" is printed.
4862 * Default to true for the unknown case. */
4863 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4866 string_ncopy(opt_cdup, name, namelen);
4872 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4873 * must be the last one! */
4875 load_repo_info(void)
4877 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4878 "=", read_repo_info);
4882 read_properties(FILE *pipe, const char *separators,
4883 int (*read_property)(char *, size_t, char *, size_t))
4885 char buffer[BUFSIZ];
4892 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4897 name = chomp_string(name);
4898 namelen = strcspn(name, separators);
4900 if (name[namelen]) {
4902 value = chomp_string(name + namelen + 1);
4903 valuelen = strlen(value);
4910 state = read_property(name, namelen, value, valuelen);
4913 if (state != ERR && ferror(pipe))
4926 static void __NORETURN
4929 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4935 static void __NORETURN
4936 die(const char *err, ...)
4942 va_start(args, err);
4943 fputs("tig: ", stderr);
4944 vfprintf(stderr, err, args);
4945 fputs("\n", stderr);
4952 warn(const char *msg, ...)
4956 va_start(args, msg);
4957 fputs("tig warning: ", stderr);
4958 vfprintf(stderr, msg, args);
4959 fputs("\n", stderr);
4964 main(int argc, char *argv[])
4967 enum request request;
4970 signal(SIGINT, quit);
4972 if (setlocale(LC_ALL, "")) {
4973 char *codeset = nl_langinfo(CODESET);
4975 string_ncopy(opt_codeset, codeset, strlen(codeset));
4978 if (load_repo_info() == ERR)
4979 die("Failed to load repo info.");
4981 if (load_options() == ERR)
4982 die("Failed to load user config.");
4984 /* Load the repo config file so options can be overwritten from
4985 * the command line. */
4986 if (load_repo_config() == ERR)
4987 die("Failed to load repo config.");
4989 if (!parse_options(argc, argv))
4992 /* Require a git repository unless when running in pager mode. */
4993 if (!opt_git_dir[0])
4994 die("Not a git repository");
4996 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4997 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4998 if (opt_iconv == ICONV_NONE)
4999 die("Failed to initialize character set conversion");
5002 if (load_refs() == ERR)
5003 die("Failed to load refs.");
5005 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5006 view->cmd_env = getenv(view->cmd_env);
5008 request = opt_request;
5012 while (view_driver(display[current_view], request)) {
5016 foreach_view (view, i)
5019 /* Refresh, accept single keystroke of input */
5020 key = wgetch(status_win);
5022 /* wgetch() with nodelay() enabled returns ERR when there's no
5029 request = get_keybinding(display[current_view]->keymap, key);
5031 /* Some low-level request handling. This keeps access to
5032 * status_win restricted. */
5036 char *cmd = read_prompt(":");
5038 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5039 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5040 opt_request = REQ_VIEW_DIFF;
5042 opt_request = REQ_VIEW_PAGER;
5051 case REQ_SEARCH_BACK:
5053 const char *prompt = request == REQ_SEARCH
5055 char *search = read_prompt(prompt);
5058 string_ncopy(opt_search, search, strlen(search));
5063 case REQ_SCREEN_RESIZE:
5067 getmaxyx(stdscr, height, width);
5069 /* Resize the status view and let the view driver take
5070 * care of resizing the displayed views. */
5071 wresize(status_win, 1, width);
5072 mvwin(status_win, height - 1, 0);
5073 wrefresh(status_win);