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 report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
59 #define ABS(x) ((x) >= 0 ? (x) : -(x))
60 #define MIN(x, y) ((x) < (y) ? (x) : (y))
62 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x) (sizeof(x) - 1)
65 #define SIZEOF_STR 1024 /* Default string size. */
66 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
71 #define REVGRAPH_INIT 'I'
72 #define REVGRAPH_MERGE 'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE '|'
77 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT (-1)
82 #define ICONV_NONE ((iconv_t) -1)
84 #define ICONV_CONST /* nothing */
87 /* The format and size of the date column in the main view. */
88 #define DATE_FORMAT "%Y-%m-%d %H:%M"
89 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
91 #define AUTHOR_COLS 20
93 /* The default interval between line numbers. */
94 #define NUMBER_INTERVAL 1
98 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
101 #define GIT_CONFIG "git config"
104 #define TIG_LS_REMOTE \
105 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
107 #define TIG_DIFF_CMD \
108 "git show --no-color --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
110 #define TIG_LOG_CMD \
111 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
113 #define TIG_MAIN_CMD \
114 "git log --no-color --topo-order --pretty=raw %s 2>/dev/null"
116 #define TIG_TREE_CMD \
119 #define TIG_BLOB_CMD \
120 "git cat-file blob %s"
122 /* XXX: Needs to be defined to the empty string. */
123 #define TIG_HELP_CMD ""
124 #define TIG_PAGER_CMD ""
125 #define TIG_STATUS_CMD ""
126 #define TIG_STAGE_CMD ""
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
130 #define KEY_RETURN '\r'
135 char *name; /* Ref name; tag or head names are shortened. */
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 unsigned int tag:1; /* Is it a tag? */
138 unsigned int remote:1; /* Is it a remote ref? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
142 static struct ref **get_refs(char *id);
151 set_from_int_map(struct int_map *map, size_t map_size,
152 int *value, const char *name, int namelen)
157 for (i = 0; i < map_size; i++)
158 if (namelen == map[i].namelen &&
159 !strncasecmp(name, map[i].name, namelen)) {
160 *value = map[i].value;
173 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
175 if (srclen > dstlen - 1)
178 strncpy(dst, src, srclen);
182 /* Shorthands for safely copying into a fixed buffer. */
184 #define string_copy(dst, src) \
185 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
187 #define string_ncopy(dst, src, srclen) \
188 string_ncopy_do(dst, sizeof(dst), src, srclen)
190 #define string_copy_rev(dst, src) \
191 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
193 #define string_add(dst, from, src) \
194 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
197 chomp_string(char *name)
201 while (isspace(*name))
204 namelen = strlen(name) - 1;
205 while (namelen > 0 && isspace(name[namelen]))
212 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
215 size_t pos = bufpos ? *bufpos : 0;
218 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
224 return pos >= bufsize ? FALSE : TRUE;
227 #define string_format(buf, fmt, args...) \
228 string_nformat(buf, sizeof(buf), NULL, fmt, args)
230 #define string_format_from(buf, from, fmt, args...) \
231 string_nformat(buf, sizeof(buf), from, fmt, args)
234 string_enum_compare(const char *str1, const char *str2, int len)
238 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
240 /* Diff-Header == DIFF_HEADER */
241 for (i = 0; i < len; i++) {
242 if (toupper(str1[i]) == toupper(str2[i]))
245 if (string_enum_sep(str1[i]) &&
246 string_enum_sep(str2[i]))
249 return str1[i] - str2[i];
257 * NOTE: The following is a slightly modified copy of the git project's shell
258 * quoting routines found in the quote.c file.
260 * Help to copy the thing properly quoted for the shell safety. any single
261 * quote is replaced with '\'', any exclamation point is replaced with '\!',
262 * and the whole thing is enclosed in a
265 * original sq_quote result
266 * name ==> name ==> 'name'
267 * a b ==> a b ==> 'a b'
268 * a'b ==> a'\''b ==> 'a'\''b'
269 * a!b ==> a'\!'b ==> 'a'\!'b'
273 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
277 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
280 while ((c = *src++)) {
281 if (c == '\'' || c == '!') {
292 if (bufsize < SIZEOF_STR)
304 /* XXX: Keep the view request first and in sync with views[]. */ \
305 REQ_GROUP("View switching") \
306 REQ_(VIEW_MAIN, "Show main view"), \
307 REQ_(VIEW_DIFF, "Show diff view"), \
308 REQ_(VIEW_LOG, "Show log view"), \
309 REQ_(VIEW_TREE, "Show tree view"), \
310 REQ_(VIEW_BLOB, "Show blob view"), \
311 REQ_(VIEW_HELP, "Show help page"), \
312 REQ_(VIEW_PAGER, "Show pager view"), \
313 REQ_(VIEW_STATUS, "Show status view"), \
314 REQ_(VIEW_STAGE, "Show stage view"), \
316 REQ_GROUP("View manipulation") \
317 REQ_(ENTER, "Enter current line and scroll"), \
318 REQ_(NEXT, "Move to next"), \
319 REQ_(PREVIOUS, "Move to previous"), \
320 REQ_(VIEW_NEXT, "Move focus to next view"), \
321 REQ_(REFRESH, "Reload and refresh"), \
322 REQ_(VIEW_CLOSE, "Close the current view"), \
323 REQ_(QUIT, "Close all views and quit"), \
325 REQ_GROUP("Cursor navigation") \
326 REQ_(MOVE_UP, "Move cursor one line up"), \
327 REQ_(MOVE_DOWN, "Move cursor one line down"), \
328 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
329 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
330 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
331 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
333 REQ_GROUP("Scrolling") \
334 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
335 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
336 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
337 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
339 REQ_GROUP("Searching") \
340 REQ_(SEARCH, "Search the view"), \
341 REQ_(SEARCH_BACK, "Search backwards in the view"), \
342 REQ_(FIND_NEXT, "Find next search match"), \
343 REQ_(FIND_PREV, "Find previous search match"), \
346 REQ_(PROMPT, "Bring up the prompt"), \
347 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
348 REQ_(SCREEN_RESIZE, "Resize the screen"), \
349 REQ_(SHOW_VERSION, "Show version information"), \
350 REQ_(STOP_LOADING, "Stop all loading views"), \
351 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
352 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
353 REQ_(STATUS_UPDATE, "Update file status"), \
354 REQ_(STATUS_MERGE, "Merge file using external tool"), \
355 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
356 REQ_(EDIT, "Open in editor"), \
357 REQ_(NONE, "Do nothing")
360 /* User action requests. */
362 #define REQ_GROUP(help)
363 #define REQ_(req, help) REQ_##req
365 /* Offset all requests to avoid conflicts with ncurses getch values. */
366 REQ_OFFSET = KEY_MAX + 1,
373 struct request_info {
374 enum request request;
380 static struct request_info req_info[] = {
381 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
382 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
389 get_request(const char *name)
391 int namelen = strlen(name);
394 for (i = 0; i < ARRAY_SIZE(req_info); i++)
395 if (req_info[i].namelen == namelen &&
396 !string_enum_compare(req_info[i].name, name, namelen))
397 return req_info[i].request;
407 static const char usage[] =
408 "tig " TIG_VERSION " (" __DATE__ ")\n"
410 "Usage: tig [options]\n"
411 " or: tig [options] [--] [git log options]\n"
412 " or: tig [options] log [git log options]\n"
413 " or: tig [options] diff [git diff options]\n"
414 " or: tig [options] show [git show options]\n"
415 " or: tig [options] < [git command output]\n"
418 " -l Start up in log view\n"
419 " -d Start up in diff view\n"
420 " -S Start up in status view\n"
421 " -n[I], --line-number[=I] Show line numbers with given interval\n"
422 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
423 " -- Mark end of tig options\n"
424 " -v, --version Show version and exit\n"
425 " -h, --help Show help message and exit\n";
427 /* Option and state variables. */
428 static bool opt_line_number = FALSE;
429 static bool opt_rev_graph = FALSE;
430 static int opt_num_interval = NUMBER_INTERVAL;
431 static int opt_tab_size = TABSIZE;
432 static enum request opt_request = REQ_VIEW_MAIN;
433 static char opt_cmd[SIZEOF_STR] = "";
434 static char opt_path[SIZEOF_STR] = "";
435 static FILE *opt_pipe = NULL;
436 static char opt_encoding[20] = "UTF-8";
437 static bool opt_utf8 = TRUE;
438 static char opt_codeset[20] = "UTF-8";
439 static iconv_t opt_iconv = ICONV_NONE;
440 static char opt_search[SIZEOF_STR] = "";
441 static char opt_cdup[SIZEOF_STR] = "";
442 static char opt_git_dir[SIZEOF_STR] = "";
443 static char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
444 static char opt_editor[SIZEOF_STR] = "";
452 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
462 int namelen = strlen(name);
466 if (strncmp(opt, name, namelen))
469 if (opt[namelen] == '=')
470 value = opt + namelen + 1;
473 if (!short_name || opt[1] != short_name)
478 va_start(args, type);
479 if (type == OPT_INT) {
480 number = va_arg(args, int *);
482 *number = atoi(value);
489 /* Returns the index of log or diff command or -1 to exit. */
491 parse_options(int argc, char *argv[])
495 for (i = 1; i < argc; i++) {
498 if (!strcmp(opt, "log") ||
499 !strcmp(opt, "diff") ||
500 !strcmp(opt, "show")) {
501 opt_request = opt[0] == 'l'
502 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
506 if (opt[0] && opt[0] != '-')
509 if (!strcmp(opt, "--")) {
514 if (check_option(opt, 'v', "version", OPT_NONE)) {
515 printf("tig version %s\n", TIG_VERSION);
519 if (check_option(opt, 'h', "help", OPT_NONE)) {
524 if (!strcmp(opt, "-S")) {
525 opt_request = REQ_VIEW_STATUS;
529 if (!strcmp(opt, "-l")) {
530 opt_request = REQ_VIEW_LOG;
534 if (!strcmp(opt, "-d")) {
535 opt_request = REQ_VIEW_DIFF;
539 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
540 opt_line_number = TRUE;
544 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
545 opt_tab_size = MIN(opt_tab_size, TABSIZE);
549 die("unknown option '%s'\n\n%s", opt, usage);
552 if (!isatty(STDIN_FILENO)) {
553 opt_request = REQ_VIEW_PAGER;
556 } else if (i < argc) {
559 if (opt_request == REQ_VIEW_MAIN)
560 /* XXX: This is vulnerable to the user overriding
561 * options required for the main view parser. */
562 string_copy(opt_cmd, "git log --no-color --pretty=raw");
564 string_copy(opt_cmd, "git");
565 buf_size = strlen(opt_cmd);
567 while (buf_size < sizeof(opt_cmd) && i < argc) {
568 opt_cmd[buf_size++] = ' ';
569 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
572 if (buf_size >= sizeof(opt_cmd))
573 die("command too long");
575 opt_cmd[buf_size] = 0;
578 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
586 * Line-oriented content detection.
590 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
591 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
592 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
593 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
594 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
595 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
599 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
600 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
603 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
604 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
605 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
606 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
607 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
608 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
609 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
610 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
611 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
612 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
613 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
614 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
615 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
616 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
619 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
620 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
621 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
622 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
623 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
624 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
625 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
626 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
627 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
628 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
629 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
630 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
631 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
632 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
633 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
634 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
635 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
637 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
640 #define LINE(type, line, fg, bg, attr) \
647 const char *name; /* Option name. */
648 int namelen; /* Size of option name. */
649 const char *line; /* The start of line to match. */
650 int linelen; /* Size of string to match. */
651 int fg, bg, attr; /* Color and text attributes for the lines. */
654 static struct line_info line_info[] = {
655 #define LINE(type, line, fg, bg, attr) \
656 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
661 static enum line_type
662 get_line_type(char *line)
664 int linelen = strlen(line);
667 for (type = 0; type < ARRAY_SIZE(line_info); type++)
668 /* Case insensitive search matches Signed-off-by lines better. */
669 if (linelen >= line_info[type].linelen &&
670 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
677 get_line_attr(enum line_type type)
679 assert(type < ARRAY_SIZE(line_info));
680 return COLOR_PAIR(type) | line_info[type].attr;
683 static struct line_info *
684 get_line_info(char *name, int namelen)
688 for (type = 0; type < ARRAY_SIZE(line_info); type++)
689 if (namelen == line_info[type].namelen &&
690 !string_enum_compare(line_info[type].name, name, namelen))
691 return &line_info[type];
699 int default_bg = COLOR_BLACK;
700 int default_fg = COLOR_WHITE;
705 if (use_default_colors() != ERR) {
710 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
711 struct line_info *info = &line_info[type];
712 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
713 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
715 init_pair(type, fg, bg);
723 unsigned int selected:1;
725 void *data; /* User data */
735 enum request request;
736 struct keybinding *next;
739 static struct keybinding default_keybindings[] = {
741 { 'm', REQ_VIEW_MAIN },
742 { 'd', REQ_VIEW_DIFF },
743 { 'l', REQ_VIEW_LOG },
744 { 't', REQ_VIEW_TREE },
745 { 'f', REQ_VIEW_BLOB },
746 { 'p', REQ_VIEW_PAGER },
747 { 'h', REQ_VIEW_HELP },
748 { 'S', REQ_VIEW_STATUS },
749 { 'c', REQ_VIEW_STAGE },
751 /* View manipulation */
752 { 'q', REQ_VIEW_CLOSE },
753 { KEY_TAB, REQ_VIEW_NEXT },
754 { KEY_RETURN, REQ_ENTER },
755 { KEY_UP, REQ_PREVIOUS },
756 { KEY_DOWN, REQ_NEXT },
757 { 'R', REQ_REFRESH },
759 /* Cursor navigation */
760 { 'k', REQ_MOVE_UP },
761 { 'j', REQ_MOVE_DOWN },
762 { KEY_HOME, REQ_MOVE_FIRST_LINE },
763 { KEY_END, REQ_MOVE_LAST_LINE },
764 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
765 { ' ', REQ_MOVE_PAGE_DOWN },
766 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
767 { 'b', REQ_MOVE_PAGE_UP },
768 { '-', REQ_MOVE_PAGE_UP },
771 { KEY_IC, REQ_SCROLL_LINE_UP },
772 { KEY_DC, REQ_SCROLL_LINE_DOWN },
773 { 'w', REQ_SCROLL_PAGE_UP },
774 { 's', REQ_SCROLL_PAGE_DOWN },
778 { '?', REQ_SEARCH_BACK },
779 { 'n', REQ_FIND_NEXT },
780 { 'N', REQ_FIND_PREV },
784 { 'z', REQ_STOP_LOADING },
785 { 'v', REQ_SHOW_VERSION },
786 { 'r', REQ_SCREEN_REDRAW },
787 { '.', REQ_TOGGLE_LINENO },
788 { 'g', REQ_TOGGLE_REV_GRAPH },
790 { 'u', REQ_STATUS_UPDATE },
791 { 'M', REQ_STATUS_MERGE },
792 { ',', REQ_TREE_PARENT },
795 /* Using the ncurses SIGWINCH handler. */
796 { KEY_RESIZE, REQ_SCREEN_RESIZE },
799 #define KEYMAP_INFO \
812 #define KEYMAP_(name) KEYMAP_##name
817 static struct int_map keymap_table[] = {
818 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
823 #define set_keymap(map, name) \
824 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
826 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
829 add_keybinding(enum keymap keymap, enum request request, int key)
831 struct keybinding *keybinding;
833 keybinding = calloc(1, sizeof(*keybinding));
835 die("Failed to allocate keybinding");
837 keybinding->alias = key;
838 keybinding->request = request;
839 keybinding->next = keybindings[keymap];
840 keybindings[keymap] = keybinding;
843 /* Looks for a key binding first in the given map, then in the generic map, and
844 * lastly in the default keybindings. */
846 get_keybinding(enum keymap keymap, int key)
848 struct keybinding *kbd;
851 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
852 if (kbd->alias == key)
855 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
856 if (kbd->alias == key)
859 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
860 if (default_keybindings[i].alias == key)
861 return default_keybindings[i].request;
863 return (enum request) key;
872 static struct key key_table[] = {
873 { "Enter", KEY_RETURN },
875 { "Backspace", KEY_BACKSPACE },
877 { "Escape", KEY_ESC },
878 { "Left", KEY_LEFT },
879 { "Right", KEY_RIGHT },
881 { "Down", KEY_DOWN },
882 { "Insert", KEY_IC },
883 { "Delete", KEY_DC },
885 { "Home", KEY_HOME },
887 { "PageUp", KEY_PPAGE },
888 { "PageDown", KEY_NPAGE },
898 { "F10", KEY_F(10) },
899 { "F11", KEY_F(11) },
900 { "F12", KEY_F(12) },
904 get_key_value(const char *name)
908 for (i = 0; i < ARRAY_SIZE(key_table); i++)
909 if (!strcasecmp(key_table[i].name, name))
910 return key_table[i].value;
912 if (strlen(name) == 1 && isprint(*name))
919 get_key_name(int key_value)
921 static char key_char[] = "'X'";
925 for (key = 0; key < ARRAY_SIZE(key_table); key++)
926 if (key_table[key].value == key_value)
927 seq = key_table[key].name;
931 isprint(key_value)) {
932 key_char[1] = (char) key_value;
936 return seq ? seq : "'?'";
940 get_key(enum request request)
942 static char buf[BUFSIZ];
949 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
950 struct keybinding *keybinding = &default_keybindings[i];
952 if (keybinding->request != request)
955 if (!string_format_from(buf, &pos, "%s%s", sep,
956 get_key_name(keybinding->alias)))
957 return "Too many keybindings!";
967 char cmd[SIZEOF_STR];
970 static struct run_request *run_request;
971 static size_t run_requests;
974 add_run_request(enum keymap keymap, int key, int argc, char **argv)
976 struct run_request *tmp;
977 struct run_request req = { keymap, key };
980 for (bufpos = 0; argc > 0; argc--, argv++)
981 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
984 req.cmd[bufpos - 1] = 0;
986 tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
991 run_request[run_requests++] = req;
993 return REQ_NONE + run_requests;
996 static struct run_request *
997 get_run_request(enum request request)
999 if (request <= REQ_NONE)
1001 return &run_request[request - REQ_NONE - 1];
1005 add_builtin_run_requests(void)
1012 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1013 { KEYMAP_GENERIC, 'G', { "git gc" } },
1017 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1020 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1021 if (req != REQ_NONE)
1022 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1027 * User config file handling.
1030 static struct int_map color_map[] = {
1031 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1043 #define set_color(color, name) \
1044 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1046 static struct int_map attr_map[] = {
1047 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1054 ATTR_MAP(UNDERLINE),
1057 #define set_attribute(attr, name) \
1058 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1060 static int config_lineno;
1061 static bool config_errors;
1062 static char *config_msg;
1064 /* Wants: object fgcolor bgcolor [attr] */
1066 option_color_command(int argc, char *argv[])
1068 struct line_info *info;
1070 if (argc != 3 && argc != 4) {
1071 config_msg = "Wrong number of arguments given to color command";
1075 info = get_line_info(argv[0], strlen(argv[0]));
1077 config_msg = "Unknown color name";
1081 if (set_color(&info->fg, argv[1]) == ERR ||
1082 set_color(&info->bg, argv[2]) == ERR) {
1083 config_msg = "Unknown color";
1087 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1088 config_msg = "Unknown attribute";
1095 /* Wants: name = value */
1097 option_set_command(int argc, char *argv[])
1100 config_msg = "Wrong number of arguments given to set command";
1104 if (strcmp(argv[1], "=")) {
1105 config_msg = "No value assigned";
1109 if (!strcmp(argv[0], "show-rev-graph")) {
1110 opt_rev_graph = (!strcmp(argv[2], "1") ||
1111 !strcmp(argv[2], "true") ||
1112 !strcmp(argv[2], "yes"));
1116 if (!strcmp(argv[0], "line-number-interval")) {
1117 opt_num_interval = atoi(argv[2]);
1121 if (!strcmp(argv[0], "tab-size")) {
1122 opt_tab_size = atoi(argv[2]);
1126 if (!strcmp(argv[0], "commit-encoding")) {
1127 char *arg = argv[2];
1128 int delimiter = *arg;
1131 switch (delimiter) {
1134 for (arg++, i = 0; arg[i]; i++)
1135 if (arg[i] == delimiter) {
1140 string_ncopy(opt_encoding, arg, strlen(arg));
1145 config_msg = "Unknown variable name";
1149 /* Wants: mode request key */
1151 option_bind_command(int argc, char *argv[])
1153 enum request request;
1158 config_msg = "Wrong number of arguments given to bind command";
1162 if (set_keymap(&keymap, argv[0]) == ERR) {
1163 config_msg = "Unknown key map";
1167 key = get_key_value(argv[1]);
1169 config_msg = "Unknown key";
1173 request = get_request(argv[2]);
1174 if (request == REQ_NONE) {
1175 const char *obsolete[] = { "cherry-pick" };
1176 size_t namelen = strlen(argv[2]);
1179 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1180 if (namelen == strlen(obsolete[i]) &&
1181 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1182 config_msg = "Obsolete request name";
1187 if (request == REQ_NONE && *argv[2]++ == '!')
1188 request = add_run_request(keymap, key, argc - 2, argv + 2);
1189 if (request == REQ_NONE) {
1190 config_msg = "Unknown request name";
1194 add_keybinding(keymap, request, key);
1200 set_option(char *opt, char *value)
1207 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1208 argv[argc++] = value;
1211 /* Nothing more to tokenize or last available token. */
1212 if (!*value || argc >= ARRAY_SIZE(argv))
1216 while (isspace(*value))
1220 if (!strcmp(opt, "color"))
1221 return option_color_command(argc, argv);
1223 if (!strcmp(opt, "set"))
1224 return option_set_command(argc, argv);
1226 if (!strcmp(opt, "bind"))
1227 return option_bind_command(argc, argv);
1229 config_msg = "Unknown option command";
1234 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1239 config_msg = "Internal error";
1241 /* Check for comment markers, since read_properties() will
1242 * only ensure opt and value are split at first " \t". */
1243 optlen = strcspn(opt, "#");
1247 if (opt[optlen] != 0) {
1248 config_msg = "No option value";
1252 /* Look for comment endings in the value. */
1253 size_t len = strcspn(value, "#");
1255 if (len < valuelen) {
1257 value[valuelen] = 0;
1260 status = set_option(opt, value);
1263 if (status == ERR) {
1264 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1265 config_lineno, (int) optlen, opt, config_msg);
1266 config_errors = TRUE;
1269 /* Always keep going if errors are encountered. */
1276 char *home = getenv("HOME");
1277 char buf[SIZEOF_STR];
1281 config_errors = FALSE;
1283 add_builtin_run_requests();
1285 if (!home || !string_format(buf, "%s/.tigrc", home))
1288 /* It's ok that the file doesn't exist. */
1289 file = fopen(buf, "r");
1293 if (read_properties(file, " \t", read_option) == ERR ||
1294 config_errors == TRUE)
1295 fprintf(stderr, "Errors while loading %s.\n", buf);
1308 /* The display array of active views and the index of the current view. */
1309 static struct view *display[2];
1310 static unsigned int current_view;
1312 /* Reading from the prompt? */
1313 static bool input_mode = FALSE;
1315 #define foreach_displayed_view(view, i) \
1316 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1318 #define displayed_views() (display[1] != NULL ? 2 : 1)
1320 /* Current head and commit ID */
1321 static char ref_blob[SIZEOF_REF] = "";
1322 static char ref_commit[SIZEOF_REF] = "HEAD";
1323 static char ref_head[SIZEOF_REF] = "HEAD";
1326 const char *name; /* View name */
1327 const char *cmd_fmt; /* Default command line format */
1328 const char *cmd_env; /* Command line set via environment */
1329 const char *id; /* Points to either of ref_{head,commit,blob} */
1331 struct view_ops *ops; /* View operations */
1333 enum keymap keymap; /* What keymap does this view have */
1335 char cmd[SIZEOF_STR]; /* Command buffer */
1336 char ref[SIZEOF_REF]; /* Hovered commit reference */
1337 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1339 int height, width; /* The width and height of the main window */
1340 WINDOW *win; /* The main window */
1341 WINDOW *title; /* The title window living below the main window */
1344 unsigned long offset; /* Offset of the window top */
1345 unsigned long lineno; /* Current line number */
1348 char grep[SIZEOF_STR]; /* Search string */
1349 regex_t *regex; /* Pre-compiled regex */
1351 /* If non-NULL, points to the view that opened this view. If this view
1352 * is closed tig will switch back to the parent view. */
1353 struct view *parent;
1356 unsigned long lines; /* Total number of lines */
1357 struct line *line; /* Line index */
1358 unsigned long line_size;/* Total number of allocated lines */
1359 unsigned int digits; /* Number of digits in the lines member. */
1367 /* What type of content being displayed. Used in the title bar. */
1369 /* Open and reads in all view content. */
1370 bool (*open)(struct view *view);
1371 /* Read one line; updates view->line. */
1372 bool (*read)(struct view *view, char *data);
1373 /* Draw one line; @lineno must be < view->height. */
1374 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1375 /* Depending on view handle a special requests. */
1376 enum request (*request)(struct view *view, enum request request, struct line *line);
1377 /* Search for regex in a line. */
1378 bool (*grep)(struct view *view, struct line *line);
1380 void (*select)(struct view *view, struct line *line);
1383 static struct view_ops pager_ops;
1384 static struct view_ops main_ops;
1385 static struct view_ops tree_ops;
1386 static struct view_ops blob_ops;
1387 static struct view_ops help_ops;
1388 static struct view_ops status_ops;
1389 static struct view_ops stage_ops;
1391 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1392 { name, cmd, #env, ref, ops, map}
1394 #define VIEW_(id, name, ops, ref) \
1395 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1398 static struct view views[] = {
1399 VIEW_(MAIN, "main", &main_ops, ref_head),
1400 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1401 VIEW_(LOG, "log", &pager_ops, ref_head),
1402 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1403 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1404 VIEW_(HELP, "help", &help_ops, ""),
1405 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1406 VIEW_(STATUS, "status", &status_ops, ""),
1407 VIEW_(STAGE, "stage", &stage_ops, ""),
1410 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1412 #define foreach_view(view, i) \
1413 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1415 #define view_is_displayed(view) \
1416 (view == display[0] || view == display[1])
1419 draw_view_line(struct view *view, unsigned int lineno)
1422 bool selected = (view->offset + lineno == view->lineno);
1425 assert(view_is_displayed(view));
1427 if (view->offset + lineno >= view->lines)
1430 line = &view->line[view->offset + lineno];
1433 line->selected = TRUE;
1434 view->ops->select(view, line);
1435 } else if (line->selected) {
1436 line->selected = FALSE;
1437 wmove(view->win, lineno, 0);
1438 wclrtoeol(view->win);
1441 scrollok(view->win, FALSE);
1442 draw_ok = view->ops->draw(view, line, lineno, selected);
1443 scrollok(view->win, TRUE);
1449 redraw_view_from(struct view *view, int lineno)
1451 assert(0 <= lineno && lineno < view->height);
1453 for (; lineno < view->height; lineno++) {
1454 if (!draw_view_line(view, lineno))
1458 redrawwin(view->win);
1460 wnoutrefresh(view->win);
1462 wrefresh(view->win);
1466 redraw_view(struct view *view)
1469 redraw_view_from(view, 0);
1474 update_view_title(struct view *view)
1476 char buf[SIZEOF_STR];
1477 char state[SIZEOF_STR];
1478 size_t bufpos = 0, statelen = 0;
1480 assert(view_is_displayed(view));
1482 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1483 unsigned int view_lines = view->offset + view->height;
1484 unsigned int lines = view->lines
1485 ? MIN(view_lines, view->lines) * 100 / view->lines
1488 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1495 time_t secs = time(NULL) - view->start_time;
1497 /* Three git seconds are a long time ... */
1499 string_format_from(state, &statelen, " %lds", secs);
1503 string_format_from(buf, &bufpos, "[%s]", view->name);
1504 if (*view->ref && bufpos < view->width) {
1505 size_t refsize = strlen(view->ref);
1506 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1508 if (minsize < view->width)
1509 refsize = view->width - minsize + 7;
1510 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1513 if (statelen && bufpos < view->width) {
1514 string_format_from(buf, &bufpos, " %s", state);
1517 if (view == display[current_view])
1518 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1520 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1522 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1523 wclrtoeol(view->title);
1524 wmove(view->title, 0, view->width - 1);
1527 wnoutrefresh(view->title);
1529 wrefresh(view->title);
1533 resize_display(void)
1536 struct view *base = display[0];
1537 struct view *view = display[1] ? display[1] : display[0];
1539 /* Setup window dimensions */
1541 getmaxyx(stdscr, base->height, base->width);
1543 /* Make room for the status window. */
1547 /* Horizontal split. */
1548 view->width = base->width;
1549 view->height = SCALE_SPLIT_VIEW(base->height);
1550 base->height -= view->height;
1552 /* Make room for the title bar. */
1556 /* Make room for the title bar. */
1561 foreach_displayed_view (view, i) {
1563 view->win = newwin(view->height, 0, offset, 0);
1565 die("Failed to create %s view", view->name);
1567 scrollok(view->win, TRUE);
1569 view->title = newwin(1, 0, offset + view->height, 0);
1571 die("Failed to create title window");
1574 wresize(view->win, view->height, view->width);
1575 mvwin(view->win, offset, 0);
1576 mvwin(view->title, offset + view->height, 0);
1579 offset += view->height + 1;
1584 redraw_display(void)
1589 foreach_displayed_view (view, i) {
1591 update_view_title(view);
1596 update_display_cursor(struct view *view)
1598 /* Move the cursor to the right-most column of the cursor line.
1600 * XXX: This could turn out to be a bit expensive, but it ensures that
1601 * the cursor does not jump around. */
1603 wmove(view->win, view->lineno - view->offset, view->width - 1);
1604 wrefresh(view->win);
1612 /* Scrolling backend */
1614 do_scroll_view(struct view *view, int lines)
1616 bool redraw_current_line = FALSE;
1618 /* The rendering expects the new offset. */
1619 view->offset += lines;
1621 assert(0 <= view->offset && view->offset < view->lines);
1624 /* Move current line into the view. */
1625 if (view->lineno < view->offset) {
1626 view->lineno = view->offset;
1627 redraw_current_line = TRUE;
1628 } else if (view->lineno >= view->offset + view->height) {
1629 view->lineno = view->offset + view->height - 1;
1630 redraw_current_line = TRUE;
1633 assert(view->offset <= view->lineno && view->lineno < view->lines);
1635 /* Redraw the whole screen if scrolling is pointless. */
1636 if (view->height < ABS(lines)) {
1640 int line = lines > 0 ? view->height - lines : 0;
1641 int end = line + ABS(lines);
1643 wscrl(view->win, lines);
1645 for (; line < end; line++) {
1646 if (!draw_view_line(view, line))
1650 if (redraw_current_line)
1651 draw_view_line(view, view->lineno - view->offset);
1654 redrawwin(view->win);
1655 wrefresh(view->win);
1659 /* Scroll frontend */
1661 scroll_view(struct view *view, enum request request)
1665 assert(view_is_displayed(view));
1668 case REQ_SCROLL_PAGE_DOWN:
1669 lines = view->height;
1670 case REQ_SCROLL_LINE_DOWN:
1671 if (view->offset + lines > view->lines)
1672 lines = view->lines - view->offset;
1674 if (lines == 0 || view->offset + view->height >= view->lines) {
1675 report("Cannot scroll beyond the last line");
1680 case REQ_SCROLL_PAGE_UP:
1681 lines = view->height;
1682 case REQ_SCROLL_LINE_UP:
1683 if (lines > view->offset)
1684 lines = view->offset;
1687 report("Cannot scroll beyond the first line");
1695 die("request %d not handled in switch", request);
1698 do_scroll_view(view, lines);
1703 move_view(struct view *view, enum request request)
1705 int scroll_steps = 0;
1709 case REQ_MOVE_FIRST_LINE:
1710 steps = -view->lineno;
1713 case REQ_MOVE_LAST_LINE:
1714 steps = view->lines - view->lineno - 1;
1717 case REQ_MOVE_PAGE_UP:
1718 steps = view->height > view->lineno
1719 ? -view->lineno : -view->height;
1722 case REQ_MOVE_PAGE_DOWN:
1723 steps = view->lineno + view->height >= view->lines
1724 ? view->lines - view->lineno - 1 : view->height;
1736 die("request %d not handled in switch", request);
1739 if (steps <= 0 && view->lineno == 0) {
1740 report("Cannot move beyond the first line");
1743 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1744 report("Cannot move beyond the last line");
1748 /* Move the current line */
1749 view->lineno += steps;
1750 assert(0 <= view->lineno && view->lineno < view->lines);
1752 /* Check whether the view needs to be scrolled */
1753 if (view->lineno < view->offset ||
1754 view->lineno >= view->offset + view->height) {
1755 scroll_steps = steps;
1756 if (steps < 0 && -steps > view->offset) {
1757 scroll_steps = -view->offset;
1759 } else if (steps > 0) {
1760 if (view->lineno == view->lines - 1 &&
1761 view->lines > view->height) {
1762 scroll_steps = view->lines - view->offset - 1;
1763 if (scroll_steps >= view->height)
1764 scroll_steps -= view->height - 1;
1769 if (!view_is_displayed(view)) {
1770 view->offset += scroll_steps;
1771 assert(0 <= view->offset && view->offset < view->lines);
1772 view->ops->select(view, &view->line[view->lineno]);
1776 /* Repaint the old "current" line if we be scrolling */
1777 if (ABS(steps) < view->height)
1778 draw_view_line(view, view->lineno - steps - view->offset);
1781 do_scroll_view(view, scroll_steps);
1785 /* Draw the current line */
1786 draw_view_line(view, view->lineno - view->offset);
1788 redrawwin(view->win);
1789 wrefresh(view->win);
1798 static void search_view(struct view *view, enum request request);
1801 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1803 assert(view_is_displayed(view));
1805 if (!view->ops->grep(view, line))
1808 if (lineno - view->offset >= view->height) {
1809 view->offset = lineno;
1810 view->lineno = lineno;
1814 unsigned long old_lineno = view->lineno - view->offset;
1816 view->lineno = lineno;
1817 draw_view_line(view, old_lineno);
1819 draw_view_line(view, view->lineno - view->offset);
1820 redrawwin(view->win);
1821 wrefresh(view->win);
1824 report("Line %ld matches '%s'", lineno + 1, view->grep);
1829 find_next(struct view *view, enum request request)
1831 unsigned long lineno = view->lineno;
1836 report("No previous search");
1838 search_view(view, request);
1848 case REQ_SEARCH_BACK:
1857 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1858 lineno += direction;
1860 /* Note, lineno is unsigned long so will wrap around in which case it
1861 * will become bigger than view->lines. */
1862 for (; lineno < view->lines; lineno += direction) {
1863 struct line *line = &view->line[lineno];
1865 if (find_next_line(view, lineno, line))
1869 report("No match found for '%s'", view->grep);
1873 search_view(struct view *view, enum request request)
1878 regfree(view->regex);
1881 view->regex = calloc(1, sizeof(*view->regex));
1886 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1887 if (regex_err != 0) {
1888 char buf[SIZEOF_STR] = "unknown error";
1890 regerror(regex_err, view->regex, buf, sizeof(buf));
1891 report("Search failed: %s", buf);
1895 string_copy(view->grep, opt_search);
1897 find_next(view, request);
1901 * Incremental updating
1905 end_update(struct view *view)
1909 set_nonblocking_input(FALSE);
1910 if (view->pipe == stdin)
1918 begin_update(struct view *view)
1924 string_copy(view->cmd, opt_cmd);
1926 /* When running random commands, initially show the
1927 * command in the title. However, it maybe later be
1928 * overwritten if a commit line is selected. */
1929 if (view == VIEW(REQ_VIEW_PAGER))
1930 string_copy(view->ref, view->cmd);
1934 } else if (view == VIEW(REQ_VIEW_TREE)) {
1935 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1936 char path[SIZEOF_STR];
1938 if (strcmp(view->vid, view->id))
1939 opt_path[0] = path[0] = 0;
1940 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1943 if (!string_format(view->cmd, format, view->id, path))
1947 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1948 const char *id = view->id;
1950 if (!string_format(view->cmd, format, id, id, id, id, id))
1953 /* Put the current ref_* value to the view title ref
1954 * member. This is needed by the blob view. Most other
1955 * views sets it automatically after loading because the
1956 * first line is a commit line. */
1957 string_copy_rev(view->ref, view->id);
1960 /* Special case for the pager view. */
1962 view->pipe = opt_pipe;
1965 view->pipe = popen(view->cmd, "r");
1971 set_nonblocking_input(TRUE);
1976 string_copy_rev(view->vid, view->id);
1981 for (i = 0; i < view->lines; i++)
1982 if (view->line[i].data)
1983 free(view->line[i].data);
1989 view->start_time = time(NULL);
1994 static struct line *
1995 realloc_lines(struct view *view, size_t line_size)
1997 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2003 view->line_size = line_size;
2008 update_view(struct view *view)
2010 char in_buffer[BUFSIZ];
2011 char out_buffer[BUFSIZ * 2];
2013 /* The number of lines to read. If too low it will cause too much
2014 * redrawing (and possible flickering), if too high responsiveness
2016 unsigned long lines = view->height;
2017 int redraw_from = -1;
2022 /* Only redraw if lines are visible. */
2023 if (view->offset + view->height >= view->lines)
2024 redraw_from = view->lines - view->offset;
2026 /* FIXME: This is probably not perfect for backgrounded views. */
2027 if (!realloc_lines(view, view->lines + lines))
2030 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2031 size_t linelen = strlen(line);
2034 line[linelen - 1] = 0;
2036 if (opt_iconv != ICONV_NONE) {
2037 ICONV_CONST char *inbuf = line;
2038 size_t inlen = linelen;
2040 char *outbuf = out_buffer;
2041 size_t outlen = sizeof(out_buffer);
2045 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2046 if (ret != (size_t) -1) {
2048 linelen = strlen(out_buffer);
2052 if (!view->ops->read(view, line))
2062 lines = view->lines;
2063 for (digits = 0; lines; digits++)
2066 /* Keep the displayed view in sync with line number scaling. */
2067 if (digits != view->digits) {
2068 view->digits = digits;
2073 if (!view_is_displayed(view))
2076 if (view == VIEW(REQ_VIEW_TREE)) {
2077 /* Clear the view and redraw everything since the tree sorting
2078 * might have rearranged things. */
2081 } else if (redraw_from >= 0) {
2082 /* If this is an incremental update, redraw the previous line
2083 * since for commits some members could have changed when
2084 * loading the main view. */
2085 if (redraw_from > 0)
2088 /* Since revision graph visualization requires knowledge
2089 * about the parent commit, it causes a further one-off
2090 * needed to be redrawn for incremental updates. */
2091 if (redraw_from > 0 && opt_rev_graph)
2094 /* Incrementally draw avoids flickering. */
2095 redraw_view_from(view, redraw_from);
2098 /* Update the title _after_ the redraw so that if the redraw picks up a
2099 * commit reference in view->ref it'll be available here. */
2100 update_view_title(view);
2103 if (ferror(view->pipe)) {
2104 report("Failed to read: %s", strerror(errno));
2107 } else if (feof(view->pipe)) {
2115 report("Allocation failure");
2118 view->ops->read(view, NULL);
2123 static struct line *
2124 add_line_data(struct view *view, void *data, enum line_type type)
2126 struct line *line = &view->line[view->lines++];
2128 memset(line, 0, sizeof(*line));
2135 static struct line *
2136 add_line_text(struct view *view, char *data, enum line_type type)
2139 data = strdup(data);
2141 return data ? add_line_data(view, data, type) : NULL;
2150 OPEN_DEFAULT = 0, /* Use default view switching. */
2151 OPEN_SPLIT = 1, /* Split current view. */
2152 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2153 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2157 open_view(struct view *prev, enum request request, enum open_flags flags)
2159 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2160 bool split = !!(flags & OPEN_SPLIT);
2161 bool reload = !!(flags & OPEN_RELOAD);
2162 struct view *view = VIEW(request);
2163 int nviews = displayed_views();
2164 struct view *base_view = display[0];
2166 if (view == prev && nviews == 1 && !reload) {
2167 report("Already in %s view", view->name);
2171 if (view->ops->open) {
2172 if (!view->ops->open(view)) {
2173 report("Failed to load %s view", view->name);
2177 } else if ((reload || strcmp(view->vid, view->id)) &&
2178 !begin_update(view)) {
2179 report("Failed to load %s view", view->name);
2188 /* Maximize the current view. */
2189 memset(display, 0, sizeof(display));
2191 display[current_view] = view;
2194 /* Resize the view when switching between split- and full-screen,
2195 * or when switching between two different full-screen views. */
2196 if (nviews != displayed_views() ||
2197 (nviews == 1 && base_view != display[0]))
2200 if (split && prev->lineno - prev->offset >= prev->height) {
2201 /* Take the title line into account. */
2202 int lines = prev->lineno - prev->offset - prev->height + 1;
2204 /* Scroll the view that was split if the current line is
2205 * outside the new limited view. */
2206 do_scroll_view(prev, lines);
2209 if (prev && view != prev) {
2210 if (split && !backgrounded) {
2211 /* "Blur" the previous view. */
2212 update_view_title(prev);
2215 view->parent = prev;
2218 if (view->pipe && view->lines == 0) {
2219 /* Clear the old view and let the incremental updating refill
2228 /* If the view is backgrounded the above calls to report()
2229 * won't redraw the view title. */
2231 update_view_title(view);
2235 open_external_viewer(const char *cmd)
2237 def_prog_mode(); /* save current tty modes */
2238 endwin(); /* restore original tty modes */
2240 fprintf(stderr, "Press Enter to continue");
2247 open_mergetool(const char *file)
2249 char cmd[SIZEOF_STR];
2250 char file_sq[SIZEOF_STR];
2252 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2253 string_format(cmd, "git mergetool %s", file_sq)) {
2254 open_external_viewer(cmd);
2259 open_editor(bool from_root, const char *file)
2261 char cmd[SIZEOF_STR];
2262 char file_sq[SIZEOF_STR];
2264 char *prefix = from_root ? opt_cdup : "";
2266 editor = getenv("GIT_EDITOR");
2267 if (!editor && *opt_editor)
2268 editor = opt_editor;
2270 editor = getenv("VISUAL");
2272 editor = getenv("EDITOR");
2276 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2277 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2278 open_external_viewer(cmd);
2283 open_run_request(enum request request)
2285 struct run_request *req = get_run_request(request);
2286 char buf[SIZEOF_STR * 2];
2291 report("Unknown run request");
2299 char *next = strstr(cmd, "%(");
2300 int len = next - cmd;
2307 } else if (!strncmp(next, "%(head)", 7)) {
2310 } else if (!strncmp(next, "%(commit)", 9)) {
2313 } else if (!strncmp(next, "%(blob)", 7)) {
2317 report("Unknown replacement in run request: `%s`", req->cmd);
2321 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2325 next = strchr(next, ')') + 1;
2329 open_external_viewer(buf);
2333 * User request switch noodle
2337 view_driver(struct view *view, enum request request)
2341 if (request == REQ_NONE) {
2346 if (request > REQ_NONE) {
2347 open_run_request(request);
2351 if (view && view->lines) {
2352 request = view->ops->request(view, request, &view->line[view->lineno]);
2353 if (request == REQ_NONE)
2360 case REQ_MOVE_PAGE_UP:
2361 case REQ_MOVE_PAGE_DOWN:
2362 case REQ_MOVE_FIRST_LINE:
2363 case REQ_MOVE_LAST_LINE:
2364 move_view(view, request);
2367 case REQ_SCROLL_LINE_DOWN:
2368 case REQ_SCROLL_LINE_UP:
2369 case REQ_SCROLL_PAGE_DOWN:
2370 case REQ_SCROLL_PAGE_UP:
2371 scroll_view(view, request);
2376 report("No file chosen, press %s to open tree view",
2377 get_key(REQ_VIEW_TREE));
2380 open_view(view, request, OPEN_DEFAULT);
2383 case REQ_VIEW_PAGER:
2384 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2385 report("No pager content, press %s to run command from prompt",
2386 get_key(REQ_PROMPT));
2389 open_view(view, request, OPEN_DEFAULT);
2392 case REQ_VIEW_STAGE:
2393 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2394 report("No stage content, press %s to open the status view and choose file",
2395 get_key(REQ_VIEW_STATUS));
2398 open_view(view, request, OPEN_DEFAULT);
2401 case REQ_VIEW_STATUS:
2402 if (opt_is_inside_work_tree == FALSE) {
2403 report("The status view requires a working tree");
2406 open_view(view, request, OPEN_DEFAULT);
2414 open_view(view, request, OPEN_DEFAULT);
2419 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2421 if ((view == VIEW(REQ_VIEW_DIFF) &&
2422 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2423 (view == VIEW(REQ_VIEW_STAGE) &&
2424 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2425 (view == VIEW(REQ_VIEW_BLOB) &&
2426 view->parent == VIEW(REQ_VIEW_TREE))) {
2429 view = view->parent;
2430 line = view->lineno;
2431 move_view(view, request);
2432 if (view_is_displayed(view))
2433 update_view_title(view);
2434 if (line != view->lineno)
2435 view->ops->request(view, REQ_ENTER,
2436 &view->line[view->lineno]);
2439 move_view(view, request);
2445 int nviews = displayed_views();
2446 int next_view = (current_view + 1) % nviews;
2448 if (next_view == current_view) {
2449 report("Only one view is displayed");
2453 current_view = next_view;
2454 /* Blur out the title of the previous view. */
2455 update_view_title(view);
2460 report("Refreshing is not yet supported for the %s view", view->name);
2463 case REQ_TOGGLE_LINENO:
2464 opt_line_number = !opt_line_number;
2468 case REQ_TOGGLE_REV_GRAPH:
2469 opt_rev_graph = !opt_rev_graph;
2474 /* Always reload^Wrerun commands from the prompt. */
2475 open_view(view, opt_request, OPEN_RELOAD);
2479 case REQ_SEARCH_BACK:
2480 search_view(view, request);
2485 find_next(view, request);
2488 case REQ_STOP_LOADING:
2489 for (i = 0; i < ARRAY_SIZE(views); i++) {
2492 report("Stopped loading the %s view", view->name),
2497 case REQ_SHOW_VERSION:
2498 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2501 case REQ_SCREEN_RESIZE:
2504 case REQ_SCREEN_REDRAW:
2509 report("Nothing to edit");
2514 report("Nothing to enter");
2518 case REQ_VIEW_CLOSE:
2519 /* XXX: Mark closed views by letting view->parent point to the
2520 * view itself. Parents to closed view should never be
2523 view->parent->parent != view->parent) {
2524 memset(display, 0, sizeof(display));
2526 display[current_view] = view->parent;
2527 view->parent = view;
2537 /* An unknown key will show most commonly used commands. */
2538 report("Unknown key, press 'h' for help");
2551 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2553 char *text = line->data;
2554 enum line_type type = line->type;
2555 int textlen = strlen(text);
2558 wmove(view->win, lineno, 0);
2562 wchgat(view->win, -1, 0, type, NULL);
2565 attr = get_line_attr(type);
2566 wattrset(view->win, attr);
2568 if (opt_line_number || opt_tab_size < TABSIZE) {
2569 static char spaces[] = " ";
2570 int col_offset = 0, col = 0;
2572 if (opt_line_number) {
2573 unsigned long real_lineno = view->offset + lineno + 1;
2575 if (real_lineno == 1 ||
2576 (real_lineno % opt_num_interval) == 0) {
2577 wprintw(view->win, "%.*d", view->digits, real_lineno);
2580 waddnstr(view->win, spaces,
2581 MIN(view->digits, STRING_SIZE(spaces)));
2583 waddstr(view->win, ": ");
2584 col_offset = view->digits + 2;
2587 while (text && col_offset + col < view->width) {
2588 int cols_max = view->width - col_offset - col;
2592 if (*text == '\t') {
2594 assert(sizeof(spaces) > TABSIZE);
2596 cols = opt_tab_size - (col % opt_tab_size);
2599 text = strchr(text, '\t');
2600 cols = line ? text - pos : strlen(pos);
2603 waddnstr(view->win, pos, MIN(cols, cols_max));
2608 int col = 0, pos = 0;
2610 for (; pos < textlen && col < view->width; pos++, col++)
2611 if (text[pos] == '\t')
2612 col += TABSIZE - (col % TABSIZE) - 1;
2614 waddnstr(view->win, text, pos);
2621 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2623 char refbuf[SIZEOF_STR];
2627 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2630 pipe = popen(refbuf, "r");
2634 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2635 ref = chomp_string(ref);
2641 /* This is the only fatal call, since it can "corrupt" the buffer. */
2642 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2649 add_pager_refs(struct view *view, struct line *line)
2651 char buf[SIZEOF_STR];
2652 char *commit_id = line->data + STRING_SIZE("commit ");
2654 size_t bufpos = 0, refpos = 0;
2655 const char *sep = "Refs: ";
2656 bool is_tag = FALSE;
2658 assert(line->type == LINE_COMMIT);
2660 refs = get_refs(commit_id);
2662 if (view == VIEW(REQ_VIEW_DIFF))
2663 goto try_add_describe_ref;
2668 struct ref *ref = refs[refpos];
2669 char *fmt = ref->tag ? "%s[%s]" :
2670 ref->remote ? "%s<%s>" : "%s%s";
2672 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2677 } while (refs[refpos++]->next);
2679 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2680 try_add_describe_ref:
2681 /* Add <tag>-g<commit_id> "fake" reference. */
2682 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2689 if (!realloc_lines(view, view->line_size + 1))
2692 add_line_text(view, buf, LINE_PP_REFS);
2696 pager_read(struct view *view, char *data)
2703 line = add_line_text(view, data, get_line_type(data));
2707 if (line->type == LINE_COMMIT &&
2708 (view == VIEW(REQ_VIEW_DIFF) ||
2709 view == VIEW(REQ_VIEW_LOG)))
2710 add_pager_refs(view, line);
2716 pager_request(struct view *view, enum request request, struct line *line)
2720 if (request != REQ_ENTER)
2723 if (line->type == LINE_COMMIT &&
2724 (view == VIEW(REQ_VIEW_LOG) ||
2725 view == VIEW(REQ_VIEW_PAGER))) {
2726 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2730 /* Always scroll the view even if it was split. That way
2731 * you can use Enter to scroll through the log view and
2732 * split open each commit diff. */
2733 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2735 /* FIXME: A minor workaround. Scrolling the view will call report("")
2736 * but if we are scrolling a non-current view this won't properly
2737 * update the view title. */
2739 update_view_title(view);
2745 pager_grep(struct view *view, struct line *line)
2748 char *text = line->data;
2753 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2760 pager_select(struct view *view, struct line *line)
2762 if (line->type == LINE_COMMIT) {
2763 char *text = line->data + STRING_SIZE("commit ");
2765 if (view != VIEW(REQ_VIEW_PAGER))
2766 string_copy_rev(view->ref, text);
2767 string_copy_rev(ref_commit, text);
2771 static struct view_ops pager_ops = {
2787 help_open(struct view *view)
2790 int lines = ARRAY_SIZE(req_info) + 2;
2793 if (view->lines > 0)
2796 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2797 if (!req_info[i].request)
2800 lines += run_requests + 1;
2802 view->line = calloc(lines, sizeof(*view->line));
2806 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2808 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2811 if (req_info[i].request == REQ_NONE)
2814 if (!req_info[i].request) {
2815 add_line_text(view, "", LINE_DEFAULT);
2816 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2820 key = get_key(req_info[i].request);
2822 key = "(no key defined)";
2824 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2827 add_line_text(view, buf, LINE_DEFAULT);
2831 add_line_text(view, "", LINE_DEFAULT);
2832 add_line_text(view, "External commands:", LINE_DEFAULT);
2835 for (i = 0; i < run_requests; i++) {
2836 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2842 key = get_key_name(req->key);
2844 key = "(no key defined)";
2846 if (!string_format(buf, " %-10s %-14s `%s`",
2847 keymap_table[req->keymap].name,
2851 add_line_text(view, buf, LINE_DEFAULT);
2857 static struct view_ops help_ops = {
2872 struct tree_stack_entry {
2873 struct tree_stack_entry *prev; /* Entry below this in the stack */
2874 unsigned long lineno; /* Line number to restore */
2875 char *name; /* Position of name in opt_path */
2878 /* The top of the path stack. */
2879 static struct tree_stack_entry *tree_stack = NULL;
2880 unsigned long tree_lineno = 0;
2883 pop_tree_stack_entry(void)
2885 struct tree_stack_entry *entry = tree_stack;
2887 tree_lineno = entry->lineno;
2889 tree_stack = entry->prev;
2894 push_tree_stack_entry(char *name, unsigned long lineno)
2896 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2897 size_t pathlen = strlen(opt_path);
2902 entry->prev = tree_stack;
2903 entry->name = opt_path + pathlen;
2906 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2907 pop_tree_stack_entry();
2911 /* Move the current line to the first tree entry. */
2913 entry->lineno = lineno;
2916 /* Parse output from git-ls-tree(1):
2918 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2919 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2920 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2921 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2924 #define SIZEOF_TREE_ATTR \
2925 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2927 #define TREE_UP_FORMAT "040000 tree %s\t.."
2930 tree_compare_entry(enum line_type type1, char *name1,
2931 enum line_type type2, char *name2)
2933 if (type1 != type2) {
2934 if (type1 == LINE_TREE_DIR)
2939 return strcmp(name1, name2);
2943 tree_read(struct view *view, char *text)
2945 size_t textlen = text ? strlen(text) : 0;
2946 char buf[SIZEOF_STR];
2948 enum line_type type;
2949 bool first_read = view->lines == 0;
2951 if (textlen <= SIZEOF_TREE_ATTR)
2954 type = text[STRING_SIZE("100644 ")] == 't'
2955 ? LINE_TREE_DIR : LINE_TREE_FILE;
2958 /* Add path info line */
2959 if (!string_format(buf, "Directory path /%s", opt_path) ||
2960 !realloc_lines(view, view->line_size + 1) ||
2961 !add_line_text(view, buf, LINE_DEFAULT))
2964 /* Insert "link" to parent directory. */
2966 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2967 !realloc_lines(view, view->line_size + 1) ||
2968 !add_line_text(view, buf, LINE_TREE_DIR))
2973 /* Strip the path part ... */
2975 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2976 size_t striplen = strlen(opt_path);
2977 char *path = text + SIZEOF_TREE_ATTR;
2979 if (pathlen > striplen)
2980 memmove(path, path + striplen,
2981 pathlen - striplen + 1);
2984 /* Skip "Directory ..." and ".." line. */
2985 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2986 struct line *line = &view->line[pos];
2987 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2988 char *path2 = text + SIZEOF_TREE_ATTR;
2989 int cmp = tree_compare_entry(line->type, path1, type, path2);
2994 text = strdup(text);
2998 if (view->lines > pos)
2999 memmove(&view->line[pos + 1], &view->line[pos],
3000 (view->lines - pos) * sizeof(*line));
3002 line = &view->line[pos];
3009 if (!add_line_text(view, text, type))
3012 if (tree_lineno > view->lineno) {
3013 view->lineno = tree_lineno;
3021 tree_request(struct view *view, enum request request, struct line *line)
3023 enum open_flags flags;
3025 if (request == REQ_TREE_PARENT) {
3028 request = REQ_ENTER;
3029 line = &view->line[1];
3031 /* quit view if at top of tree */
3032 return REQ_VIEW_CLOSE;
3035 if (request != REQ_ENTER)
3038 /* Cleanup the stack if the tree view is at a different tree. */
3039 while (!*opt_path && tree_stack)
3040 pop_tree_stack_entry();
3042 switch (line->type) {
3044 /* Depending on whether it is a subdir or parent (updir?) link
3045 * mangle the path buffer. */
3046 if (line == &view->line[1] && *opt_path) {
3047 pop_tree_stack_entry();
3050 char *data = line->data;
3051 char *basename = data + SIZEOF_TREE_ATTR;
3053 push_tree_stack_entry(basename, view->lineno);
3056 /* Trees and subtrees share the same ID, so they are not not
3057 * unique like blobs. */
3058 flags = OPEN_RELOAD;
3059 request = REQ_VIEW_TREE;
3062 case LINE_TREE_FILE:
3063 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3064 request = REQ_VIEW_BLOB;
3071 open_view(view, request, flags);
3072 if (request == REQ_VIEW_TREE) {
3073 view->lineno = tree_lineno;
3080 tree_select(struct view *view, struct line *line)
3082 char *text = line->data + STRING_SIZE("100644 blob ");
3084 if (line->type == LINE_TREE_FILE) {
3085 string_copy_rev(ref_blob, text);
3087 } else if (line->type != LINE_TREE_DIR) {
3091 string_copy_rev(view->ref, text);
3094 static struct view_ops tree_ops = {
3105 blob_read(struct view *view, char *line)
3107 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3110 static struct view_ops blob_ops = {
3129 char rev[SIZEOF_REV];
3133 char rev[SIZEOF_REV];
3135 char name[SIZEOF_STR];
3138 static struct status stage_status;
3139 static enum line_type stage_line_type;
3141 /* Get fields from the diff line:
3142 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3145 status_get_diff(struct status *file, char *buf, size_t bufsize)
3147 char *old_mode = buf + 1;
3148 char *new_mode = buf + 8;
3149 char *old_rev = buf + 15;
3150 char *new_rev = buf + 56;
3151 char *status = buf + 97;
3153 if (bufsize != 99 ||
3154 old_mode[-1] != ':' ||
3155 new_mode[-1] != ' ' ||
3156 old_rev[-1] != ' ' ||
3157 new_rev[-1] != ' ' ||
3161 file->status = *status;
3163 string_copy_rev(file->old.rev, old_rev);
3164 string_copy_rev(file->new.rev, new_rev);
3166 file->old.mode = strtoul(old_mode, NULL, 8);
3167 file->new.mode = strtoul(new_mode, NULL, 8);
3175 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3177 struct status *file = NULL;
3178 struct status *unmerged = NULL;
3179 char buf[SIZEOF_STR * 4];
3183 pipe = popen(cmd, "r");
3187 add_line_data(view, NULL, type);
3189 while (!feof(pipe) && !ferror(pipe)) {
3193 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3196 bufsize += readsize;
3198 /* Process while we have NUL chars. */
3199 while ((sep = memchr(buf, 0, bufsize))) {
3200 size_t sepsize = sep - buf + 1;
3203 if (!realloc_lines(view, view->line_size + 1))
3206 file = calloc(1, sizeof(*file));
3210 add_line_data(view, file, type);
3213 /* Parse diff info part. */
3217 } else if (!file->status) {
3218 if (!status_get_diff(file, buf, sepsize))
3222 memmove(buf, sep + 1, bufsize);
3224 sep = memchr(buf, 0, bufsize);
3227 sepsize = sep - buf + 1;
3229 /* Collapse all 'M'odified entries that
3230 * follow a associated 'U'nmerged entry.
3232 if (file->status == 'U') {
3235 } else if (unmerged) {
3236 int collapse = !strcmp(buf, unmerged->name);
3247 /* git-ls-files just delivers a NUL separated
3248 * list of file names similar to the second half
3249 * of the git-diff-* output. */
3250 string_ncopy(file->name, buf, sepsize);
3252 memmove(buf, sep + 1, bufsize);
3263 if (!view->line[view->lines - 1].data)
3264 add_line_data(view, NULL, LINE_STAT_NONE);
3270 /* Don't show unmerged entries in the staged section. */
3271 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3272 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3273 #define STATUS_LIST_OTHER_CMD \
3274 "git ls-files -z --others --exclude-per-directory=.gitignore"
3276 #define STATUS_DIFF_INDEX_SHOW_CMD \
3277 "git diff-index --root --patch-with-stat --find-copies-harder -B -C --cached HEAD -- %s 2>/dev/null"
3279 #define STATUS_DIFF_FILES_SHOW_CMD \
3280 "git diff-files --root --patch-with-stat --find-copies-harder -B -C -- %s 2>/dev/null"
3282 /* First parse staged info using git-diff-index(1), then parse unstaged
3283 * info using git-diff-files(1), and finally untracked files using
3284 * git-ls-files(1). */
3286 status_open(struct view *view)
3288 struct stat statbuf;
3289 char exclude[SIZEOF_STR];
3290 char cmd[SIZEOF_STR];
3291 unsigned long prev_lineno = view->lineno;
3294 for (i = 0; i < view->lines; i++)
3295 free(view->line[i].data);
3297 view->lines = view->line_size = view->lineno = 0;
3300 if (!realloc_lines(view, view->line_size + 6))
3303 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3306 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3308 if (stat(exclude, &statbuf) >= 0) {
3309 size_t cmdsize = strlen(cmd);
3311 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3312 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3316 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3317 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3318 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3321 /* If all went well restore the previous line number to stay in
3323 if (prev_lineno < view->lines)
3324 view->lineno = prev_lineno;
3326 view->lineno = view->lines - 1;
3332 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3334 struct status *status = line->data;
3336 wmove(view->win, lineno, 0);
3339 wattrset(view->win, get_line_attr(LINE_CURSOR));
3340 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3342 } else if (!status && line->type != LINE_STAT_NONE) {
3343 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3344 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3347 wattrset(view->win, get_line_attr(line->type));
3353 switch (line->type) {
3354 case LINE_STAT_STAGED:
3355 text = "Changes to be committed:";
3358 case LINE_STAT_UNSTAGED:
3359 text = "Changed but not updated:";
3362 case LINE_STAT_UNTRACKED:
3363 text = "Untracked files:";
3366 case LINE_STAT_NONE:
3367 text = " (no files)";
3374 waddstr(view->win, text);
3378 waddch(view->win, status->status);
3380 wattrset(view->win, A_NORMAL);
3381 wmove(view->win, lineno, 4);
3382 waddstr(view->win, status->name);
3388 status_enter(struct view *view, struct line *line)
3390 struct status *status = line->data;
3391 char path[SIZEOF_STR] = "";
3395 if (line->type == LINE_STAT_NONE ||
3396 (!status && line[1].type == LINE_STAT_NONE)) {
3397 report("No file to diff");
3401 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3405 line->type != LINE_STAT_UNTRACKED &&
3406 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3409 switch (line->type) {
3410 case LINE_STAT_STAGED:
3411 if (!string_format_from(opt_cmd, &cmdsize,
3412 STATUS_DIFF_INDEX_SHOW_CMD, path))
3415 info = "Staged changes to %s";
3417 info = "Staged changes";
3420 case LINE_STAT_UNSTAGED:
3421 if (!string_format_from(opt_cmd, &cmdsize,
3422 STATUS_DIFF_FILES_SHOW_CMD, path))
3425 info = "Unstaged changes to %s";
3427 info = "Unstaged changes";
3430 case LINE_STAT_UNTRACKED:
3436 report("No file to show");
3440 opt_pipe = fopen(status->name, "r");
3441 info = "Untracked file %s";
3445 die("line type %d not handled in switch", line->type);
3448 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3449 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3451 stage_status = *status;
3453 memset(&stage_status, 0, sizeof(stage_status));
3456 stage_line_type = line->type;
3457 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3465 status_update_file(struct view *view, struct status *status, enum line_type type)
3467 char cmd[SIZEOF_STR];
3468 char buf[SIZEOF_STR];
3475 type != LINE_STAT_UNTRACKED &&
3476 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3480 case LINE_STAT_STAGED:
3481 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3487 string_add(cmd, cmdsize, "git update-index -z --index-info");
3490 case LINE_STAT_UNSTAGED:
3491 case LINE_STAT_UNTRACKED:
3492 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3495 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3499 die("line type %d not handled in switch", type);
3502 pipe = popen(cmd, "w");
3506 while (!ferror(pipe) && written < bufsize) {
3507 written += fwrite(buf + written, 1, bufsize - written, pipe);
3512 if (written != bufsize)
3519 status_update(struct view *view)
3521 struct line *line = &view->line[view->lineno];
3523 assert(view->lines);
3526 while (++line < view->line + view->lines && line->data) {
3527 if (!status_update_file(view, line->data, line->type))
3528 report("Failed to update file status");
3531 if (!line[-1].data) {
3532 report("Nothing to update");
3536 } else if (!status_update_file(view, line->data, line->type)) {
3537 report("Failed to update file status");
3542 status_request(struct view *view, enum request request, struct line *line)
3544 struct status *status = line->data;
3547 case REQ_STATUS_UPDATE:
3548 status_update(view);
3551 case REQ_STATUS_MERGE:
3552 if (!status || status->status != 'U') {
3553 report("Merging only possible for files with unmerged status ('U').");
3556 open_mergetool(status->name);
3563 open_editor(status->status != '?', status->name);
3567 /* After returning the status view has been split to
3568 * show the stage view. No further reloading is
3570 status_enter(view, line);
3574 /* Simply reload the view. */
3581 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3587 status_select(struct view *view, struct line *line)
3589 struct status *status = line->data;
3590 char file[SIZEOF_STR] = "all files";
3594 if (status && !string_format(file, "'%s'", status->name))
3597 if (!status && line[1].type == LINE_STAT_NONE)
3600 switch (line->type) {
3601 case LINE_STAT_STAGED:
3602 text = "Press %s to unstage %s for commit";
3605 case LINE_STAT_UNSTAGED:
3606 text = "Press %s to stage %s for commit";
3609 case LINE_STAT_UNTRACKED:
3610 text = "Press %s to stage %s for addition";
3613 case LINE_STAT_NONE:
3614 text = "Nothing to update";
3618 die("line type %d not handled in switch", line->type);
3621 if (status && status->status == 'U') {
3622 text = "Press %s to resolve conflict in %s";
3623 key = get_key(REQ_STATUS_MERGE);
3626 key = get_key(REQ_STATUS_UPDATE);
3629 string_format(view->ref, text, key, file);
3633 status_grep(struct view *view, struct line *line)
3635 struct status *status = line->data;
3636 enum { S_STATUS, S_NAME, S_END } state;
3643 for (state = S_STATUS; state < S_END; state++) {
3647 case S_NAME: text = status->name; break;
3649 buf[0] = status->status;
3657 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3664 static struct view_ops status_ops = {
3676 stage_diff_line(FILE *pipe, struct line *line)
3678 char *buf = line->data;
3679 size_t bufsize = strlen(buf);
3682 while (!ferror(pipe) && written < bufsize) {
3683 written += fwrite(buf + written, 1, bufsize - written, pipe);
3688 return written == bufsize;
3691 static struct line *
3692 stage_diff_hdr(struct view *view, struct line *line)
3694 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3695 struct line *diff_hdr;
3697 if (line->type == LINE_DIFF_CHUNK)
3698 diff_hdr = line - 1;
3700 diff_hdr = view->line + 1;
3702 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3703 if (diff_hdr->type == LINE_DIFF_HEADER)
3706 diff_hdr += diff_hdr_dir;
3713 stage_update_chunk(struct view *view, struct line *line)
3715 char cmd[SIZEOF_STR];
3717 struct line *diff_hdr, *diff_chunk, *diff_end;
3720 diff_hdr = stage_diff_hdr(view, line);
3725 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3728 if (!string_format_from(cmd, &cmdsize,
3729 "git apply --cached %s - && "
3730 "git update-index -q --unmerged --refresh 2>/dev/null",
3731 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3734 pipe = popen(cmd, "w");
3738 diff_end = view->line + view->lines;
3739 if (line->type != LINE_DIFF_CHUNK) {
3740 diff_chunk = diff_hdr;
3743 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3744 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3745 diff_chunk->type == LINE_DIFF_HEADER)
3746 diff_end = diff_chunk;
3750 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3751 switch (diff_hdr->type) {
3752 case LINE_DIFF_HEADER:
3753 case LINE_DIFF_INDEX:
3763 if (!stage_diff_line(pipe, diff_hdr++)) {
3770 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3775 if (diff_chunk != diff_end)
3782 stage_update(struct view *view, struct line *line)
3784 if (stage_line_type != LINE_STAT_UNTRACKED &&
3785 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3786 if (!stage_update_chunk(view, line)) {
3787 report("Failed to apply chunk");
3791 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3792 report("Failed to update file");
3796 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3798 view = VIEW(REQ_VIEW_STATUS);
3799 if (view_is_displayed(view))
3800 status_enter(view, &view->line[view->lineno]);
3804 stage_request(struct view *view, enum request request, struct line *line)
3807 case REQ_STATUS_UPDATE:
3808 stage_update(view, line);
3812 if (!stage_status.name[0])
3815 open_editor(stage_status.status != '?', stage_status.name);
3819 pager_request(view, request, line);
3829 static struct view_ops stage_ops = {
3845 char id[SIZEOF_REV]; /* SHA1 ID. */
3846 char title[128]; /* First line of the commit message. */
3847 char author[75]; /* Author of the commit. */
3848 struct tm time; /* Date from the author ident. */
3849 struct ref **refs; /* Repository references. */
3850 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3851 size_t graph_size; /* The width of the graph array. */
3854 /* Size of rev graph with no "padding" columns */
3855 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3858 struct rev_graph *prev, *next, *parents;
3859 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3861 struct commit *commit;
3865 /* Parents of the commit being visualized. */
3866 static struct rev_graph graph_parents[4];
3868 /* The current stack of revisions on the graph. */
3869 static struct rev_graph graph_stacks[4] = {
3870 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3871 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3872 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3873 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3877 graph_parent_is_merge(struct rev_graph *graph)
3879 return graph->parents->size > 1;
3883 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3885 struct commit *commit = graph->commit;
3887 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3888 commit->graph[commit->graph_size++] = symbol;
3892 done_rev_graph(struct rev_graph *graph)
3894 if (graph_parent_is_merge(graph) &&
3895 graph->pos < graph->size - 1 &&
3896 graph->next->size == graph->size + graph->parents->size - 1) {
3897 size_t i = graph->pos + graph->parents->size - 1;
3899 graph->commit->graph_size = i * 2;
3900 while (i < graph->next->size - 1) {
3901 append_to_rev_graph(graph, ' ');
3902 append_to_rev_graph(graph, '\\');
3907 graph->size = graph->pos = 0;
3908 graph->commit = NULL;
3909 memset(graph->parents, 0, sizeof(*graph->parents));
3913 push_rev_graph(struct rev_graph *graph, char *parent)
3917 /* "Collapse" duplicate parents lines.
3919 * FIXME: This needs to also update update the drawn graph but
3920 * for now it just serves as a method for pruning graph lines. */
3921 for (i = 0; i < graph->size; i++)
3922 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3925 if (graph->size < SIZEOF_REVITEMS) {
3926 string_copy_rev(graph->rev[graph->size++], parent);
3931 get_rev_graph_symbol(struct rev_graph *graph)
3935 if (graph->parents->size == 0)
3936 symbol = REVGRAPH_INIT;
3937 else if (graph_parent_is_merge(graph))
3938 symbol = REVGRAPH_MERGE;
3939 else if (graph->pos >= graph->size)
3940 symbol = REVGRAPH_BRANCH;
3942 symbol = REVGRAPH_COMMIT;
3948 draw_rev_graph(struct rev_graph *graph)
3951 chtype separator, line;
3953 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3954 static struct rev_filler fillers[] = {
3955 { ' ', REVGRAPH_LINE },
3960 chtype symbol = get_rev_graph_symbol(graph);
3961 struct rev_filler *filler;
3964 filler = &fillers[DEFAULT];
3966 for (i = 0; i < graph->pos; i++) {
3967 append_to_rev_graph(graph, filler->line);
3968 if (graph_parent_is_merge(graph->prev) &&
3969 graph->prev->pos == i)
3970 filler = &fillers[RSHARP];
3972 append_to_rev_graph(graph, filler->separator);
3975 /* Place the symbol for this revision. */
3976 append_to_rev_graph(graph, symbol);
3978 if (graph->prev->size > graph->size)
3979 filler = &fillers[RDIAG];
3981 filler = &fillers[DEFAULT];
3985 for (; i < graph->size; i++) {
3986 append_to_rev_graph(graph, filler->separator);
3987 append_to_rev_graph(graph, filler->line);
3988 if (graph_parent_is_merge(graph->prev) &&
3989 i < graph->prev->pos + graph->parents->size)
3990 filler = &fillers[RSHARP];
3991 if (graph->prev->size > graph->size)
3992 filler = &fillers[LDIAG];
3995 if (graph->prev->size > graph->size) {
3996 append_to_rev_graph(graph, filler->separator);
3997 if (filler->line != ' ')
3998 append_to_rev_graph(graph, filler->line);
4002 /* Prepare the next rev graph */
4004 prepare_rev_graph(struct rev_graph *graph)
4008 /* First, traverse all lines of revisions up to the active one. */
4009 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4010 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4013 push_rev_graph(graph->next, graph->rev[graph->pos]);
4016 /* Interleave the new revision parent(s). */
4017 for (i = 0; i < graph->parents->size; i++)
4018 push_rev_graph(graph->next, graph->parents->rev[i]);
4020 /* Lastly, put any remaining revisions. */
4021 for (i = graph->pos + 1; i < graph->size; i++)
4022 push_rev_graph(graph->next, graph->rev[i]);
4026 update_rev_graph(struct rev_graph *graph)
4028 /* If this is the finalizing update ... */
4030 prepare_rev_graph(graph);
4032 /* Graph visualization needs a one rev look-ahead,
4033 * so the first update doesn't visualize anything. */
4034 if (!graph->prev->commit)
4037 draw_rev_graph(graph->prev);
4038 done_rev_graph(graph->prev->prev);
4047 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4049 char buf[DATE_COLS + 1];
4050 struct commit *commit = line->data;
4051 enum line_type type;
4057 if (!*commit->author)
4060 wmove(view->win, lineno, col);
4064 wattrset(view->win, get_line_attr(type));
4065 wchgat(view->win, -1, 0, type, NULL);
4068 type = LINE_MAIN_COMMIT;
4069 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4072 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4073 waddnstr(view->win, buf, timelen);
4074 waddstr(view->win, " ");
4077 wmove(view->win, lineno, col);
4078 if (type != LINE_CURSOR)
4079 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4082 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
4084 authorlen = strlen(commit->author);
4085 if (authorlen > AUTHOR_COLS - 2) {
4086 authorlen = AUTHOR_COLS - 2;
4092 waddnstr(view->win, commit->author, authorlen);
4093 if (type != LINE_CURSOR)
4094 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
4095 waddch(view->win, '~');
4097 waddstr(view->win, commit->author);
4102 if (opt_rev_graph && commit->graph_size) {
4105 if (type != LINE_CURSOR)
4106 wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4107 wmove(view->win, lineno, col);
4108 /* Using waddch() instead of waddnstr() ensures that
4109 * they'll be rendered correctly for the cursor line. */
4110 for (i = 0; i < commit->graph_size; i++)
4111 waddch(view->win, commit->graph[i]);
4113 waddch(view->win, ' ');
4114 col += commit->graph_size + 1;
4116 if (type != LINE_CURSOR)
4117 wattrset(view->win, A_NORMAL);
4119 wmove(view->win, lineno, col);
4125 if (type == LINE_CURSOR)
4127 else if (commit->refs[i]->tag)
4128 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4129 else if (commit->refs[i]->remote)
4130 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4132 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4133 waddstr(view->win, "[");
4134 waddstr(view->win, commit->refs[i]->name);
4135 waddstr(view->win, "]");
4136 if (type != LINE_CURSOR)
4137 wattrset(view->win, A_NORMAL);
4138 waddstr(view->win, " ");
4139 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
4140 } while (commit->refs[i++]->next);
4143 if (type != LINE_CURSOR)
4144 wattrset(view->win, get_line_attr(type));
4147 int titlelen = strlen(commit->title);
4149 if (col + titlelen > view->width)
4150 titlelen = view->width - col;
4152 waddnstr(view->win, commit->title, titlelen);
4158 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4160 main_read(struct view *view, char *line)
4162 static struct rev_graph *graph = graph_stacks;
4163 enum line_type type;
4164 struct commit *commit;
4167 update_rev_graph(graph);
4171 type = get_line_type(line);
4172 if (type == LINE_COMMIT) {
4173 commit = calloc(1, sizeof(struct commit));
4177 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
4178 commit->refs = get_refs(commit->id);
4179 graph->commit = commit;
4180 add_line_data(view, commit, LINE_MAIN_COMMIT);
4186 commit = view->line[view->lines - 1].data;
4190 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4195 /* Parse author lines where the name may be empty:
4196 * author <email@address.tld> 1138474660 +0100
4198 char *ident = line + STRING_SIZE("author ");
4199 char *nameend = strchr(ident, '<');
4200 char *emailend = strchr(ident, '>');
4202 if (!nameend || !emailend)
4205 update_rev_graph(graph);
4206 graph = graph->next;
4208 *nameend = *emailend = 0;
4209 ident = chomp_string(ident);
4211 ident = chomp_string(nameend + 1);
4216 string_ncopy(commit->author, ident, strlen(ident));
4218 /* Parse epoch and timezone */
4219 if (emailend[1] == ' ') {
4220 char *secs = emailend + 2;
4221 char *zone = strchr(secs, ' ');
4222 time_t time = (time_t) atol(secs);
4224 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4228 tz = ('0' - zone[1]) * 60 * 60 * 10;
4229 tz += ('0' - zone[2]) * 60 * 60;
4230 tz += ('0' - zone[3]) * 60;
4231 tz += ('0' - zone[4]) * 60;
4239 gmtime_r(&time, &commit->time);
4244 /* Fill in the commit title if it has not already been set. */
4245 if (commit->title[0])
4248 /* Require titles to start with a non-space character at the
4249 * offset used by git log. */
4250 if (strncmp(line, " ", 4))
4253 /* Well, if the title starts with a whitespace character,
4254 * try to be forgiving. Otherwise we end up with no title. */
4255 while (isspace(*line))
4259 /* FIXME: More graceful handling of titles; append "..." to
4260 * shortened titles, etc. */
4262 string_ncopy(commit->title, line, strlen(line));
4269 main_request(struct view *view, enum request request, struct line *line)
4271 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4273 if (request == REQ_ENTER)
4274 open_view(view, REQ_VIEW_DIFF, flags);
4282 main_grep(struct view *view, struct line *line)
4284 struct commit *commit = line->data;
4285 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4286 char buf[DATE_COLS + 1];
4289 for (state = S_TITLE; state < S_END; state++) {
4293 case S_TITLE: text = commit->title; break;
4294 case S_AUTHOR: text = commit->author; break;
4296 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4305 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4313 main_select(struct view *view, struct line *line)
4315 struct commit *commit = line->data;
4317 string_copy_rev(view->ref, commit->id);
4318 string_copy_rev(ref_commit, view->ref);
4321 static struct view_ops main_ops = {
4333 * Unicode / UTF-8 handling
4335 * NOTE: Much of the following code for dealing with unicode is derived from
4336 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4337 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4340 /* I've (over)annotated a lot of code snippets because I am not entirely
4341 * confident that the approach taken by this small UTF-8 interface is correct.
4345 unicode_width(unsigned long c)
4348 (c <= 0x115f /* Hangul Jamo */
4351 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4353 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4354 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4355 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4356 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4357 || (c >= 0xffe0 && c <= 0xffe6)
4358 || (c >= 0x20000 && c <= 0x2fffd)
4359 || (c >= 0x30000 && c <= 0x3fffd)))
4365 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4366 * Illegal bytes are set one. */
4367 static const unsigned char utf8_bytes[256] = {
4368 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,
4369 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,
4370 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,
4371 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,
4372 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,
4373 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,
4374 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,
4375 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,
4378 /* Decode UTF-8 multi-byte representation into a unicode character. */
4379 static inline unsigned long
4380 utf8_to_unicode(const char *string, size_t length)
4382 unsigned long unicode;
4386 unicode = string[0];
4389 unicode = (string[0] & 0x1f) << 6;
4390 unicode += (string[1] & 0x3f);
4393 unicode = (string[0] & 0x0f) << 12;
4394 unicode += ((string[1] & 0x3f) << 6);
4395 unicode += (string[2] & 0x3f);
4398 unicode = (string[0] & 0x0f) << 18;
4399 unicode += ((string[1] & 0x3f) << 12);
4400 unicode += ((string[2] & 0x3f) << 6);
4401 unicode += (string[3] & 0x3f);
4404 unicode = (string[0] & 0x0f) << 24;
4405 unicode += ((string[1] & 0x3f) << 18);
4406 unicode += ((string[2] & 0x3f) << 12);
4407 unicode += ((string[3] & 0x3f) << 6);
4408 unicode += (string[4] & 0x3f);
4411 unicode = (string[0] & 0x01) << 30;
4412 unicode += ((string[1] & 0x3f) << 24);
4413 unicode += ((string[2] & 0x3f) << 18);
4414 unicode += ((string[3] & 0x3f) << 12);
4415 unicode += ((string[4] & 0x3f) << 6);
4416 unicode += (string[5] & 0x3f);
4419 die("Invalid unicode length");
4422 /* Invalid characters could return the special 0xfffd value but NUL
4423 * should be just as good. */
4424 return unicode > 0xffff ? 0 : unicode;
4427 /* Calculates how much of string can be shown within the given maximum width
4428 * and sets trimmed parameter to non-zero value if all of string could not be
4431 * Additionally, adds to coloffset how many many columns to move to align with
4432 * the expected position. Takes into account how multi-byte and double-width
4433 * characters will effect the cursor position.
4435 * Returns the number of bytes to output from string to satisfy max_width. */
4437 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4439 const char *start = string;
4440 const char *end = strchr(string, '\0');
4446 while (string < end) {
4447 int c = *(unsigned char *) string;
4448 unsigned char bytes = utf8_bytes[c];
4450 unsigned long unicode;
4452 if (string + bytes > end)
4455 /* Change representation to figure out whether
4456 * it is a single- or double-width character. */
4458 unicode = utf8_to_unicode(string, bytes);
4459 /* FIXME: Graceful handling of invalid unicode character. */
4463 ucwidth = unicode_width(unicode);
4465 if (width > max_width) {
4470 /* The column offset collects the differences between the
4471 * number of bytes encoding a character and the number of
4472 * columns will be used for rendering said character.
4474 * So if some character A is encoded in 2 bytes, but will be
4475 * represented on the screen using only 1 byte this will and up
4476 * adding 1 to the multi-byte column offset.
4478 * Assumes that no double-width character can be encoding in
4479 * less than two bytes. */
4480 if (bytes > ucwidth)
4481 mbwidth += bytes - ucwidth;
4486 *coloffset += mbwidth;
4488 return string - start;
4496 /* Whether or not the curses interface has been initialized. */
4497 static bool cursed = FALSE;
4499 /* The status window is used for polling keystrokes. */
4500 static WINDOW *status_win;
4502 static bool status_empty = TRUE;
4504 /* Update status and title window. */
4506 report(const char *msg, ...)
4508 struct view *view = display[current_view];
4514 char buf[SIZEOF_STR];
4517 va_start(args, msg);
4518 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4519 buf[sizeof(buf) - 1] = 0;
4520 buf[sizeof(buf) - 2] = '.';
4521 buf[sizeof(buf) - 3] = '.';
4522 buf[sizeof(buf) - 4] = '.';
4528 if (!status_empty || *msg) {
4531 va_start(args, msg);
4533 wmove(status_win, 0, 0);
4535 vwprintw(status_win, msg, args);
4536 status_empty = FALSE;
4538 status_empty = TRUE;
4540 wclrtoeol(status_win);
4541 wrefresh(status_win);
4546 update_view_title(view);
4547 update_display_cursor(view);
4550 /* Controls when nodelay should be in effect when polling user input. */
4552 set_nonblocking_input(bool loading)
4554 static unsigned int loading_views;
4556 if ((loading == FALSE && loading_views-- == 1) ||
4557 (loading == TRUE && loading_views++ == 0))
4558 nodelay(status_win, loading);
4566 /* Initialize the curses library */
4567 if (isatty(STDIN_FILENO)) {
4568 cursed = !!initscr();
4570 /* Leave stdin and stdout alone when acting as a pager. */
4571 FILE *io = fopen("/dev/tty", "r+");
4574 die("Failed to open /dev/tty");
4575 cursed = !!newterm(NULL, io, io);
4579 die("Failed to initialize curses");
4581 nonl(); /* Tell curses not to do NL->CR/NL on output */
4582 cbreak(); /* Take input chars one at a time, no wait for \n */
4583 noecho(); /* Don't echo input */
4584 leaveok(stdscr, TRUE);
4589 getmaxyx(stdscr, y, x);
4590 status_win = newwin(1, 0, y - 1, 0);
4592 die("Failed to create status window");
4594 /* Enable keyboard mapping */
4595 keypad(status_win, TRUE);
4596 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4600 read_prompt(const char *prompt)
4602 enum { READING, STOP, CANCEL } status = READING;
4603 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4606 while (status == READING) {
4612 foreach_view (view, i)
4617 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4618 wclrtoeol(status_win);
4620 /* Refresh, accept single keystroke of input */
4621 key = wgetch(status_win);
4626 status = pos ? STOP : CANCEL;
4644 if (pos >= sizeof(buf)) {
4645 report("Input string too long");
4650 buf[pos++] = (char) key;
4654 /* Clear the status window */
4655 status_empty = FALSE;
4658 if (status == CANCEL)
4667 * Repository references
4670 static struct ref *refs;
4671 static size_t refs_size;
4673 /* Id <-> ref store */
4674 static struct ref ***id_refs;
4675 static size_t id_refs_size;
4677 static struct ref **
4680 struct ref ***tmp_id_refs;
4681 struct ref **ref_list = NULL;
4682 size_t ref_list_size = 0;
4685 for (i = 0; i < id_refs_size; i++)
4686 if (!strcmp(id, id_refs[i][0]->id))
4689 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4693 id_refs = tmp_id_refs;
4695 for (i = 0; i < refs_size; i++) {
4698 if (strcmp(id, refs[i].id))
4701 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4709 if (ref_list_size > 0)
4710 ref_list[ref_list_size - 1]->next = 1;
4711 ref_list[ref_list_size] = &refs[i];
4713 /* XXX: The properties of the commit chains ensures that we can
4714 * safely modify the shared ref. The repo references will
4715 * always be similar for the same id. */
4716 ref_list[ref_list_size]->next = 0;
4721 id_refs[id_refs_size++] = ref_list;
4727 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4731 bool remote = FALSE;
4733 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4734 /* Commits referenced by tags has "^{}" appended. */
4735 if (name[namelen - 1] != '}')
4738 while (namelen > 0 && name[namelen] != '^')
4742 namelen -= STRING_SIZE("refs/tags/");
4743 name += STRING_SIZE("refs/tags/");
4745 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4747 namelen -= STRING_SIZE("refs/remotes/");
4748 name += STRING_SIZE("refs/remotes/");
4750 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4751 namelen -= STRING_SIZE("refs/heads/");
4752 name += STRING_SIZE("refs/heads/");
4754 } else if (!strcmp(name, "HEAD")) {
4758 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4762 ref = &refs[refs_size++];
4763 ref->name = malloc(namelen + 1);
4767 strncpy(ref->name, name, namelen);
4768 ref->name[namelen] = 0;
4770 ref->remote = remote;
4771 string_copy_rev(ref->id, id);
4779 const char *cmd_env = getenv("TIG_LS_REMOTE");
4780 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4782 return read_properties(popen(cmd, "r"), "\t", read_ref);
4786 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4788 if (!strcmp(name, "i18n.commitencoding"))
4789 string_ncopy(opt_encoding, value, valuelen);
4791 if (!strcmp(name, "core.editor"))
4792 string_ncopy(opt_editor, value, valuelen);
4798 load_repo_config(void)
4800 return read_properties(popen(GIT_CONFIG " --list", "r"),
4801 "=", read_repo_config_option);
4805 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4807 if (!opt_git_dir[0]) {
4808 string_ncopy(opt_git_dir, name, namelen);
4810 } else if (opt_is_inside_work_tree == -1) {
4811 /* This can be 3 different values depending on the
4812 * version of git being used. If git-rev-parse does not
4813 * understand --is-inside-work-tree it will simply echo
4814 * the option else either "true" or "false" is printed.
4815 * Default to true for the unknown case. */
4816 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4819 string_ncopy(opt_cdup, name, namelen);
4825 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4826 * must be the last one! */
4828 load_repo_info(void)
4830 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4831 "=", read_repo_info);
4835 read_properties(FILE *pipe, const char *separators,
4836 int (*read_property)(char *, size_t, char *, size_t))
4838 char buffer[BUFSIZ];
4845 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4850 name = chomp_string(name);
4851 namelen = strcspn(name, separators);
4853 if (name[namelen]) {
4855 value = chomp_string(name + namelen + 1);
4856 valuelen = strlen(value);
4863 state = read_property(name, namelen, value, valuelen);
4866 if (state != ERR && ferror(pipe))
4879 static void __NORETURN
4882 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4888 static void __NORETURN
4889 die(const char *err, ...)
4895 va_start(args, err);
4896 fputs("tig: ", stderr);
4897 vfprintf(stderr, err, args);
4898 fputs("\n", stderr);
4905 main(int argc, char *argv[])
4908 enum request request;
4911 signal(SIGINT, quit);
4913 if (setlocale(LC_ALL, "")) {
4914 char *codeset = nl_langinfo(CODESET);
4916 string_ncopy(opt_codeset, codeset, strlen(codeset));
4919 if (load_repo_info() == ERR)
4920 die("Failed to load repo info.");
4922 if (load_options() == ERR)
4923 die("Failed to load user config.");
4925 /* Load the repo config file so options can be overwritten from
4926 * the command line. */
4927 if (load_repo_config() == ERR)
4928 die("Failed to load repo config.");
4930 if (!parse_options(argc, argv))
4933 /* Require a git repository unless when running in pager mode. */
4934 if (!opt_git_dir[0])
4935 die("Not a git repository");
4937 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4938 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4939 if (opt_iconv == ICONV_NONE)
4940 die("Failed to initialize character set conversion");
4943 if (load_refs() == ERR)
4944 die("Failed to load refs.");
4946 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4947 view->cmd_env = getenv(view->cmd_env);
4949 request = opt_request;
4953 while (view_driver(display[current_view], request)) {
4957 foreach_view (view, i)
4960 /* Refresh, accept single keystroke of input */
4961 key = wgetch(status_win);
4963 /* wgetch() with nodelay() enabled returns ERR when there's no
4970 request = get_keybinding(display[current_view]->keymap, key);
4972 /* Some low-level request handling. This keeps access to
4973 * status_win restricted. */
4977 char *cmd = read_prompt(":");
4979 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4980 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4981 opt_request = REQ_VIEW_DIFF;
4983 opt_request = REQ_VIEW_PAGER;
4992 case REQ_SEARCH_BACK:
4994 const char *prompt = request == REQ_SEARCH
4996 char *search = read_prompt(prompt);
4999 string_ncopy(opt_search, search, strlen(search));
5004 case REQ_SCREEN_RESIZE:
5008 getmaxyx(stdscr, height, width);
5010 /* Resize the status view and let the view driver take
5011 * care of resizing the displayed views. */
5012 wresize(status_win, 1, width);
5013 mvwin(status_win, height - 1, 0);
5014 wrefresh(status_win);