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>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
51 #define __NORETURN __attribute__((__noreturn__))
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x) ((x) >= 0 ? (x) : -(x))
64 #define MIN(x, y) ((x) < (y) ? (x) : (y))
66 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x) (sizeof(x) - 1)
69 #define SIZEOF_STR 1024 /* Default string size. */
70 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
75 #define REVGRAPH_INIT 'I'
76 #define REVGRAPH_MERGE 'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND '^'
80 #define REVGRAPH_LINE '|'
82 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT (-1)
87 #define ICONV_NONE ((iconv_t) -1)
89 #define ICONV_CONST /* nothing */
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT "%Y-%m-%d %H:%M"
94 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS 20
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 1
103 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
106 #define GIT_CONFIG "git config"
109 #define TIG_LS_REMOTE \
110 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
112 #define TIG_DIFF_CMD \
113 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
115 #define TIG_LOG_CMD \
116 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
118 #define TIG_MAIN_CMD \
119 "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
121 #define TIG_TREE_CMD \
124 #define TIG_BLOB_CMD \
125 "git cat-file blob %s"
127 /* XXX: Needs to be defined to the empty string. */
128 #define TIG_HELP_CMD ""
129 #define TIG_PAGER_CMD ""
130 #define TIG_STATUS_CMD ""
131 #define TIG_STAGE_CMD ""
133 /* Some ascii-shorthands fitted into the ncurses namespace. */
135 #define KEY_RETURN '\r'
140 char *name; /* Ref name; tag or head names are shortened. */
141 char id[SIZEOF_REV]; /* Commit SHA1 ID */
142 unsigned int tag:1; /* Is it a tag? */
143 unsigned int ltag:1; /* If so, is the tag local? */
144 unsigned int remote:1; /* Is it a remote ref? */
145 unsigned int next:1; /* For ref lists: are there more refs? */
148 static struct ref **get_refs(char *id);
157 set_from_int_map(struct int_map *map, size_t map_size,
158 int *value, const char *name, int namelen)
163 for (i = 0; i < map_size; i++)
164 if (namelen == map[i].namelen &&
165 !strncasecmp(name, map[i].name, namelen)) {
166 *value = map[i].value;
179 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
181 if (srclen > dstlen - 1)
184 strncpy(dst, src, srclen);
188 /* Shorthands for safely copying into a fixed buffer. */
190 #define string_copy(dst, src) \
191 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
193 #define string_ncopy(dst, src, srclen) \
194 string_ncopy_do(dst, sizeof(dst), src, srclen)
196 #define string_copy_rev(dst, src) \
197 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
199 #define string_add(dst, from, src) \
200 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 chomp_string(char *name)
207 while (isspace(*name))
210 namelen = strlen(name) - 1;
211 while (namelen > 0 && isspace(name[namelen]))
218 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
221 size_t pos = bufpos ? *bufpos : 0;
224 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
230 return pos >= bufsize ? FALSE : TRUE;
233 #define string_format(buf, fmt, args...) \
234 string_nformat(buf, sizeof(buf), NULL, fmt, args)
236 #define string_format_from(buf, from, fmt, args...) \
237 string_nformat(buf, sizeof(buf), from, fmt, args)
240 string_enum_compare(const char *str1, const char *str2, int len)
244 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
246 /* Diff-Header == DIFF_HEADER */
247 for (i = 0; i < len; i++) {
248 if (toupper(str1[i]) == toupper(str2[i]))
251 if (string_enum_sep(str1[i]) &&
252 string_enum_sep(str2[i]))
255 return str1[i] - str2[i];
263 * NOTE: The following is a slightly modified copy of the git project's shell
264 * quoting routines found in the quote.c file.
266 * Help to copy the thing properly quoted for the shell safety. any single
267 * quote is replaced with '\'', any exclamation point is replaced with '\!',
268 * and the whole thing is enclosed in a
271 * original sq_quote result
272 * name ==> name ==> 'name'
273 * a b ==> a b ==> 'a b'
274 * a'b ==> a'\''b ==> 'a'\''b'
275 * a!b ==> a'\!'b ==> 'a'\!'b'
279 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
283 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
286 while ((c = *src++)) {
287 if (c == '\'' || c == '!') {
298 if (bufsize < SIZEOF_STR)
310 /* XXX: Keep the view request first and in sync with views[]. */ \
311 REQ_GROUP("View switching") \
312 REQ_(VIEW_MAIN, "Show main view"), \
313 REQ_(VIEW_DIFF, "Show diff view"), \
314 REQ_(VIEW_LOG, "Show log view"), \
315 REQ_(VIEW_TREE, "Show tree view"), \
316 REQ_(VIEW_BLOB, "Show blob view"), \
317 REQ_(VIEW_HELP, "Show help page"), \
318 REQ_(VIEW_PAGER, "Show pager view"), \
319 REQ_(VIEW_STATUS, "Show status view"), \
320 REQ_(VIEW_STAGE, "Show stage view"), \
322 REQ_GROUP("View manipulation") \
323 REQ_(ENTER, "Enter current line and scroll"), \
324 REQ_(NEXT, "Move to next"), \
325 REQ_(PREVIOUS, "Move to previous"), \
326 REQ_(VIEW_NEXT, "Move focus to next view"), \
327 REQ_(REFRESH, "Reload and refresh"), \
328 REQ_(VIEW_CLOSE, "Close the current view"), \
329 REQ_(QUIT, "Close all views and quit"), \
331 REQ_GROUP("Cursor navigation") \
332 REQ_(MOVE_UP, "Move cursor one line up"), \
333 REQ_(MOVE_DOWN, "Move cursor one line down"), \
334 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
335 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
336 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
337 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
339 REQ_GROUP("Scrolling") \
340 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
341 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
342 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
343 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
345 REQ_GROUP("Searching") \
346 REQ_(SEARCH, "Search the view"), \
347 REQ_(SEARCH_BACK, "Search backwards in the view"), \
348 REQ_(FIND_NEXT, "Find next search match"), \
349 REQ_(FIND_PREV, "Find previous search match"), \
352 REQ_(PROMPT, "Bring up the prompt"), \
353 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
354 REQ_(SCREEN_RESIZE, "Resize the screen"), \
355 REQ_(SHOW_VERSION, "Show version information"), \
356 REQ_(STOP_LOADING, "Stop all loading views"), \
357 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
358 REQ_(TOGGLE_DATE, "Toggle date display"), \
359 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
360 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
361 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
362 REQ_(STATUS_UPDATE, "Update file status"), \
363 REQ_(STATUS_MERGE, "Merge file using external tool"), \
364 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
365 REQ_(EDIT, "Open in editor"), \
366 REQ_(NONE, "Do nothing")
369 /* User action requests. */
371 #define REQ_GROUP(help)
372 #define REQ_(req, help) REQ_##req
374 /* Offset all requests to avoid conflicts with ncurses getch values. */
375 REQ_OFFSET = KEY_MAX + 1,
382 struct request_info {
383 enum request request;
389 static struct request_info req_info[] = {
390 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
391 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
398 get_request(const char *name)
400 int namelen = strlen(name);
403 for (i = 0; i < ARRAY_SIZE(req_info); i++)
404 if (req_info[i].namelen == namelen &&
405 !string_enum_compare(req_info[i].name, name, namelen))
406 return req_info[i].request;
416 static const char usage[] =
417 "tig " TIG_VERSION " (" __DATE__ ")\n"
419 "Usage: tig [options] [revs] [--] [paths]\n"
420 " or: tig show [options] [revs] [--] [paths]\n"
422 " or: tig < [git command output]\n"
425 " -v, --version Show version and exit\n"
426 " -h, --help Show help message and exit\n";
428 /* Option and state variables. */
429 static bool opt_date = TRUE;
430 static bool opt_author = TRUE;
431 static bool opt_line_number = FALSE;
432 static bool opt_rev_graph = FALSE;
433 static bool opt_show_refs = TRUE;
434 static int opt_num_interval = NUMBER_INTERVAL;
435 static int opt_tab_size = TABSIZE;
436 static enum request opt_request = REQ_VIEW_MAIN;
437 static char opt_cmd[SIZEOF_STR] = "";
438 static char opt_path[SIZEOF_STR] = "";
439 static FILE *opt_pipe = NULL;
440 static char opt_encoding[20] = "UTF-8";
441 static bool opt_utf8 = TRUE;
442 static char opt_codeset[20] = "UTF-8";
443 static iconv_t opt_iconv = ICONV_NONE;
444 static char opt_search[SIZEOF_STR] = "";
445 static char opt_cdup[SIZEOF_STR] = "";
446 static char opt_git_dir[SIZEOF_STR] = "";
447 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
448 static char opt_editor[SIZEOF_STR] = "";
456 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
466 int namelen = strlen(name);
470 if (strncmp(opt, name, namelen))
473 if (opt[namelen] == '=')
474 value = opt + namelen + 1;
477 if (!short_name || opt[1] != short_name)
482 va_start(args, type);
483 if (type == OPT_INT) {
484 number = va_arg(args, int *);
486 *number = atoi(value);
493 /* Returns the index of log or diff command or -1 to exit. */
495 parse_options(int argc, char *argv[])
499 char *subcommand = NULL;
502 for (i = 1; i < argc; i++) {
505 if (!strcmp(opt, "log") ||
506 !strcmp(opt, "diff")) {
508 opt_request = opt[0] == 'l'
509 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
510 warn("`tig %s' has been deprecated", opt);
514 if (!strcmp(opt, "show")) {
516 opt_request = REQ_VIEW_DIFF;
520 if (!strcmp(opt, "status")) {
522 opt_request = REQ_VIEW_STATUS;
526 if (opt[0] && opt[0] != '-')
529 if (!strcmp(opt, "--")) {
534 if (check_option(opt, 'v', "version", OPT_NONE)) {
535 printf("tig version %s\n", TIG_VERSION);
539 if (check_option(opt, 'h', "help", OPT_NONE)) {
544 if (!strcmp(opt, "-S")) {
545 warn("`%s' has been deprecated; use `tig status' instead", opt);
546 opt_request = REQ_VIEW_STATUS;
550 if (!strcmp(opt, "-l")) {
551 opt_request = REQ_VIEW_LOG;
552 } else if (!strcmp(opt, "-d")) {
553 opt_request = REQ_VIEW_DIFF;
554 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
555 opt_line_number = TRUE;
556 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
557 opt_tab_size = MIN(opt_tab_size, TABSIZE);
559 if (altargc >= ARRAY_SIZE(altargv))
560 die("maximum number of arguments exceeded");
561 altargv[altargc++] = opt;
565 warn("`%s' has been deprecated", opt);
568 /* Check that no 'alt' arguments occured before a subcommand. */
569 if (subcommand && i < argc && altargc > 0)
570 die("unknown arguments before `%s'", argv[i]);
572 if (!isatty(STDIN_FILENO)) {
573 opt_request = REQ_VIEW_PAGER;
576 } else if (opt_request == REQ_VIEW_STATUS) {
578 warn("ignoring arguments after `%s'", argv[i]);
580 } else if (i < argc || altargc > 0) {
584 if (opt_request == REQ_VIEW_MAIN)
585 /* XXX: This is vulnerable to the user overriding
586 * options required for the main view parser. */
587 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
589 string_copy(opt_cmd, "git");
590 buf_size = strlen(opt_cmd);
592 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
593 opt_cmd[buf_size++] = ' ';
594 buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
597 while (buf_size < sizeof(opt_cmd) && i < argc) {
598 opt_cmd[buf_size++] = ' ';
599 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
602 if (buf_size >= sizeof(opt_cmd))
603 die("command too long");
605 opt_cmd[buf_size] = 0;
608 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
616 * Line-oriented content detection.
620 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
621 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
622 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
623 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
624 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
625 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
626 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
627 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
628 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
629 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
630 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
631 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
632 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
633 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
634 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
635 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
637 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
638 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
639 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
640 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
641 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
642 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
643 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
644 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
645 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
646 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
647 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
648 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
649 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
650 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
651 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
652 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
653 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
654 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
655 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
656 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
657 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
658 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
659 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
660 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
661 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
662 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
663 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
664 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
665 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
666 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
667 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
668 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
671 #define LINE(type, line, fg, bg, attr) \
678 const char *name; /* Option name. */
679 int namelen; /* Size of option name. */
680 const char *line; /* The start of line to match. */
681 int linelen; /* Size of string to match. */
682 int fg, bg, attr; /* Color and text attributes for the lines. */
685 static struct line_info line_info[] = {
686 #define LINE(type, line, fg, bg, attr) \
687 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
692 static enum line_type
693 get_line_type(char *line)
695 int linelen = strlen(line);
698 for (type = 0; type < ARRAY_SIZE(line_info); type++)
699 /* Case insensitive search matches Signed-off-by lines better. */
700 if (linelen >= line_info[type].linelen &&
701 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
708 get_line_attr(enum line_type type)
710 assert(type < ARRAY_SIZE(line_info));
711 return COLOR_PAIR(type) | line_info[type].attr;
714 static struct line_info *
715 get_line_info(char *name, int namelen)
719 for (type = 0; type < ARRAY_SIZE(line_info); type++)
720 if (namelen == line_info[type].namelen &&
721 !string_enum_compare(line_info[type].name, name, namelen))
722 return &line_info[type];
730 int default_bg = line_info[LINE_DEFAULT].bg;
731 int default_fg = line_info[LINE_DEFAULT].fg;
736 if (assume_default_colors(default_fg, default_bg) == ERR) {
737 default_bg = COLOR_BLACK;
738 default_fg = COLOR_WHITE;
741 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
742 struct line_info *info = &line_info[type];
743 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
744 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
746 init_pair(type, fg, bg);
754 unsigned int selected:1;
756 void *data; /* User data */
766 enum request request;
767 struct keybinding *next;
770 static struct keybinding default_keybindings[] = {
772 { 'm', REQ_VIEW_MAIN },
773 { 'd', REQ_VIEW_DIFF },
774 { 'l', REQ_VIEW_LOG },
775 { 't', REQ_VIEW_TREE },
776 { 'f', REQ_VIEW_BLOB },
777 { 'p', REQ_VIEW_PAGER },
778 { 'h', REQ_VIEW_HELP },
779 { 'S', REQ_VIEW_STATUS },
780 { 'c', REQ_VIEW_STAGE },
782 /* View manipulation */
783 { 'q', REQ_VIEW_CLOSE },
784 { KEY_TAB, REQ_VIEW_NEXT },
785 { KEY_RETURN, REQ_ENTER },
786 { KEY_UP, REQ_PREVIOUS },
787 { KEY_DOWN, REQ_NEXT },
788 { 'R', REQ_REFRESH },
790 /* Cursor navigation */
791 { 'k', REQ_MOVE_UP },
792 { 'j', REQ_MOVE_DOWN },
793 { KEY_HOME, REQ_MOVE_FIRST_LINE },
794 { KEY_END, REQ_MOVE_LAST_LINE },
795 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
796 { ' ', REQ_MOVE_PAGE_DOWN },
797 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
798 { 'b', REQ_MOVE_PAGE_UP },
799 { '-', REQ_MOVE_PAGE_UP },
802 { KEY_IC, REQ_SCROLL_LINE_UP },
803 { KEY_DC, REQ_SCROLL_LINE_DOWN },
804 { 'w', REQ_SCROLL_PAGE_UP },
805 { 's', REQ_SCROLL_PAGE_DOWN },
809 { '?', REQ_SEARCH_BACK },
810 { 'n', REQ_FIND_NEXT },
811 { 'N', REQ_FIND_PREV },
815 { 'z', REQ_STOP_LOADING },
816 { 'v', REQ_SHOW_VERSION },
817 { 'r', REQ_SCREEN_REDRAW },
818 { '.', REQ_TOGGLE_LINENO },
819 { 'D', REQ_TOGGLE_DATE },
820 { 'A', REQ_TOGGLE_AUTHOR },
821 { 'g', REQ_TOGGLE_REV_GRAPH },
822 { 'F', REQ_TOGGLE_REFS },
824 { 'u', REQ_STATUS_UPDATE },
825 { 'M', REQ_STATUS_MERGE },
826 { ',', REQ_TREE_PARENT },
829 /* Using the ncurses SIGWINCH handler. */
830 { KEY_RESIZE, REQ_SCREEN_RESIZE },
833 #define KEYMAP_INFO \
846 #define KEYMAP_(name) KEYMAP_##name
851 static struct int_map keymap_table[] = {
852 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
857 #define set_keymap(map, name) \
858 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
860 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
863 add_keybinding(enum keymap keymap, enum request request, int key)
865 struct keybinding *keybinding;
867 keybinding = calloc(1, sizeof(*keybinding));
869 die("Failed to allocate keybinding");
871 keybinding->alias = key;
872 keybinding->request = request;
873 keybinding->next = keybindings[keymap];
874 keybindings[keymap] = keybinding;
877 /* Looks for a key binding first in the given map, then in the generic map, and
878 * lastly in the default keybindings. */
880 get_keybinding(enum keymap keymap, int key)
882 struct keybinding *kbd;
885 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
886 if (kbd->alias == key)
889 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
890 if (kbd->alias == key)
893 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
894 if (default_keybindings[i].alias == key)
895 return default_keybindings[i].request;
897 return (enum request) key;
906 static struct key key_table[] = {
907 { "Enter", KEY_RETURN },
909 { "Backspace", KEY_BACKSPACE },
911 { "Escape", KEY_ESC },
912 { "Left", KEY_LEFT },
913 { "Right", KEY_RIGHT },
915 { "Down", KEY_DOWN },
916 { "Insert", KEY_IC },
917 { "Delete", KEY_DC },
919 { "Home", KEY_HOME },
921 { "PageUp", KEY_PPAGE },
922 { "PageDown", KEY_NPAGE },
932 { "F10", KEY_F(10) },
933 { "F11", KEY_F(11) },
934 { "F12", KEY_F(12) },
938 get_key_value(const char *name)
942 for (i = 0; i < ARRAY_SIZE(key_table); i++)
943 if (!strcasecmp(key_table[i].name, name))
944 return key_table[i].value;
946 if (strlen(name) == 1 && isprint(*name))
953 get_key_name(int key_value)
955 static char key_char[] = "'X'";
959 for (key = 0; key < ARRAY_SIZE(key_table); key++)
960 if (key_table[key].value == key_value)
961 seq = key_table[key].name;
965 isprint(key_value)) {
966 key_char[1] = (char) key_value;
970 return seq ? seq : "'?'";
974 get_key(enum request request)
976 static char buf[BUFSIZ];
983 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
984 struct keybinding *keybinding = &default_keybindings[i];
986 if (keybinding->request != request)
989 if (!string_format_from(buf, &pos, "%s%s", sep,
990 get_key_name(keybinding->alias)))
991 return "Too many keybindings!";
1001 char cmd[SIZEOF_STR];
1004 static struct run_request *run_request;
1005 static size_t run_requests;
1008 add_run_request(enum keymap keymap, int key, int argc, char **argv)
1010 struct run_request *tmp;
1011 struct run_request req = { keymap, key };
1014 for (bufpos = 0; argc > 0; argc--, argv++)
1015 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1018 req.cmd[bufpos - 1] = 0;
1020 tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1025 run_request[run_requests++] = req;
1027 return REQ_NONE + run_requests;
1030 static struct run_request *
1031 get_run_request(enum request request)
1033 if (request <= REQ_NONE)
1035 return &run_request[request - REQ_NONE - 1];
1039 add_builtin_run_requests(void)
1046 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1047 { KEYMAP_GENERIC, 'G', { "git gc" } },
1051 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1054 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1055 if (req != REQ_NONE)
1056 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1061 * User config file handling.
1064 static struct int_map color_map[] = {
1065 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1077 #define set_color(color, name) \
1078 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1080 static struct int_map attr_map[] = {
1081 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1088 ATTR_MAP(UNDERLINE),
1091 #define set_attribute(attr, name) \
1092 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1094 static int config_lineno;
1095 static bool config_errors;
1096 static char *config_msg;
1098 /* Wants: object fgcolor bgcolor [attr] */
1100 option_color_command(int argc, char *argv[])
1102 struct line_info *info;
1104 if (argc != 3 && argc != 4) {
1105 config_msg = "Wrong number of arguments given to color command";
1109 info = get_line_info(argv[0], strlen(argv[0]));
1111 config_msg = "Unknown color name";
1115 if (set_color(&info->fg, argv[1]) == ERR ||
1116 set_color(&info->bg, argv[2]) == ERR) {
1117 config_msg = "Unknown color";
1121 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1122 config_msg = "Unknown attribute";
1129 static bool parse_bool(const char *s)
1131 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1132 !strcmp(s, "yes")) ? TRUE : FALSE;
1135 /* Wants: name = value */
1137 option_set_command(int argc, char *argv[])
1140 config_msg = "Wrong number of arguments given to set command";
1144 if (strcmp(argv[1], "=")) {
1145 config_msg = "No value assigned";
1149 if (!strcmp(argv[0], "show-author")) {
1150 opt_author = parse_bool(argv[2]);
1154 if (!strcmp(argv[0], "show-date")) {
1155 opt_date = parse_bool(argv[2]);
1159 if (!strcmp(argv[0], "show-rev-graph")) {
1160 opt_rev_graph = parse_bool(argv[2]);
1164 if (!strcmp(argv[0], "show-refs")) {
1165 opt_show_refs = parse_bool(argv[2]);
1169 if (!strcmp(argv[0], "show-line-numbers")) {
1170 opt_line_number = parse_bool(argv[2]);
1174 if (!strcmp(argv[0], "line-number-interval")) {
1175 opt_num_interval = atoi(argv[2]);
1179 if (!strcmp(argv[0], "tab-size")) {
1180 opt_tab_size = atoi(argv[2]);
1184 if (!strcmp(argv[0], "commit-encoding")) {
1185 char *arg = argv[2];
1186 int delimiter = *arg;
1189 switch (delimiter) {
1192 for (arg++, i = 0; arg[i]; i++)
1193 if (arg[i] == delimiter) {
1198 string_ncopy(opt_encoding, arg, strlen(arg));
1203 config_msg = "Unknown variable name";
1207 /* Wants: mode request key */
1209 option_bind_command(int argc, char *argv[])
1211 enum request request;
1216 config_msg = "Wrong number of arguments given to bind command";
1220 if (set_keymap(&keymap, argv[0]) == ERR) {
1221 config_msg = "Unknown key map";
1225 key = get_key_value(argv[1]);
1227 config_msg = "Unknown key";
1231 request = get_request(argv[2]);
1232 if (request == REQ_NONE) {
1233 const char *obsolete[] = { "cherry-pick" };
1234 size_t namelen = strlen(argv[2]);
1237 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1238 if (namelen == strlen(obsolete[i]) &&
1239 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1240 config_msg = "Obsolete request name";
1245 if (request == REQ_NONE && *argv[2]++ == '!')
1246 request = add_run_request(keymap, key, argc - 2, argv + 2);
1247 if (request == REQ_NONE) {
1248 config_msg = "Unknown request name";
1252 add_keybinding(keymap, request, key);
1258 set_option(char *opt, char *value)
1265 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1266 argv[argc++] = value;
1269 /* Nothing more to tokenize or last available token. */
1270 if (!*value || argc >= ARRAY_SIZE(argv))
1274 while (isspace(*value))
1278 if (!strcmp(opt, "color"))
1279 return option_color_command(argc, argv);
1281 if (!strcmp(opt, "set"))
1282 return option_set_command(argc, argv);
1284 if (!strcmp(opt, "bind"))
1285 return option_bind_command(argc, argv);
1287 config_msg = "Unknown option command";
1292 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1297 config_msg = "Internal error";
1299 /* Check for comment markers, since read_properties() will
1300 * only ensure opt and value are split at first " \t". */
1301 optlen = strcspn(opt, "#");
1305 if (opt[optlen] != 0) {
1306 config_msg = "No option value";
1310 /* Look for comment endings in the value. */
1311 size_t len = strcspn(value, "#");
1313 if (len < valuelen) {
1315 value[valuelen] = 0;
1318 status = set_option(opt, value);
1321 if (status == ERR) {
1322 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1323 config_lineno, (int) optlen, opt, config_msg);
1324 config_errors = TRUE;
1327 /* Always keep going if errors are encountered. */
1332 load_option_file(const char *path)
1336 /* It's ok that the file doesn't exist. */
1337 file = fopen(path, "r");
1342 config_errors = FALSE;
1344 if (read_properties(file, " \t", read_option) == ERR ||
1345 config_errors == TRUE)
1346 fprintf(stderr, "Errors while loading %s.\n", path);
1352 char *home = getenv("HOME");
1353 char *tigrc_user = getenv("TIGRC_USER");
1354 char *tigrc_system = getenv("TIGRC_SYSTEM");
1355 char buf[SIZEOF_STR];
1357 add_builtin_run_requests();
1359 if (!tigrc_system) {
1360 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1364 load_option_file(tigrc_system);
1367 if (!home || !string_format(buf, "%s/.tigrc", home))
1371 load_option_file(tigrc_user);
1384 /* The display array of active views and the index of the current view. */
1385 static struct view *display[2];
1386 static unsigned int current_view;
1388 /* Reading from the prompt? */
1389 static bool input_mode = FALSE;
1391 #define foreach_displayed_view(view, i) \
1392 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1394 #define displayed_views() (display[1] != NULL ? 2 : 1)
1396 /* Current head and commit ID */
1397 static char ref_blob[SIZEOF_REF] = "";
1398 static char ref_commit[SIZEOF_REF] = "HEAD";
1399 static char ref_head[SIZEOF_REF] = "HEAD";
1402 const char *name; /* View name */
1403 const char *cmd_fmt; /* Default command line format */
1404 const char *cmd_env; /* Command line set via environment */
1405 const char *id; /* Points to either of ref_{head,commit,blob} */
1407 struct view_ops *ops; /* View operations */
1409 enum keymap keymap; /* What keymap does this view have */
1411 char cmd[SIZEOF_STR]; /* Command buffer */
1412 char ref[SIZEOF_REF]; /* Hovered commit reference */
1413 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1415 int height, width; /* The width and height of the main window */
1416 WINDOW *win; /* The main window */
1417 WINDOW *title; /* The title window living below the main window */
1420 unsigned long offset; /* Offset of the window top */
1421 unsigned long lineno; /* Current line number */
1424 char grep[SIZEOF_STR]; /* Search string */
1425 regex_t *regex; /* Pre-compiled regex */
1427 /* If non-NULL, points to the view that opened this view. If this view
1428 * is closed tig will switch back to the parent view. */
1429 struct view *parent;
1432 unsigned long lines; /* Total number of lines */
1433 struct line *line; /* Line index */
1434 unsigned long line_size;/* Total number of allocated lines */
1435 unsigned int digits; /* Number of digits in the lines member. */
1443 /* What type of content being displayed. Used in the title bar. */
1445 /* Open and reads in all view content. */
1446 bool (*open)(struct view *view);
1447 /* Read one line; updates view->line. */
1448 bool (*read)(struct view *view, char *data);
1449 /* Draw one line; @lineno must be < view->height. */
1450 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1451 /* Depending on view handle a special requests. */
1452 enum request (*request)(struct view *view, enum request request, struct line *line);
1453 /* Search for regex in a line. */
1454 bool (*grep)(struct view *view, struct line *line);
1456 void (*select)(struct view *view, struct line *line);
1459 static struct view_ops pager_ops;
1460 static struct view_ops main_ops;
1461 static struct view_ops tree_ops;
1462 static struct view_ops blob_ops;
1463 static struct view_ops help_ops;
1464 static struct view_ops status_ops;
1465 static struct view_ops stage_ops;
1467 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1468 { name, cmd, #env, ref, ops, map}
1470 #define VIEW_(id, name, ops, ref) \
1471 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1474 static struct view views[] = {
1475 VIEW_(MAIN, "main", &main_ops, ref_head),
1476 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1477 VIEW_(LOG, "log", &pager_ops, ref_head),
1478 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1479 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1480 VIEW_(HELP, "help", &help_ops, ""),
1481 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1482 VIEW_(STATUS, "status", &status_ops, ""),
1483 VIEW_(STAGE, "stage", &stage_ops, ""),
1486 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1488 #define foreach_view(view, i) \
1489 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1491 #define view_is_displayed(view) \
1492 (view == display[0] || view == display[1])
1495 draw_text(struct view *view, const char *string, int max_len, int col,
1496 bool use_tilde, int tilde_attr)
1499 int trimmed = FALSE;
1505 len = utf8_length(string, max_len, &trimmed, use_tilde);
1507 len = strlen(string);
1508 if (len > max_len) {
1517 waddnstr(view->win, string, len);
1518 if (trimmed && use_tilde) {
1519 if (tilde_attr != -1)
1520 wattrset(view->win, tilde_attr);
1521 waddch(view->win, '~');
1529 draw_view_line(struct view *view, unsigned int lineno)
1532 bool selected = (view->offset + lineno == view->lineno);
1535 assert(view_is_displayed(view));
1537 if (view->offset + lineno >= view->lines)
1540 line = &view->line[view->offset + lineno];
1543 line->selected = TRUE;
1544 view->ops->select(view, line);
1545 } else if (line->selected) {
1546 line->selected = FALSE;
1547 wmove(view->win, lineno, 0);
1548 wclrtoeol(view->win);
1551 scrollok(view->win, FALSE);
1552 draw_ok = view->ops->draw(view, line, lineno, selected);
1553 scrollok(view->win, TRUE);
1559 redraw_view_from(struct view *view, int lineno)
1561 assert(0 <= lineno && lineno < view->height);
1563 for (; lineno < view->height; lineno++) {
1564 if (!draw_view_line(view, lineno))
1568 redrawwin(view->win);
1570 wnoutrefresh(view->win);
1572 wrefresh(view->win);
1576 redraw_view(struct view *view)
1579 redraw_view_from(view, 0);
1584 update_view_title(struct view *view)
1586 char buf[SIZEOF_STR];
1587 char state[SIZEOF_STR];
1588 size_t bufpos = 0, statelen = 0;
1590 assert(view_is_displayed(view));
1592 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1593 unsigned int view_lines = view->offset + view->height;
1594 unsigned int lines = view->lines
1595 ? MIN(view_lines, view->lines) * 100 / view->lines
1598 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1605 time_t secs = time(NULL) - view->start_time;
1607 /* Three git seconds are a long time ... */
1609 string_format_from(state, &statelen, " %lds", secs);
1613 string_format_from(buf, &bufpos, "[%s]", view->name);
1614 if (*view->ref && bufpos < view->width) {
1615 size_t refsize = strlen(view->ref);
1616 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1618 if (minsize < view->width)
1619 refsize = view->width - minsize + 7;
1620 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1623 if (statelen && bufpos < view->width) {
1624 string_format_from(buf, &bufpos, " %s", state);
1627 if (view == display[current_view])
1628 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1630 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1632 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1633 wclrtoeol(view->title);
1634 wmove(view->title, 0, view->width - 1);
1637 wnoutrefresh(view->title);
1639 wrefresh(view->title);
1643 resize_display(void)
1646 struct view *base = display[0];
1647 struct view *view = display[1] ? display[1] : display[0];
1649 /* Setup window dimensions */
1651 getmaxyx(stdscr, base->height, base->width);
1653 /* Make room for the status window. */
1657 /* Horizontal split. */
1658 view->width = base->width;
1659 view->height = SCALE_SPLIT_VIEW(base->height);
1660 base->height -= view->height;
1662 /* Make room for the title bar. */
1666 /* Make room for the title bar. */
1671 foreach_displayed_view (view, i) {
1673 view->win = newwin(view->height, 0, offset, 0);
1675 die("Failed to create %s view", view->name);
1677 scrollok(view->win, TRUE);
1679 view->title = newwin(1, 0, offset + view->height, 0);
1681 die("Failed to create title window");
1684 wresize(view->win, view->height, view->width);
1685 mvwin(view->win, offset, 0);
1686 mvwin(view->title, offset + view->height, 0);
1689 offset += view->height + 1;
1694 redraw_display(void)
1699 foreach_displayed_view (view, i) {
1701 update_view_title(view);
1706 update_display_cursor(struct view *view)
1708 /* Move the cursor to the right-most column of the cursor line.
1710 * XXX: This could turn out to be a bit expensive, but it ensures that
1711 * the cursor does not jump around. */
1713 wmove(view->win, view->lineno - view->offset, view->width - 1);
1714 wrefresh(view->win);
1722 /* Scrolling backend */
1724 do_scroll_view(struct view *view, int lines)
1726 bool redraw_current_line = FALSE;
1728 /* The rendering expects the new offset. */
1729 view->offset += lines;
1731 assert(0 <= view->offset && view->offset < view->lines);
1734 /* Move current line into the view. */
1735 if (view->lineno < view->offset) {
1736 view->lineno = view->offset;
1737 redraw_current_line = TRUE;
1738 } else if (view->lineno >= view->offset + view->height) {
1739 view->lineno = view->offset + view->height - 1;
1740 redraw_current_line = TRUE;
1743 assert(view->offset <= view->lineno && view->lineno < view->lines);
1745 /* Redraw the whole screen if scrolling is pointless. */
1746 if (view->height < ABS(lines)) {
1750 int line = lines > 0 ? view->height - lines : 0;
1751 int end = line + ABS(lines);
1753 wscrl(view->win, lines);
1755 for (; line < end; line++) {
1756 if (!draw_view_line(view, line))
1760 if (redraw_current_line)
1761 draw_view_line(view, view->lineno - view->offset);
1764 redrawwin(view->win);
1765 wrefresh(view->win);
1769 /* Scroll frontend */
1771 scroll_view(struct view *view, enum request request)
1775 assert(view_is_displayed(view));
1778 case REQ_SCROLL_PAGE_DOWN:
1779 lines = view->height;
1780 case REQ_SCROLL_LINE_DOWN:
1781 if (view->offset + lines > view->lines)
1782 lines = view->lines - view->offset;
1784 if (lines == 0 || view->offset + view->height >= view->lines) {
1785 report("Cannot scroll beyond the last line");
1790 case REQ_SCROLL_PAGE_UP:
1791 lines = view->height;
1792 case REQ_SCROLL_LINE_UP:
1793 if (lines > view->offset)
1794 lines = view->offset;
1797 report("Cannot scroll beyond the first line");
1805 die("request %d not handled in switch", request);
1808 do_scroll_view(view, lines);
1813 move_view(struct view *view, enum request request)
1815 int scroll_steps = 0;
1819 case REQ_MOVE_FIRST_LINE:
1820 steps = -view->lineno;
1823 case REQ_MOVE_LAST_LINE:
1824 steps = view->lines - view->lineno - 1;
1827 case REQ_MOVE_PAGE_UP:
1828 steps = view->height > view->lineno
1829 ? -view->lineno : -view->height;
1832 case REQ_MOVE_PAGE_DOWN:
1833 steps = view->lineno + view->height >= view->lines
1834 ? view->lines - view->lineno - 1 : view->height;
1846 die("request %d not handled in switch", request);
1849 if (steps <= 0 && view->lineno == 0) {
1850 report("Cannot move beyond the first line");
1853 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1854 report("Cannot move beyond the last line");
1858 /* Move the current line */
1859 view->lineno += steps;
1860 assert(0 <= view->lineno && view->lineno < view->lines);
1862 /* Check whether the view needs to be scrolled */
1863 if (view->lineno < view->offset ||
1864 view->lineno >= view->offset + view->height) {
1865 scroll_steps = steps;
1866 if (steps < 0 && -steps > view->offset) {
1867 scroll_steps = -view->offset;
1869 } else if (steps > 0) {
1870 if (view->lineno == view->lines - 1 &&
1871 view->lines > view->height) {
1872 scroll_steps = view->lines - view->offset - 1;
1873 if (scroll_steps >= view->height)
1874 scroll_steps -= view->height - 1;
1879 if (!view_is_displayed(view)) {
1880 view->offset += scroll_steps;
1881 assert(0 <= view->offset && view->offset < view->lines);
1882 view->ops->select(view, &view->line[view->lineno]);
1886 /* Repaint the old "current" line if we be scrolling */
1887 if (ABS(steps) < view->height)
1888 draw_view_line(view, view->lineno - steps - view->offset);
1891 do_scroll_view(view, scroll_steps);
1895 /* Draw the current line */
1896 draw_view_line(view, view->lineno - view->offset);
1898 redrawwin(view->win);
1899 wrefresh(view->win);
1908 static void search_view(struct view *view, enum request request);
1911 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1913 assert(view_is_displayed(view));
1915 if (!view->ops->grep(view, line))
1918 if (lineno - view->offset >= view->height) {
1919 view->offset = lineno;
1920 view->lineno = lineno;
1924 unsigned long old_lineno = view->lineno - view->offset;
1926 view->lineno = lineno;
1927 draw_view_line(view, old_lineno);
1929 draw_view_line(view, view->lineno - view->offset);
1930 redrawwin(view->win);
1931 wrefresh(view->win);
1934 report("Line %ld matches '%s'", lineno + 1, view->grep);
1939 find_next(struct view *view, enum request request)
1941 unsigned long lineno = view->lineno;
1946 report("No previous search");
1948 search_view(view, request);
1958 case REQ_SEARCH_BACK:
1967 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1968 lineno += direction;
1970 /* Note, lineno is unsigned long so will wrap around in which case it
1971 * will become bigger than view->lines. */
1972 for (; lineno < view->lines; lineno += direction) {
1973 struct line *line = &view->line[lineno];
1975 if (find_next_line(view, lineno, line))
1979 report("No match found for '%s'", view->grep);
1983 search_view(struct view *view, enum request request)
1988 regfree(view->regex);
1991 view->regex = calloc(1, sizeof(*view->regex));
1996 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1997 if (regex_err != 0) {
1998 char buf[SIZEOF_STR] = "unknown error";
2000 regerror(regex_err, view->regex, buf, sizeof(buf));
2001 report("Search failed: %s", buf);
2005 string_copy(view->grep, opt_search);
2007 find_next(view, request);
2011 * Incremental updating
2015 end_update(struct view *view)
2019 set_nonblocking_input(FALSE);
2020 if (view->pipe == stdin)
2028 begin_update(struct view *view)
2034 string_copy(view->cmd, opt_cmd);
2036 /* When running random commands, initially show the
2037 * command in the title. However, it maybe later be
2038 * overwritten if a commit line is selected. */
2039 if (view == VIEW(REQ_VIEW_PAGER))
2040 string_copy(view->ref, view->cmd);
2044 } else if (view == VIEW(REQ_VIEW_TREE)) {
2045 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2046 char path[SIZEOF_STR];
2048 if (strcmp(view->vid, view->id))
2049 opt_path[0] = path[0] = 0;
2050 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2053 if (!string_format(view->cmd, format, view->id, path))
2057 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2058 const char *id = view->id;
2060 if (!string_format(view->cmd, format, id, id, id, id, id))
2063 /* Put the current ref_* value to the view title ref
2064 * member. This is needed by the blob view. Most other
2065 * views sets it automatically after loading because the
2066 * first line is a commit line. */
2067 string_copy_rev(view->ref, view->id);
2070 /* Special case for the pager view. */
2072 view->pipe = opt_pipe;
2075 view->pipe = popen(view->cmd, "r");
2081 set_nonblocking_input(TRUE);
2086 string_copy_rev(view->vid, view->id);
2091 for (i = 0; i < view->lines; i++)
2092 if (view->line[i].data)
2093 free(view->line[i].data);
2099 view->start_time = time(NULL);
2104 static struct line *
2105 realloc_lines(struct view *view, size_t line_size)
2107 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2113 view->line_size = line_size;
2118 update_view(struct view *view)
2120 char in_buffer[BUFSIZ];
2121 char out_buffer[BUFSIZ * 2];
2123 /* The number of lines to read. If too low it will cause too much
2124 * redrawing (and possible flickering), if too high responsiveness
2126 unsigned long lines = view->height;
2127 int redraw_from = -1;
2132 /* Only redraw if lines are visible. */
2133 if (view->offset + view->height >= view->lines)
2134 redraw_from = view->lines - view->offset;
2136 /* FIXME: This is probably not perfect for backgrounded views. */
2137 if (!realloc_lines(view, view->lines + lines))
2140 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2141 size_t linelen = strlen(line);
2144 line[linelen - 1] = 0;
2146 if (opt_iconv != ICONV_NONE) {
2147 ICONV_CONST char *inbuf = line;
2148 size_t inlen = linelen;
2150 char *outbuf = out_buffer;
2151 size_t outlen = sizeof(out_buffer);
2155 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2156 if (ret != (size_t) -1) {
2158 linelen = strlen(out_buffer);
2162 if (!view->ops->read(view, line))
2172 lines = view->lines;
2173 for (digits = 0; lines; digits++)
2176 /* Keep the displayed view in sync with line number scaling. */
2177 if (digits != view->digits) {
2178 view->digits = digits;
2183 if (!view_is_displayed(view))
2186 if (view == VIEW(REQ_VIEW_TREE)) {
2187 /* Clear the view and redraw everything since the tree sorting
2188 * might have rearranged things. */
2191 } else if (redraw_from >= 0) {
2192 /* If this is an incremental update, redraw the previous line
2193 * since for commits some members could have changed when
2194 * loading the main view. */
2195 if (redraw_from > 0)
2198 /* Since revision graph visualization requires knowledge
2199 * about the parent commit, it causes a further one-off
2200 * needed to be redrawn for incremental updates. */
2201 if (redraw_from > 0 && opt_rev_graph)
2204 /* Incrementally draw avoids flickering. */
2205 redraw_view_from(view, redraw_from);
2208 /* Update the title _after_ the redraw so that if the redraw picks up a
2209 * commit reference in view->ref it'll be available here. */
2210 update_view_title(view);
2213 if (ferror(view->pipe)) {
2214 report("Failed to read: %s", strerror(errno));
2217 } else if (feof(view->pipe)) {
2225 report("Allocation failure");
2228 view->ops->read(view, NULL);
2233 static struct line *
2234 add_line_data(struct view *view, void *data, enum line_type type)
2236 struct line *line = &view->line[view->lines++];
2238 memset(line, 0, sizeof(*line));
2245 static struct line *
2246 add_line_text(struct view *view, char *data, enum line_type type)
2249 data = strdup(data);
2251 return data ? add_line_data(view, data, type) : NULL;
2260 OPEN_DEFAULT = 0, /* Use default view switching. */
2261 OPEN_SPLIT = 1, /* Split current view. */
2262 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2263 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2267 open_view(struct view *prev, enum request request, enum open_flags flags)
2269 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2270 bool split = !!(flags & OPEN_SPLIT);
2271 bool reload = !!(flags & OPEN_RELOAD);
2272 struct view *view = VIEW(request);
2273 int nviews = displayed_views();
2274 struct view *base_view = display[0];
2276 if (view == prev && nviews == 1 && !reload) {
2277 report("Already in %s view", view->name);
2281 if (view->ops->open) {
2282 if (!view->ops->open(view)) {
2283 report("Failed to load %s view", view->name);
2287 } else if ((reload || strcmp(view->vid, view->id)) &&
2288 !begin_update(view)) {
2289 report("Failed to load %s view", view->name);
2298 /* Maximize the current view. */
2299 memset(display, 0, sizeof(display));
2301 display[current_view] = view;
2304 /* Resize the view when switching between split- and full-screen,
2305 * or when switching between two different full-screen views. */
2306 if (nviews != displayed_views() ||
2307 (nviews == 1 && base_view != display[0]))
2310 if (split && prev->lineno - prev->offset >= prev->height) {
2311 /* Take the title line into account. */
2312 int lines = prev->lineno - prev->offset - prev->height + 1;
2314 /* Scroll the view that was split if the current line is
2315 * outside the new limited view. */
2316 do_scroll_view(prev, lines);
2319 if (prev && view != prev) {
2320 if (split && !backgrounded) {
2321 /* "Blur" the previous view. */
2322 update_view_title(prev);
2325 view->parent = prev;
2328 if (view->pipe && view->lines == 0) {
2329 /* Clear the old view and let the incremental updating refill
2338 /* If the view is backgrounded the above calls to report()
2339 * won't redraw the view title. */
2341 update_view_title(view);
2345 open_external_viewer(const char *cmd)
2347 def_prog_mode(); /* save current tty modes */
2348 endwin(); /* restore original tty modes */
2350 fprintf(stderr, "Press Enter to continue");
2357 open_mergetool(const char *file)
2359 char cmd[SIZEOF_STR];
2360 char file_sq[SIZEOF_STR];
2362 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2363 string_format(cmd, "git mergetool %s", file_sq)) {
2364 open_external_viewer(cmd);
2369 open_editor(bool from_root, const char *file)
2371 char cmd[SIZEOF_STR];
2372 char file_sq[SIZEOF_STR];
2374 char *prefix = from_root ? opt_cdup : "";
2376 editor = getenv("GIT_EDITOR");
2377 if (!editor && *opt_editor)
2378 editor = opt_editor;
2380 editor = getenv("VISUAL");
2382 editor = getenv("EDITOR");
2386 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2387 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2388 open_external_viewer(cmd);
2393 open_run_request(enum request request)
2395 struct run_request *req = get_run_request(request);
2396 char buf[SIZEOF_STR * 2];
2401 report("Unknown run request");
2409 char *next = strstr(cmd, "%(");
2410 int len = next - cmd;
2417 } else if (!strncmp(next, "%(head)", 7)) {
2420 } else if (!strncmp(next, "%(commit)", 9)) {
2423 } else if (!strncmp(next, "%(blob)", 7)) {
2427 report("Unknown replacement in run request: `%s`", req->cmd);
2431 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2435 next = strchr(next, ')') + 1;
2439 open_external_viewer(buf);
2443 * User request switch noodle
2447 view_driver(struct view *view, enum request request)
2451 if (request == REQ_NONE) {
2456 if (request > REQ_NONE) {
2457 open_run_request(request);
2461 if (view && view->lines) {
2462 request = view->ops->request(view, request, &view->line[view->lineno]);
2463 if (request == REQ_NONE)
2470 case REQ_MOVE_PAGE_UP:
2471 case REQ_MOVE_PAGE_DOWN:
2472 case REQ_MOVE_FIRST_LINE:
2473 case REQ_MOVE_LAST_LINE:
2474 move_view(view, request);
2477 case REQ_SCROLL_LINE_DOWN:
2478 case REQ_SCROLL_LINE_UP:
2479 case REQ_SCROLL_PAGE_DOWN:
2480 case REQ_SCROLL_PAGE_UP:
2481 scroll_view(view, request);
2486 report("No file chosen, press %s to open tree view",
2487 get_key(REQ_VIEW_TREE));
2490 open_view(view, request, OPEN_DEFAULT);
2493 case REQ_VIEW_PAGER:
2494 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2495 report("No pager content, press %s to run command from prompt",
2496 get_key(REQ_PROMPT));
2499 open_view(view, request, OPEN_DEFAULT);
2502 case REQ_VIEW_STAGE:
2503 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2504 report("No stage content, press %s to open the status view and choose file",
2505 get_key(REQ_VIEW_STATUS));
2508 open_view(view, request, OPEN_DEFAULT);
2511 case REQ_VIEW_STATUS:
2512 if (opt_is_inside_work_tree == FALSE) {
2513 report("The status view requires a working tree");
2516 open_view(view, request, OPEN_DEFAULT);
2524 open_view(view, request, OPEN_DEFAULT);
2529 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2531 if ((view == VIEW(REQ_VIEW_DIFF) &&
2532 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2533 (view == VIEW(REQ_VIEW_STAGE) &&
2534 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2535 (view == VIEW(REQ_VIEW_BLOB) &&
2536 view->parent == VIEW(REQ_VIEW_TREE))) {
2539 view = view->parent;
2540 line = view->lineno;
2541 move_view(view, request);
2542 if (view_is_displayed(view))
2543 update_view_title(view);
2544 if (line != view->lineno)
2545 view->ops->request(view, REQ_ENTER,
2546 &view->line[view->lineno]);
2549 move_view(view, request);
2555 int nviews = displayed_views();
2556 int next_view = (current_view + 1) % nviews;
2558 if (next_view == current_view) {
2559 report("Only one view is displayed");
2563 current_view = next_view;
2564 /* Blur out the title of the previous view. */
2565 update_view_title(view);
2570 report("Refreshing is not yet supported for the %s view", view->name);
2573 case REQ_TOGGLE_LINENO:
2574 opt_line_number = !opt_line_number;
2578 case REQ_TOGGLE_DATE:
2579 opt_date = !opt_date;
2583 case REQ_TOGGLE_AUTHOR:
2584 opt_author = !opt_author;
2588 case REQ_TOGGLE_REV_GRAPH:
2589 opt_rev_graph = !opt_rev_graph;
2593 case REQ_TOGGLE_REFS:
2594 opt_show_refs = !opt_show_refs;
2599 /* Always reload^Wrerun commands from the prompt. */
2600 open_view(view, opt_request, OPEN_RELOAD);
2604 case REQ_SEARCH_BACK:
2605 search_view(view, request);
2610 find_next(view, request);
2613 case REQ_STOP_LOADING:
2614 for (i = 0; i < ARRAY_SIZE(views); i++) {
2617 report("Stopped loading the %s view", view->name),
2622 case REQ_SHOW_VERSION:
2623 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2626 case REQ_SCREEN_RESIZE:
2629 case REQ_SCREEN_REDRAW:
2634 report("Nothing to edit");
2639 report("Nothing to enter");
2643 case REQ_VIEW_CLOSE:
2644 /* XXX: Mark closed views by letting view->parent point to the
2645 * view itself. Parents to closed view should never be
2648 view->parent->parent != view->parent) {
2649 memset(display, 0, sizeof(display));
2651 display[current_view] = view->parent;
2652 view->parent = view;
2662 /* An unknown key will show most commonly used commands. */
2663 report("Unknown key, press 'h' for help");
2676 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2678 char *text = line->data;
2679 enum line_type type = line->type;
2682 wmove(view->win, lineno, 0);
2686 wchgat(view->win, -1, 0, type, NULL);
2689 attr = get_line_attr(type);
2690 wattrset(view->win, attr);
2692 if (opt_line_number || opt_tab_size < TABSIZE) {
2693 static char spaces[] = " ";
2694 int col_offset = 0, col = 0;
2696 if (opt_line_number) {
2697 unsigned long real_lineno = view->offset + lineno + 1;
2699 if (real_lineno == 1 ||
2700 (real_lineno % opt_num_interval) == 0) {
2701 wprintw(view->win, "%.*d", view->digits, real_lineno);
2704 waddnstr(view->win, spaces,
2705 MIN(view->digits, STRING_SIZE(spaces)));
2707 waddstr(view->win, ": ");
2708 col_offset = view->digits + 2;
2711 while (text && col_offset + col < view->width) {
2712 int cols_max = view->width - col_offset - col;
2716 if (*text == '\t') {
2718 assert(sizeof(spaces) > TABSIZE);
2720 cols = opt_tab_size - (col % opt_tab_size);
2723 text = strchr(text, '\t');
2724 cols = line ? text - pos : strlen(pos);
2727 waddnstr(view->win, pos, MIN(cols, cols_max));
2732 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2734 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
2741 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2743 char refbuf[SIZEOF_STR];
2747 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2750 pipe = popen(refbuf, "r");
2754 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2755 ref = chomp_string(ref);
2761 /* This is the only fatal call, since it can "corrupt" the buffer. */
2762 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2769 add_pager_refs(struct view *view, struct line *line)
2771 char buf[SIZEOF_STR];
2772 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2774 size_t bufpos = 0, refpos = 0;
2775 const char *sep = "Refs: ";
2776 bool is_tag = FALSE;
2778 assert(line->type == LINE_COMMIT);
2780 refs = get_refs(commit_id);
2782 if (view == VIEW(REQ_VIEW_DIFF))
2783 goto try_add_describe_ref;
2788 struct ref *ref = refs[refpos];
2789 char *fmt = ref->tag ? "%s[%s]" :
2790 ref->remote ? "%s<%s>" : "%s%s";
2792 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2797 } while (refs[refpos++]->next);
2799 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2800 try_add_describe_ref:
2801 /* Add <tag>-g<commit_id> "fake" reference. */
2802 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2809 if (!realloc_lines(view, view->line_size + 1))
2812 add_line_text(view, buf, LINE_PP_REFS);
2816 pager_read(struct view *view, char *data)
2823 line = add_line_text(view, data, get_line_type(data));
2827 if (line->type == LINE_COMMIT &&
2828 (view == VIEW(REQ_VIEW_DIFF) ||
2829 view == VIEW(REQ_VIEW_LOG)))
2830 add_pager_refs(view, line);
2836 pager_request(struct view *view, enum request request, struct line *line)
2840 if (request != REQ_ENTER)
2843 if (line->type == LINE_COMMIT &&
2844 (view == VIEW(REQ_VIEW_LOG) ||
2845 view == VIEW(REQ_VIEW_PAGER))) {
2846 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2850 /* Always scroll the view even if it was split. That way
2851 * you can use Enter to scroll through the log view and
2852 * split open each commit diff. */
2853 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2855 /* FIXME: A minor workaround. Scrolling the view will call report("")
2856 * but if we are scrolling a non-current view this won't properly
2857 * update the view title. */
2859 update_view_title(view);
2865 pager_grep(struct view *view, struct line *line)
2868 char *text = line->data;
2873 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2880 pager_select(struct view *view, struct line *line)
2882 if (line->type == LINE_COMMIT) {
2883 char *text = (char *)line->data + STRING_SIZE("commit ");
2885 if (view != VIEW(REQ_VIEW_PAGER))
2886 string_copy_rev(view->ref, text);
2887 string_copy_rev(ref_commit, text);
2891 static struct view_ops pager_ops = {
2907 help_open(struct view *view)
2910 int lines = ARRAY_SIZE(req_info) + 2;
2913 if (view->lines > 0)
2916 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2917 if (!req_info[i].request)
2920 lines += run_requests + 1;
2922 view->line = calloc(lines, sizeof(*view->line));
2926 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2928 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2931 if (req_info[i].request == REQ_NONE)
2934 if (!req_info[i].request) {
2935 add_line_text(view, "", LINE_DEFAULT);
2936 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2940 key = get_key(req_info[i].request);
2942 key = "(no key defined)";
2944 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2947 add_line_text(view, buf, LINE_DEFAULT);
2951 add_line_text(view, "", LINE_DEFAULT);
2952 add_line_text(view, "External commands:", LINE_DEFAULT);
2955 for (i = 0; i < run_requests; i++) {
2956 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2962 key = get_key_name(req->key);
2964 key = "(no key defined)";
2966 if (!string_format(buf, " %-10s %-14s `%s`",
2967 keymap_table[req->keymap].name,
2971 add_line_text(view, buf, LINE_DEFAULT);
2977 static struct view_ops help_ops = {
2992 struct tree_stack_entry {
2993 struct tree_stack_entry *prev; /* Entry below this in the stack */
2994 unsigned long lineno; /* Line number to restore */
2995 char *name; /* Position of name in opt_path */
2998 /* The top of the path stack. */
2999 static struct tree_stack_entry *tree_stack = NULL;
3000 unsigned long tree_lineno = 0;
3003 pop_tree_stack_entry(void)
3005 struct tree_stack_entry *entry = tree_stack;
3007 tree_lineno = entry->lineno;
3009 tree_stack = entry->prev;
3014 push_tree_stack_entry(char *name, unsigned long lineno)
3016 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3017 size_t pathlen = strlen(opt_path);
3022 entry->prev = tree_stack;
3023 entry->name = opt_path + pathlen;
3026 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3027 pop_tree_stack_entry();
3031 /* Move the current line to the first tree entry. */
3033 entry->lineno = lineno;
3036 /* Parse output from git-ls-tree(1):
3038 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3039 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3040 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3041 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3044 #define SIZEOF_TREE_ATTR \
3045 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3047 #define TREE_UP_FORMAT "040000 tree %s\t.."
3050 tree_compare_entry(enum line_type type1, char *name1,
3051 enum line_type type2, char *name2)
3053 if (type1 != type2) {
3054 if (type1 == LINE_TREE_DIR)
3059 return strcmp(name1, name2);
3063 tree_read(struct view *view, char *text)
3065 size_t textlen = text ? strlen(text) : 0;
3066 char buf[SIZEOF_STR];
3068 enum line_type type;
3069 bool first_read = view->lines == 0;
3071 if (textlen <= SIZEOF_TREE_ATTR)
3074 type = text[STRING_SIZE("100644 ")] == 't'
3075 ? LINE_TREE_DIR : LINE_TREE_FILE;
3078 /* Add path info line */
3079 if (!string_format(buf, "Directory path /%s", opt_path) ||
3080 !realloc_lines(view, view->line_size + 1) ||
3081 !add_line_text(view, buf, LINE_DEFAULT))
3084 /* Insert "link" to parent directory. */
3086 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3087 !realloc_lines(view, view->line_size + 1) ||
3088 !add_line_text(view, buf, LINE_TREE_DIR))
3093 /* Strip the path part ... */
3095 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3096 size_t striplen = strlen(opt_path);
3097 char *path = text + SIZEOF_TREE_ATTR;
3099 if (pathlen > striplen)
3100 memmove(path, path + striplen,
3101 pathlen - striplen + 1);
3104 /* Skip "Directory ..." and ".." line. */
3105 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3106 struct line *line = &view->line[pos];
3107 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3108 char *path2 = text + SIZEOF_TREE_ATTR;
3109 int cmp = tree_compare_entry(line->type, path1, type, path2);
3114 text = strdup(text);
3118 if (view->lines > pos)
3119 memmove(&view->line[pos + 1], &view->line[pos],
3120 (view->lines - pos) * sizeof(*line));
3122 line = &view->line[pos];
3129 if (!add_line_text(view, text, type))
3132 if (tree_lineno > view->lineno) {
3133 view->lineno = tree_lineno;
3141 tree_request(struct view *view, enum request request, struct line *line)
3143 enum open_flags flags;
3145 if (request == REQ_TREE_PARENT) {
3148 request = REQ_ENTER;
3149 line = &view->line[1];
3151 /* quit view if at top of tree */
3152 return REQ_VIEW_CLOSE;
3155 if (request != REQ_ENTER)
3158 /* Cleanup the stack if the tree view is at a different tree. */
3159 while (!*opt_path && tree_stack)
3160 pop_tree_stack_entry();
3162 switch (line->type) {
3164 /* Depending on whether it is a subdir or parent (updir?) link
3165 * mangle the path buffer. */
3166 if (line == &view->line[1] && *opt_path) {
3167 pop_tree_stack_entry();
3170 char *data = line->data;
3171 char *basename = data + SIZEOF_TREE_ATTR;
3173 push_tree_stack_entry(basename, view->lineno);
3176 /* Trees and subtrees share the same ID, so they are not not
3177 * unique like blobs. */
3178 flags = OPEN_RELOAD;
3179 request = REQ_VIEW_TREE;
3182 case LINE_TREE_FILE:
3183 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3184 request = REQ_VIEW_BLOB;
3191 open_view(view, request, flags);
3192 if (request == REQ_VIEW_TREE) {
3193 view->lineno = tree_lineno;
3200 tree_select(struct view *view, struct line *line)
3202 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3204 if (line->type == LINE_TREE_FILE) {
3205 string_copy_rev(ref_blob, text);
3207 } else if (line->type != LINE_TREE_DIR) {
3211 string_copy_rev(view->ref, text);
3214 static struct view_ops tree_ops = {
3225 blob_read(struct view *view, char *line)
3227 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3230 static struct view_ops blob_ops = {
3249 char rev[SIZEOF_REV];
3250 char name[SIZEOF_STR];
3254 char rev[SIZEOF_REV];
3255 char name[SIZEOF_STR];
3259 static struct status stage_status;
3260 static enum line_type stage_line_type;
3262 /* Get fields from the diff line:
3263 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3266 status_get_diff(struct status *file, char *buf, size_t bufsize)
3268 char *old_mode = buf + 1;
3269 char *new_mode = buf + 8;
3270 char *old_rev = buf + 15;
3271 char *new_rev = buf + 56;
3272 char *status = buf + 97;
3275 old_mode[-1] != ':' ||
3276 new_mode[-1] != ' ' ||
3277 old_rev[-1] != ' ' ||
3278 new_rev[-1] != ' ' ||
3282 file->status = *status;
3284 string_copy_rev(file->old.rev, old_rev);
3285 string_copy_rev(file->new.rev, new_rev);
3287 file->old.mode = strtoul(old_mode, NULL, 8);
3288 file->new.mode = strtoul(new_mode, NULL, 8);
3290 file->old.name[0] = file->new.name[0] = 0;
3296 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3298 struct status *file = NULL;
3299 struct status *unmerged = NULL;
3300 char buf[SIZEOF_STR * 4];
3304 pipe = popen(cmd, "r");
3308 add_line_data(view, NULL, type);
3310 while (!feof(pipe) && !ferror(pipe)) {
3314 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3317 bufsize += readsize;
3319 /* Process while we have NUL chars. */
3320 while ((sep = memchr(buf, 0, bufsize))) {
3321 size_t sepsize = sep - buf + 1;
3324 if (!realloc_lines(view, view->line_size + 1))
3327 file = calloc(1, sizeof(*file));
3331 add_line_data(view, file, type);
3334 /* Parse diff info part. */
3338 } else if (!file->status) {
3339 if (!status_get_diff(file, buf, sepsize))
3343 memmove(buf, sep + 1, bufsize);
3345 sep = memchr(buf, 0, bufsize);
3348 sepsize = sep - buf + 1;
3350 /* Collapse all 'M'odified entries that
3351 * follow a associated 'U'nmerged entry.
3353 if (file->status == 'U') {
3356 } else if (unmerged) {
3357 int collapse = !strcmp(buf, unmerged->new.name);
3368 /* Grab the old name for rename/copy. */
3369 if (!*file->old.name &&
3370 (file->status == 'R' || file->status == 'C')) {
3371 sepsize = sep - buf + 1;
3372 string_ncopy(file->old.name, buf, sepsize);
3374 memmove(buf, sep + 1, bufsize);
3376 sep = memchr(buf, 0, bufsize);
3379 sepsize = sep - buf + 1;
3382 /* git-ls-files just delivers a NUL separated
3383 * list of file names similar to the second half
3384 * of the git-diff-* output. */
3385 string_ncopy(file->new.name, buf, sepsize);
3386 if (!*file->old.name)
3387 string_copy(file->old.name, file->new.name);
3389 memmove(buf, sep + 1, bufsize);
3400 if (!view->line[view->lines - 1].data)
3401 add_line_data(view, NULL, LINE_STAT_NONE);
3407 /* Don't show unmerged entries in the staged section. */
3408 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3409 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3410 #define STATUS_LIST_OTHER_CMD \
3411 "git ls-files -z --others --exclude-per-directory=.gitignore"
3413 #define STATUS_DIFF_INDEX_SHOW_CMD \
3414 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3416 #define STATUS_DIFF_FILES_SHOW_CMD \
3417 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3419 /* First parse staged info using git-diff-index(1), then parse unstaged
3420 * info using git-diff-files(1), and finally untracked files using
3421 * git-ls-files(1). */
3423 status_open(struct view *view)
3425 struct stat statbuf;
3426 char exclude[SIZEOF_STR];
3427 char cmd[SIZEOF_STR];
3428 unsigned long prev_lineno = view->lineno;
3431 for (i = 0; i < view->lines; i++)
3432 free(view->line[i].data);
3434 view->lines = view->line_size = view->lineno = 0;
3437 if (!realloc_lines(view, view->line_size + 6))
3440 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3443 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3445 if (stat(exclude, &statbuf) >= 0) {
3446 size_t cmdsize = strlen(cmd);
3448 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3449 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3453 system("git update-index -q --refresh");
3455 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3456 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3457 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3460 /* If all went well restore the previous line number to stay in
3462 if (prev_lineno < view->lines)
3463 view->lineno = prev_lineno;
3465 view->lineno = view->lines - 1;
3471 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3473 struct status *status = line->data;
3474 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3476 wmove(view->win, lineno, 0);
3479 wattrset(view->win, get_line_attr(LINE_CURSOR));
3480 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3483 } else if (!status && line->type != LINE_STAT_NONE) {
3484 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3485 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3488 wattrset(view->win, get_line_attr(line->type));
3494 switch (line->type) {
3495 case LINE_STAT_STAGED:
3496 text = "Changes to be committed:";
3499 case LINE_STAT_UNSTAGED:
3500 text = "Changed but not updated:";
3503 case LINE_STAT_UNTRACKED:
3504 text = "Untracked files:";
3507 case LINE_STAT_NONE:
3508 text = " (no files)";
3515 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
3519 waddch(view->win, status->status);
3521 wattrset(view->win, A_NORMAL);
3522 wmove(view->win, lineno, 4);
3523 if (view->width < 5)
3526 draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
3531 status_enter(struct view *view, struct line *line)
3533 struct status *status = line->data;
3534 char oldpath[SIZEOF_STR] = "";
3535 char newpath[SIZEOF_STR] = "";
3539 if (line->type == LINE_STAT_NONE ||
3540 (!status && line[1].type == LINE_STAT_NONE)) {
3541 report("No file to diff");
3546 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3548 /* Diffs for unmerged entries are empty when pasing the
3549 * new path, so leave it empty. */
3550 if (status->status != 'U' &&
3551 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3556 line->type != LINE_STAT_UNTRACKED &&
3557 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3560 switch (line->type) {
3561 case LINE_STAT_STAGED:
3562 if (!string_format_from(opt_cmd, &cmdsize,
3563 STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3566 info = "Staged changes to %s";
3568 info = "Staged changes";
3571 case LINE_STAT_UNSTAGED:
3572 if (!string_format_from(opt_cmd, &cmdsize,
3573 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3576 info = "Unstaged changes to %s";
3578 info = "Unstaged changes";
3581 case LINE_STAT_UNTRACKED:
3587 report("No file to show");
3591 opt_pipe = fopen(status->new.name, "r");
3592 info = "Untracked file %s";
3596 die("line type %d not handled in switch", line->type);
3599 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3600 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3602 stage_status = *status;
3604 memset(&stage_status, 0, sizeof(stage_status));
3607 stage_line_type = line->type;
3608 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3616 status_update_file(struct view *view, struct status *status, enum line_type type)
3618 char cmd[SIZEOF_STR];
3619 char buf[SIZEOF_STR];
3626 type != LINE_STAT_UNTRACKED &&
3627 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3631 case LINE_STAT_STAGED:
3632 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3635 status->old.name, 0))
3638 string_add(cmd, cmdsize, "git update-index -z --index-info");
3641 case LINE_STAT_UNSTAGED:
3642 case LINE_STAT_UNTRACKED:
3643 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3646 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3650 die("line type %d not handled in switch", type);
3653 pipe = popen(cmd, "w");
3657 while (!ferror(pipe) && written < bufsize) {
3658 written += fwrite(buf + written, 1, bufsize - written, pipe);
3663 if (written != bufsize)
3670 status_update(struct view *view)
3672 struct line *line = &view->line[view->lineno];
3674 assert(view->lines);
3677 while (++line < view->line + view->lines && line->data) {
3678 if (!status_update_file(view, line->data, line->type))
3679 report("Failed to update file status");
3682 if (!line[-1].data) {
3683 report("Nothing to update");
3687 } else if (!status_update_file(view, line->data, line->type)) {
3688 report("Failed to update file status");
3693 status_request(struct view *view, enum request request, struct line *line)
3695 struct status *status = line->data;
3698 case REQ_STATUS_UPDATE:
3699 status_update(view);
3702 case REQ_STATUS_MERGE:
3703 if (!status || status->status != 'U') {
3704 report("Merging only possible for files with unmerged status ('U').");
3707 open_mergetool(status->new.name);
3714 open_editor(status->status != '?', status->new.name);
3718 /* After returning the status view has been split to
3719 * show the stage view. No further reloading is
3721 status_enter(view, line);
3725 /* Simply reload the view. */
3732 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3738 status_select(struct view *view, struct line *line)
3740 struct status *status = line->data;
3741 char file[SIZEOF_STR] = "all files";
3745 if (status && !string_format(file, "'%s'", status->new.name))
3748 if (!status && line[1].type == LINE_STAT_NONE)
3751 switch (line->type) {
3752 case LINE_STAT_STAGED:
3753 text = "Press %s to unstage %s for commit";
3756 case LINE_STAT_UNSTAGED:
3757 text = "Press %s to stage %s for commit";
3760 case LINE_STAT_UNTRACKED:
3761 text = "Press %s to stage %s for addition";
3764 case LINE_STAT_NONE:
3765 text = "Nothing to update";
3769 die("line type %d not handled in switch", line->type);
3772 if (status && status->status == 'U') {
3773 text = "Press %s to resolve conflict in %s";
3774 key = get_key(REQ_STATUS_MERGE);
3777 key = get_key(REQ_STATUS_UPDATE);
3780 string_format(view->ref, text, key, file);
3784 status_grep(struct view *view, struct line *line)
3786 struct status *status = line->data;
3787 enum { S_STATUS, S_NAME, S_END } state;
3794 for (state = S_STATUS; state < S_END; state++) {
3798 case S_NAME: text = status->new.name; break;
3800 buf[0] = status->status;
3808 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3815 static struct view_ops status_ops = {
3827 stage_diff_line(FILE *pipe, struct line *line)
3829 char *buf = line->data;
3830 size_t bufsize = strlen(buf);
3833 while (!ferror(pipe) && written < bufsize) {
3834 written += fwrite(buf + written, 1, bufsize - written, pipe);
3839 return written == bufsize;
3842 static struct line *
3843 stage_diff_hdr(struct view *view, struct line *line)
3845 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3846 struct line *diff_hdr;
3848 if (line->type == LINE_DIFF_CHUNK)
3849 diff_hdr = line - 1;
3851 diff_hdr = view->line + 1;
3853 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3854 if (diff_hdr->type == LINE_DIFF_HEADER)
3857 diff_hdr += diff_hdr_dir;
3864 stage_update_chunk(struct view *view, struct line *line)
3866 char cmd[SIZEOF_STR];
3868 struct line *diff_hdr, *diff_chunk, *diff_end;
3871 diff_hdr = stage_diff_hdr(view, line);
3876 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3879 if (!string_format_from(cmd, &cmdsize,
3880 "git apply --cached %s - && "
3881 "git update-index -q --unmerged --refresh 2>/dev/null",
3882 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3885 pipe = popen(cmd, "w");
3889 diff_end = view->line + view->lines;
3890 if (line->type != LINE_DIFF_CHUNK) {
3891 diff_chunk = diff_hdr;
3894 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3895 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3896 diff_chunk->type == LINE_DIFF_HEADER)
3897 diff_end = diff_chunk;
3901 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3902 switch (diff_hdr->type) {
3903 case LINE_DIFF_HEADER:
3904 case LINE_DIFF_INDEX:
3914 if (!stage_diff_line(pipe, diff_hdr++)) {
3921 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3926 if (diff_chunk != diff_end)
3933 stage_update(struct view *view, struct line *line)
3935 if (stage_line_type != LINE_STAT_UNTRACKED &&
3936 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3937 if (!stage_update_chunk(view, line)) {
3938 report("Failed to apply chunk");
3942 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3943 report("Failed to update file");
3947 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3949 view = VIEW(REQ_VIEW_STATUS);
3950 if (view_is_displayed(view))
3951 status_enter(view, &view->line[view->lineno]);
3955 stage_request(struct view *view, enum request request, struct line *line)
3958 case REQ_STATUS_UPDATE:
3959 stage_update(view, line);
3963 if (!stage_status.new.name[0])
3966 open_editor(stage_status.status != '?', stage_status.new.name);
3970 pager_request(view, request, line);
3980 static struct view_ops stage_ops = {
3996 char id[SIZEOF_REV]; /* SHA1 ID. */
3997 char title[128]; /* First line of the commit message. */
3998 char author[75]; /* Author of the commit. */
3999 struct tm time; /* Date from the author ident. */
4000 struct ref **refs; /* Repository references. */
4001 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4002 size_t graph_size; /* The width of the graph array. */
4005 /* Size of rev graph with no "padding" columns */
4006 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4009 struct rev_graph *prev, *next, *parents;
4010 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4012 struct commit *commit;
4014 unsigned int boundary:1;
4017 /* Parents of the commit being visualized. */
4018 static struct rev_graph graph_parents[4];
4020 /* The current stack of revisions on the graph. */
4021 static struct rev_graph graph_stacks[4] = {
4022 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4023 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4024 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4025 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4029 graph_parent_is_merge(struct rev_graph *graph)
4031 return graph->parents->size > 1;
4035 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4037 struct commit *commit = graph->commit;
4039 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4040 commit->graph[commit->graph_size++] = symbol;
4044 done_rev_graph(struct rev_graph *graph)
4046 if (graph_parent_is_merge(graph) &&
4047 graph->pos < graph->size - 1 &&
4048 graph->next->size == graph->size + graph->parents->size - 1) {
4049 size_t i = graph->pos + graph->parents->size - 1;
4051 graph->commit->graph_size = i * 2;
4052 while (i < graph->next->size - 1) {
4053 append_to_rev_graph(graph, ' ');
4054 append_to_rev_graph(graph, '\\');
4059 graph->size = graph->pos = 0;
4060 graph->commit = NULL;
4061 memset(graph->parents, 0, sizeof(*graph->parents));
4065 push_rev_graph(struct rev_graph *graph, char *parent)
4069 /* "Collapse" duplicate parents lines.
4071 * FIXME: This needs to also update update the drawn graph but
4072 * for now it just serves as a method for pruning graph lines. */
4073 for (i = 0; i < graph->size; i++)
4074 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4077 if (graph->size < SIZEOF_REVITEMS) {
4078 string_copy_rev(graph->rev[graph->size++], parent);
4083 get_rev_graph_symbol(struct rev_graph *graph)
4087 if (graph->boundary)
4088 symbol = REVGRAPH_BOUND;
4089 else if (graph->parents->size == 0)
4090 symbol = REVGRAPH_INIT;
4091 else if (graph_parent_is_merge(graph))
4092 symbol = REVGRAPH_MERGE;
4093 else if (graph->pos >= graph->size)
4094 symbol = REVGRAPH_BRANCH;
4096 symbol = REVGRAPH_COMMIT;
4102 draw_rev_graph(struct rev_graph *graph)
4105 chtype separator, line;
4107 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4108 static struct rev_filler fillers[] = {
4109 { ' ', REVGRAPH_LINE },
4114 chtype symbol = get_rev_graph_symbol(graph);
4115 struct rev_filler *filler;
4118 filler = &fillers[DEFAULT];
4120 for (i = 0; i < graph->pos; i++) {
4121 append_to_rev_graph(graph, filler->line);
4122 if (graph_parent_is_merge(graph->prev) &&
4123 graph->prev->pos == i)
4124 filler = &fillers[RSHARP];
4126 append_to_rev_graph(graph, filler->separator);
4129 /* Place the symbol for this revision. */
4130 append_to_rev_graph(graph, symbol);
4132 if (graph->prev->size > graph->size)
4133 filler = &fillers[RDIAG];
4135 filler = &fillers[DEFAULT];
4139 for (; i < graph->size; i++) {
4140 append_to_rev_graph(graph, filler->separator);
4141 append_to_rev_graph(graph, filler->line);
4142 if (graph_parent_is_merge(graph->prev) &&
4143 i < graph->prev->pos + graph->parents->size)
4144 filler = &fillers[RSHARP];
4145 if (graph->prev->size > graph->size)
4146 filler = &fillers[LDIAG];
4149 if (graph->prev->size > graph->size) {
4150 append_to_rev_graph(graph, filler->separator);
4151 if (filler->line != ' ')
4152 append_to_rev_graph(graph, filler->line);
4156 /* Prepare the next rev graph */
4158 prepare_rev_graph(struct rev_graph *graph)
4162 /* First, traverse all lines of revisions up to the active one. */
4163 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4164 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4167 push_rev_graph(graph->next, graph->rev[graph->pos]);
4170 /* Interleave the new revision parent(s). */
4171 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4172 push_rev_graph(graph->next, graph->parents->rev[i]);
4174 /* Lastly, put any remaining revisions. */
4175 for (i = graph->pos + 1; i < graph->size; i++)
4176 push_rev_graph(graph->next, graph->rev[i]);
4180 update_rev_graph(struct rev_graph *graph)
4182 /* If this is the finalizing update ... */
4184 prepare_rev_graph(graph);
4186 /* Graph visualization needs a one rev look-ahead,
4187 * so the first update doesn't visualize anything. */
4188 if (!graph->prev->commit)
4191 draw_rev_graph(graph->prev);
4192 done_rev_graph(graph->prev->prev);
4201 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4203 char buf[DATE_COLS + 1];
4204 struct commit *commit = line->data;
4205 enum line_type type;
4211 if (!*commit->author)
4214 space = view->width;
4215 wmove(view->win, lineno, col);
4219 wattrset(view->win, get_line_attr(type));
4220 wchgat(view->win, -1, 0, type, NULL);
4223 type = LINE_MAIN_COMMIT;
4224 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4225 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4231 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4233 view, buf, view->width - col, col, FALSE, tilde_attr);
4235 view, " ", view->width - col - n, col + n, FALSE,
4239 wmove(view->win, lineno, col);
4240 if (col >= view->width)
4243 if (type != LINE_CURSOR)
4244 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4249 max_len = view->width - col;
4250 if (max_len > AUTHOR_COLS - 1)
4251 max_len = AUTHOR_COLS - 1;
4253 view, commit->author, max_len, col, TRUE, tilde_attr);
4255 if (col >= view->width)
4259 if (opt_rev_graph && commit->graph_size) {
4260 size_t graph_size = view->width - col;
4263 if (type != LINE_CURSOR)
4264 wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4265 wmove(view->win, lineno, col);
4266 if (graph_size > commit->graph_size)
4267 graph_size = commit->graph_size;
4268 /* Using waddch() instead of waddnstr() ensures that
4269 * they'll be rendered correctly for the cursor line. */
4270 for (i = 0; i < graph_size; i++)
4271 waddch(view->win, commit->graph[i]);
4273 col += commit->graph_size + 1;
4274 if (col >= view->width)
4276 waddch(view->win, ' ');
4278 if (type != LINE_CURSOR)
4279 wattrset(view->win, A_NORMAL);
4281 wmove(view->win, lineno, col);
4283 if (opt_show_refs && commit->refs) {
4287 if (type == LINE_CURSOR)
4289 else if (commit->refs[i]->ltag)
4290 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4291 else if (commit->refs[i]->tag)
4292 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4293 else if (commit->refs[i]->remote)
4294 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4296 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4299 view, "[", view->width - col, col, TRUE,
4302 view, commit->refs[i]->name, view->width - col,
4303 col, TRUE, tilde_attr);
4305 view, "]", view->width - col, col, TRUE,
4307 if (type != LINE_CURSOR)
4308 wattrset(view->win, A_NORMAL);
4310 view, " ", view->width - col, col, TRUE,
4312 if (col >= view->width)
4314 } while (commit->refs[i++]->next);
4317 if (type != LINE_CURSOR)
4318 wattrset(view->win, get_line_attr(type));
4321 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4326 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4328 main_read(struct view *view, char *line)
4330 static struct rev_graph *graph = graph_stacks;
4331 enum line_type type;
4332 struct commit *commit;
4335 update_rev_graph(graph);
4339 type = get_line_type(line);
4340 if (type == LINE_COMMIT) {
4341 commit = calloc(1, sizeof(struct commit));
4345 line += STRING_SIZE("commit ");
4347 graph->boundary = 1;
4351 string_copy_rev(commit->id, line);
4352 commit->refs = get_refs(commit->id);
4353 graph->commit = commit;
4354 add_line_data(view, commit, LINE_MAIN_COMMIT);
4360 commit = view->line[view->lines - 1].data;
4364 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4369 /* Parse author lines where the name may be empty:
4370 * author <email@address.tld> 1138474660 +0100
4372 char *ident = line + STRING_SIZE("author ");
4373 char *nameend = strchr(ident, '<');
4374 char *emailend = strchr(ident, '>');
4376 if (!nameend || !emailend)
4379 update_rev_graph(graph);
4380 graph = graph->next;
4382 *nameend = *emailend = 0;
4383 ident = chomp_string(ident);
4385 ident = chomp_string(nameend + 1);
4390 string_ncopy(commit->author, ident, strlen(ident));
4392 /* Parse epoch and timezone */
4393 if (emailend[1] == ' ') {
4394 char *secs = emailend + 2;
4395 char *zone = strchr(secs, ' ');
4396 time_t time = (time_t) atol(secs);
4398 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4402 tz = ('0' - zone[1]) * 60 * 60 * 10;
4403 tz += ('0' - zone[2]) * 60 * 60;
4404 tz += ('0' - zone[3]) * 60;
4405 tz += ('0' - zone[4]) * 60;
4413 gmtime_r(&time, &commit->time);
4418 /* Fill in the commit title if it has not already been set. */
4419 if (commit->title[0])
4422 /* Require titles to start with a non-space character at the
4423 * offset used by git log. */
4424 if (strncmp(line, " ", 4))
4427 /* Well, if the title starts with a whitespace character,
4428 * try to be forgiving. Otherwise we end up with no title. */
4429 while (isspace(*line))
4433 /* FIXME: More graceful handling of titles; append "..." to
4434 * shortened titles, etc. */
4436 string_ncopy(commit->title, line, strlen(line));
4443 main_request(struct view *view, enum request request, struct line *line)
4445 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4447 if (request == REQ_ENTER)
4448 open_view(view, REQ_VIEW_DIFF, flags);
4456 main_grep(struct view *view, struct line *line)
4458 struct commit *commit = line->data;
4459 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4460 char buf[DATE_COLS + 1];
4463 for (state = S_TITLE; state < S_END; state++) {
4467 case S_TITLE: text = commit->title; break;
4468 case S_AUTHOR: text = commit->author; break;
4470 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4479 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4487 main_select(struct view *view, struct line *line)
4489 struct commit *commit = line->data;
4491 string_copy_rev(view->ref, commit->id);
4492 string_copy_rev(ref_commit, view->ref);
4495 static struct view_ops main_ops = {
4507 * Unicode / UTF-8 handling
4509 * NOTE: Much of the following code for dealing with unicode is derived from
4510 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4511 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4514 /* I've (over)annotated a lot of code snippets because I am not entirely
4515 * confident that the approach taken by this small UTF-8 interface is correct.
4519 unicode_width(unsigned long c)
4522 (c <= 0x115f /* Hangul Jamo */
4525 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4527 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4528 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4529 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4530 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4531 || (c >= 0xffe0 && c <= 0xffe6)
4532 || (c >= 0x20000 && c <= 0x2fffd)
4533 || (c >= 0x30000 && c <= 0x3fffd)))
4537 return opt_tab_size;
4542 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4543 * Illegal bytes are set one. */
4544 static const unsigned char utf8_bytes[256] = {
4545 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,
4546 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,
4547 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,
4548 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,
4549 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,
4550 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,
4551 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,
4552 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,
4555 /* Decode UTF-8 multi-byte representation into a unicode character. */
4556 static inline unsigned long
4557 utf8_to_unicode(const char *string, size_t length)
4559 unsigned long unicode;
4563 unicode = string[0];
4566 unicode = (string[0] & 0x1f) << 6;
4567 unicode += (string[1] & 0x3f);
4570 unicode = (string[0] & 0x0f) << 12;
4571 unicode += ((string[1] & 0x3f) << 6);
4572 unicode += (string[2] & 0x3f);
4575 unicode = (string[0] & 0x0f) << 18;
4576 unicode += ((string[1] & 0x3f) << 12);
4577 unicode += ((string[2] & 0x3f) << 6);
4578 unicode += (string[3] & 0x3f);
4581 unicode = (string[0] & 0x0f) << 24;
4582 unicode += ((string[1] & 0x3f) << 18);
4583 unicode += ((string[2] & 0x3f) << 12);
4584 unicode += ((string[3] & 0x3f) << 6);
4585 unicode += (string[4] & 0x3f);
4588 unicode = (string[0] & 0x01) << 30;
4589 unicode += ((string[1] & 0x3f) << 24);
4590 unicode += ((string[2] & 0x3f) << 18);
4591 unicode += ((string[3] & 0x3f) << 12);
4592 unicode += ((string[4] & 0x3f) << 6);
4593 unicode += (string[5] & 0x3f);
4596 die("Invalid unicode length");
4599 /* Invalid characters could return the special 0xfffd value but NUL
4600 * should be just as good. */
4601 return unicode > 0xffff ? 0 : unicode;
4604 /* Calculates how much of string can be shown within the given maximum width
4605 * and sets trimmed parameter to non-zero value if all of string could not be
4606 * shown. If the reserve flag is TRUE, it will reserve at least one
4607 * trailing character, which can be useful when drawing a delimiter.
4609 * Returns the number of bytes to output from string to satisfy max_width. */
4611 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
4613 const char *start = string;
4614 const char *end = strchr(string, '\0');
4615 unsigned char last_bytes = 0;
4620 while (string < end) {
4621 int c = *(unsigned char *) string;
4622 unsigned char bytes = utf8_bytes[c];
4624 unsigned long unicode;
4626 if (string + bytes > end)
4629 /* Change representation to figure out whether
4630 * it is a single- or double-width character. */
4632 unicode = utf8_to_unicode(string, bytes);
4633 /* FIXME: Graceful handling of invalid unicode character. */
4637 ucwidth = unicode_width(unicode);
4639 if (width > max_width) {
4641 if (reserve && width - ucwidth == max_width) {
4642 string -= last_bytes;
4651 return string - start;
4659 /* Whether or not the curses interface has been initialized. */
4660 static bool cursed = FALSE;
4662 /* The status window is used for polling keystrokes. */
4663 static WINDOW *status_win;
4665 static bool status_empty = TRUE;
4667 /* Update status and title window. */
4669 report(const char *msg, ...)
4671 struct view *view = display[current_view];
4677 char buf[SIZEOF_STR];
4680 va_start(args, msg);
4681 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4682 buf[sizeof(buf) - 1] = 0;
4683 buf[sizeof(buf) - 2] = '.';
4684 buf[sizeof(buf) - 3] = '.';
4685 buf[sizeof(buf) - 4] = '.';
4691 if (!status_empty || *msg) {
4694 va_start(args, msg);
4696 wmove(status_win, 0, 0);
4698 vwprintw(status_win, msg, args);
4699 status_empty = FALSE;
4701 status_empty = TRUE;
4703 wclrtoeol(status_win);
4704 wrefresh(status_win);
4709 update_view_title(view);
4710 update_display_cursor(view);
4713 /* Controls when nodelay should be in effect when polling user input. */
4715 set_nonblocking_input(bool loading)
4717 static unsigned int loading_views;
4719 if ((loading == FALSE && loading_views-- == 1) ||
4720 (loading == TRUE && loading_views++ == 0))
4721 nodelay(status_win, loading);
4729 /* Initialize the curses library */
4730 if (isatty(STDIN_FILENO)) {
4731 cursed = !!initscr();
4733 /* Leave stdin and stdout alone when acting as a pager. */
4734 FILE *io = fopen("/dev/tty", "r+");
4737 die("Failed to open /dev/tty");
4738 cursed = !!newterm(NULL, io, io);
4742 die("Failed to initialize curses");
4744 nonl(); /* Tell curses not to do NL->CR/NL on output */
4745 cbreak(); /* Take input chars one at a time, no wait for \n */
4746 noecho(); /* Don't echo input */
4747 leaveok(stdscr, TRUE);
4752 getmaxyx(stdscr, y, x);
4753 status_win = newwin(1, 0, y - 1, 0);
4755 die("Failed to create status window");
4757 /* Enable keyboard mapping */
4758 keypad(status_win, TRUE);
4759 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4763 read_prompt(const char *prompt)
4765 enum { READING, STOP, CANCEL } status = READING;
4766 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4769 while (status == READING) {
4775 foreach_view (view, i)
4780 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4781 wclrtoeol(status_win);
4783 /* Refresh, accept single keystroke of input */
4784 key = wgetch(status_win);
4789 status = pos ? STOP : CANCEL;
4807 if (pos >= sizeof(buf)) {
4808 report("Input string too long");
4813 buf[pos++] = (char) key;
4817 /* Clear the status window */
4818 status_empty = FALSE;
4821 if (status == CANCEL)
4830 * Repository references
4833 static struct ref *refs;
4834 static size_t refs_size;
4836 /* Id <-> ref store */
4837 static struct ref ***id_refs;
4838 static size_t id_refs_size;
4840 static struct ref **
4843 struct ref ***tmp_id_refs;
4844 struct ref **ref_list = NULL;
4845 size_t ref_list_size = 0;
4848 for (i = 0; i < id_refs_size; i++)
4849 if (!strcmp(id, id_refs[i][0]->id))
4852 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4856 id_refs = tmp_id_refs;
4858 for (i = 0; i < refs_size; i++) {
4861 if (strcmp(id, refs[i].id))
4864 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4872 if (ref_list_size > 0)
4873 ref_list[ref_list_size - 1]->next = 1;
4874 ref_list[ref_list_size] = &refs[i];
4876 /* XXX: The properties of the commit chains ensures that we can
4877 * safely modify the shared ref. The repo references will
4878 * always be similar for the same id. */
4879 ref_list[ref_list_size]->next = 0;
4884 id_refs[id_refs_size++] = ref_list;
4890 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4895 bool remote = FALSE;
4896 bool check_replace = FALSE;
4898 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4899 if (!strcmp(name + namelen - 3, "^{}")) {
4902 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
4903 check_replace = TRUE;
4909 namelen -= STRING_SIZE("refs/tags/");
4910 name += STRING_SIZE("refs/tags/");
4912 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4914 namelen -= STRING_SIZE("refs/remotes/");
4915 name += STRING_SIZE("refs/remotes/");
4917 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4918 namelen -= STRING_SIZE("refs/heads/");
4919 name += STRING_SIZE("refs/heads/");
4921 } else if (!strcmp(name, "HEAD")) {
4925 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
4926 /* it's an annotated tag, replace the previous sha1 with the
4927 * resolved commit id; relies on the fact git-ls-remote lists
4928 * the commit id of an annotated tag right beofre the commit id
4930 refs[refs_size - 1].ltag = ltag;
4931 string_copy_rev(refs[refs_size - 1].id, id);
4935 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4939 ref = &refs[refs_size++];
4940 ref->name = malloc(namelen + 1);
4944 strncpy(ref->name, name, namelen);
4945 ref->name[namelen] = 0;
4948 ref->remote = remote;
4949 string_copy_rev(ref->id, id);
4957 const char *cmd_env = getenv("TIG_LS_REMOTE");
4958 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4960 return read_properties(popen(cmd, "r"), "\t", read_ref);
4964 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4966 if (!strcmp(name, "i18n.commitencoding"))
4967 string_ncopy(opt_encoding, value, valuelen);
4969 if (!strcmp(name, "core.editor"))
4970 string_ncopy(opt_editor, value, valuelen);
4976 load_repo_config(void)
4978 return read_properties(popen(GIT_CONFIG " --list", "r"),
4979 "=", read_repo_config_option);
4983 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4985 if (!opt_git_dir[0]) {
4986 string_ncopy(opt_git_dir, name, namelen);
4988 } else if (opt_is_inside_work_tree == -1) {
4989 /* This can be 3 different values depending on the
4990 * version of git being used. If git-rev-parse does not
4991 * understand --is-inside-work-tree it will simply echo
4992 * the option else either "true" or "false" is printed.
4993 * Default to true for the unknown case. */
4994 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4997 string_ncopy(opt_cdup, name, namelen);
5003 /* XXX: The line outputted by "--show-cdup" can be empty so the option
5004 * must be the last one! */
5006 load_repo_info(void)
5008 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
5009 "=", read_repo_info);
5013 read_properties(FILE *pipe, const char *separators,
5014 int (*read_property)(char *, size_t, char *, size_t))
5016 char buffer[BUFSIZ];
5023 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5028 name = chomp_string(name);
5029 namelen = strcspn(name, separators);
5031 if (name[namelen]) {
5033 value = chomp_string(name + namelen + 1);
5034 valuelen = strlen(value);
5041 state = read_property(name, namelen, value, valuelen);
5044 if (state != ERR && ferror(pipe))
5057 static void __NORETURN
5060 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5066 static void __NORETURN
5067 die(const char *err, ...)
5073 va_start(args, err);
5074 fputs("tig: ", stderr);
5075 vfprintf(stderr, err, args);
5076 fputs("\n", stderr);
5083 warn(const char *msg, ...)
5087 va_start(args, msg);
5088 fputs("tig warning: ", stderr);
5089 vfprintf(stderr, msg, args);
5090 fputs("\n", stderr);
5095 main(int argc, char *argv[])
5098 enum request request;
5101 signal(SIGINT, quit);
5103 if (setlocale(LC_ALL, "")) {
5104 char *codeset = nl_langinfo(CODESET);
5106 string_ncopy(opt_codeset, codeset, strlen(codeset));
5109 if (load_repo_info() == ERR)
5110 die("Failed to load repo info.");
5112 if (load_options() == ERR)
5113 die("Failed to load user config.");
5115 /* Load the repo config file so options can be overwritten from
5116 * the command line. */
5117 if (load_repo_config() == ERR)
5118 die("Failed to load repo config.");
5120 if (!parse_options(argc, argv))
5123 /* Require a git repository unless when running in pager mode. */
5124 if (!opt_git_dir[0])
5125 die("Not a git repository");
5127 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5128 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5129 if (opt_iconv == ICONV_NONE)
5130 die("Failed to initialize character set conversion");
5133 if (load_refs() == ERR)
5134 die("Failed to load refs.");
5136 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5137 view->cmd_env = getenv(view->cmd_env);
5139 request = opt_request;
5143 while (view_driver(display[current_view], request)) {
5147 foreach_view (view, i)
5150 /* Refresh, accept single keystroke of input */
5151 key = wgetch(status_win);
5153 /* wgetch() with nodelay() enabled returns ERR when there's no
5160 request = get_keybinding(display[current_view]->keymap, key);
5162 /* Some low-level request handling. This keeps access to
5163 * status_win restricted. */
5167 char *cmd = read_prompt(":");
5169 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5170 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5171 opt_request = REQ_VIEW_DIFF;
5173 opt_request = REQ_VIEW_PAGER;
5182 case REQ_SEARCH_BACK:
5184 const char *prompt = request == REQ_SEARCH
5186 char *search = read_prompt(prompt);
5189 string_ncopy(opt_search, search, strlen(search));
5194 case REQ_SCREEN_RESIZE:
5198 getmaxyx(stdscr, height, width);
5200 /* Resize the status view and let the view driver take
5201 * care of resizing the displayed views. */
5202 wresize(status_win, 1, width);
5203 mvwin(status_win, height - 1, 0);
5204 wrefresh(status_win);