1 /* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
48 #define __NORETURN __attribute__((__noreturn__))
53 static void __NORETURN die(const char *err, ...);
54 static void report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
59 #define ABS(x) ((x) >= 0 ? (x) : -(x))
60 #define MIN(x, y) ((x) < (y) ? (x) : (y))
62 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x) (sizeof(x) - 1)
65 #define SIZEOF_STR 1024 /* Default string size. */
66 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
71 #define REVGRAPH_INIT 'I'
72 #define REVGRAPH_MERGE 'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE '|'
77 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT (-1)
82 #define ICONV_NONE ((iconv_t) -1)
84 #define ICONV_CONST /* nothing */
87 /* The format and size of the date column in the main view. */
88 #define DATE_FORMAT "%Y-%m-%d %H:%M"
89 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
91 #define AUTHOR_COLS 20
93 /* The default interval between line numbers. */
94 #define NUMBER_INTERVAL 1
98 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
101 #define GIT_CONFIG "git config"
104 #define TIG_LS_REMOTE \
105 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
107 #define TIG_DIFF_CMD \
108 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
110 #define TIG_LOG_CMD \
111 "git log --cc --stat -n100 %s 2>/dev/null"
113 #define TIG_MAIN_CMD \
114 "git log --topo-order --pretty=raw %s 2>/dev/null"
116 #define TIG_TREE_CMD \
119 #define TIG_BLOB_CMD \
120 "git cat-file blob %s"
122 /* XXX: Needs to be defined to the empty string. */
123 #define TIG_HELP_CMD ""
124 #define TIG_PAGER_CMD ""
125 #define TIG_STATUS_CMD ""
126 #define TIG_STAGE_CMD ""
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
130 #define KEY_RETURN '\r'
135 char *name; /* Ref name; tag or head names are shortened. */
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 unsigned int tag:1; /* Is it a tag? */
138 unsigned int remote:1; /* Is it a remote ref? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
142 static struct ref **get_refs(char *id);
151 set_from_int_map(struct int_map *map, size_t map_size,
152 int *value, const char *name, int namelen)
157 for (i = 0; i < map_size; i++)
158 if (namelen == map[i].namelen &&
159 !strncasecmp(name, map[i].name, namelen)) {
160 *value = map[i].value;
173 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
175 if (srclen > dstlen - 1)
178 strncpy(dst, src, srclen);
182 /* Shorthands for safely copying into a fixed buffer. */
184 #define string_copy(dst, src) \
185 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
187 #define string_ncopy(dst, src, srclen) \
188 string_ncopy_do(dst, sizeof(dst), src, srclen)
190 #define string_copy_rev(dst, src) \
191 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
193 #define string_add(dst, from, src) \
194 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
197 chomp_string(char *name)
201 while (isspace(*name))
204 namelen = strlen(name) - 1;
205 while (namelen > 0 && isspace(name[namelen]))
212 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
215 size_t pos = bufpos ? *bufpos : 0;
218 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
224 return pos >= bufsize ? FALSE : TRUE;
227 #define string_format(buf, fmt, args...) \
228 string_nformat(buf, sizeof(buf), NULL, fmt, args)
230 #define string_format_from(buf, from, fmt, args...) \
231 string_nformat(buf, sizeof(buf), from, fmt, args)
234 string_enum_compare(const char *str1, const char *str2, int len)
238 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
240 /* Diff-Header == DIFF_HEADER */
241 for (i = 0; i < len; i++) {
242 if (toupper(str1[i]) == toupper(str2[i]))
245 if (string_enum_sep(str1[i]) &&
246 string_enum_sep(str2[i]))
249 return str1[i] - str2[i];
257 * NOTE: The following is a slightly modified copy of the git project's shell
258 * quoting routines found in the quote.c file.
260 * Help to copy the thing properly quoted for the shell safety. any single
261 * quote is replaced with '\'', any exclamation point is replaced with '\!',
262 * and the whole thing is enclosed in a
265 * original sq_quote result
266 * name ==> name ==> 'name'
267 * a b ==> a b ==> 'a b'
268 * a'b ==> a'\''b ==> 'a'\''b'
269 * a!b ==> a'\!'b ==> 'a'\!'b'
273 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
277 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
280 while ((c = *src++)) {
281 if (c == '\'' || c == '!') {
292 if (bufsize < SIZEOF_STR)
304 /* XXX: Keep the view request first and in sync with views[]. */ \
305 REQ_GROUP("View switching") \
306 REQ_(VIEW_MAIN, "Show main view"), \
307 REQ_(VIEW_DIFF, "Show diff view"), \
308 REQ_(VIEW_LOG, "Show log view"), \
309 REQ_(VIEW_TREE, "Show tree view"), \
310 REQ_(VIEW_BLOB, "Show blob view"), \
311 REQ_(VIEW_HELP, "Show help page"), \
312 REQ_(VIEW_PAGER, "Show pager view"), \
313 REQ_(VIEW_STATUS, "Show status view"), \
314 REQ_(VIEW_STAGE, "Show stage view"), \
316 REQ_GROUP("View manipulation") \
317 REQ_(ENTER, "Enter current line and scroll"), \
318 REQ_(NEXT, "Move to next"), \
319 REQ_(PREVIOUS, "Move to previous"), \
320 REQ_(VIEW_NEXT, "Move focus to next view"), \
321 REQ_(REFRESH, "Reload and refresh"), \
322 REQ_(VIEW_CLOSE, "Close the current view"), \
323 REQ_(QUIT, "Close all views and quit"), \
325 REQ_GROUP("Cursor navigation") \
326 REQ_(MOVE_UP, "Move cursor one line up"), \
327 REQ_(MOVE_DOWN, "Move cursor one line down"), \
328 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
329 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
330 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
331 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
333 REQ_GROUP("Scrolling") \
334 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
335 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
336 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
337 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
339 REQ_GROUP("Searching") \
340 REQ_(SEARCH, "Search the view"), \
341 REQ_(SEARCH_BACK, "Search backwards in the view"), \
342 REQ_(FIND_NEXT, "Find next search match"), \
343 REQ_(FIND_PREV, "Find previous search match"), \
346 REQ_(PROMPT, "Bring up the prompt"), \
347 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
348 REQ_(SCREEN_RESIZE, "Resize the screen"), \
349 REQ_(SHOW_VERSION, "Show version information"), \
350 REQ_(STOP_LOADING, "Stop all loading views"), \
351 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
352 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
353 REQ_(STATUS_UPDATE, "Update file status"), \
354 REQ_(STATUS_MERGE, "Merge file using external tool"), \
355 REQ_(EDIT, "Open in editor"), \
356 REQ_(CHERRY_PICK, "Cherry-pick commit to current branch"), \
357 REQ_(NONE, "Do nothing")
360 /* User action requests. */
362 #define REQ_GROUP(help)
363 #define REQ_(req, help) REQ_##req
365 /* Offset all requests to avoid conflicts with ncurses getch values. */
366 REQ_OFFSET = KEY_MAX + 1,
373 struct request_info {
374 enum request request;
380 static struct request_info req_info[] = {
381 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
382 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
389 get_request(const char *name)
391 int namelen = strlen(name);
394 for (i = 0; i < ARRAY_SIZE(req_info); i++)
395 if (req_info[i].namelen == namelen &&
396 !string_enum_compare(req_info[i].name, name, namelen))
397 return req_info[i].request;
407 static const char usage[] =
408 "tig " TIG_VERSION " (" __DATE__ ")\n"
410 "Usage: tig [options]\n"
411 " or: tig [options] [--] [git log options]\n"
412 " or: tig [options] log [git log options]\n"
413 " or: tig [options] diff [git diff options]\n"
414 " or: tig [options] show [git show options]\n"
415 " or: tig [options] < [git command output]\n"
418 " -l Start up in log view\n"
419 " -d Start up in diff view\n"
420 " -S Start up in status view\n"
421 " -n[I], --line-number[=I] Show line numbers with given interval\n"
422 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
423 " -- Mark end of tig options\n"
424 " -v, --version Show version and exit\n"
425 " -h, --help Show help message and exit\n";
427 /* Option and state variables. */
428 static bool opt_line_number = FALSE;
429 static bool opt_rev_graph = FALSE;
430 static int opt_num_interval = NUMBER_INTERVAL;
431 static int opt_tab_size = TABSIZE;
432 static enum request opt_request = REQ_VIEW_MAIN;
433 static char opt_cmd[SIZEOF_STR] = "";
434 static char opt_path[SIZEOF_STR] = "";
435 static FILE *opt_pipe = NULL;
436 static char opt_encoding[20] = "UTF-8";
437 static bool opt_utf8 = TRUE;
438 static char opt_codeset[20] = "UTF-8";
439 static iconv_t opt_iconv = ICONV_NONE;
440 static char opt_search[SIZEOF_STR] = "";
441 static char opt_cdup[SIZEOF_STR] = "";
442 static char opt_git_dir[SIZEOF_STR] = "";
443 static char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
444 static char opt_editor[SIZEOF_STR] = "";
452 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
462 int namelen = strlen(name);
466 if (strncmp(opt, name, namelen))
469 if (opt[namelen] == '=')
470 value = opt + namelen + 1;
473 if (!short_name || opt[1] != short_name)
478 va_start(args, type);
479 if (type == OPT_INT) {
480 number = va_arg(args, int *);
482 *number = atoi(value);
489 /* Returns the index of log or diff command or -1 to exit. */
491 parse_options(int argc, char *argv[])
495 for (i = 1; i < argc; i++) {
498 if (!strcmp(opt, "log") ||
499 !strcmp(opt, "diff") ||
500 !strcmp(opt, "show")) {
501 opt_request = opt[0] == 'l'
502 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
506 if (opt[0] && opt[0] != '-')
509 if (!strcmp(opt, "-l")) {
510 opt_request = REQ_VIEW_LOG;
514 if (!strcmp(opt, "-d")) {
515 opt_request = REQ_VIEW_DIFF;
519 if (!strcmp(opt, "-S")) {
520 opt_request = REQ_VIEW_STATUS;
524 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
525 opt_line_number = TRUE;
529 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
530 opt_tab_size = MIN(opt_tab_size, TABSIZE);
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, "--")) {
549 die("unknown option '%s'\n\n%s", opt, usage);
552 if (!isatty(STDIN_FILENO)) {
553 opt_request = REQ_VIEW_PAGER;
556 } else if (i < argc) {
559 if (opt_request == REQ_VIEW_MAIN)
560 /* XXX: This is vulnerable to the user overriding
561 * options required for the main view parser. */
562 string_copy(opt_cmd, "git log --pretty=raw");
564 string_copy(opt_cmd, "git");
565 buf_size = strlen(opt_cmd);
567 while (buf_size < sizeof(opt_cmd) && i < argc) {
568 opt_cmd[buf_size++] = ' ';
569 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
572 if (buf_size >= sizeof(opt_cmd))
573 die("command too long");
575 opt_cmd[buf_size] = 0;
578 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
586 * Line-oriented content detection.
590 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
591 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
592 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
593 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
594 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
595 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
599 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
600 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
603 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
604 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
605 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
606 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
607 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
608 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
609 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
610 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
611 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
612 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
613 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
614 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
615 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
616 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
619 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
620 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
621 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
622 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
623 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
624 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
625 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
626 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
627 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
628 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
629 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
630 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
631 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
632 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
633 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
634 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
635 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
639 #define LINE(type, line, fg, bg, attr) \
646 const char *name; /* Option name. */
647 int namelen; /* Size of option name. */
648 const char *line; /* The start of line to match. */
649 int linelen; /* Size of string to match. */
650 int fg, bg, attr; /* Color and text attributes for the lines. */
653 static struct line_info line_info[] = {
654 #define LINE(type, line, fg, bg, attr) \
655 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
660 static enum line_type
661 get_line_type(char *line)
663 int linelen = strlen(line);
666 for (type = 0; type < ARRAY_SIZE(line_info); type++)
667 /* Case insensitive search matches Signed-off-by lines better. */
668 if (linelen >= line_info[type].linelen &&
669 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
676 get_line_attr(enum line_type type)
678 assert(type < ARRAY_SIZE(line_info));
679 return COLOR_PAIR(type) | line_info[type].attr;
682 static struct line_info *
683 get_line_info(char *name, int namelen)
687 for (type = 0; type < ARRAY_SIZE(line_info); type++)
688 if (namelen == line_info[type].namelen &&
689 !string_enum_compare(line_info[type].name, name, namelen))
690 return &line_info[type];
698 int default_bg = COLOR_BLACK;
699 int default_fg = COLOR_WHITE;
704 if (use_default_colors() != ERR) {
709 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
710 struct line_info *info = &line_info[type];
711 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
712 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
714 init_pair(type, fg, bg);
722 unsigned int selected:1;
724 void *data; /* User data */
734 enum request request;
735 struct keybinding *next;
738 static struct keybinding default_keybindings[] = {
740 { 'm', REQ_VIEW_MAIN },
741 { 'd', REQ_VIEW_DIFF },
742 { 'l', REQ_VIEW_LOG },
743 { 't', REQ_VIEW_TREE },
744 { 'f', REQ_VIEW_BLOB },
745 { 'p', REQ_VIEW_PAGER },
746 { 'h', REQ_VIEW_HELP },
747 { 'S', REQ_VIEW_STATUS },
748 { 'c', REQ_VIEW_STAGE },
750 /* View manipulation */
751 { 'q', REQ_VIEW_CLOSE },
752 { KEY_TAB, REQ_VIEW_NEXT },
753 { KEY_RETURN, REQ_ENTER },
754 { KEY_UP, REQ_PREVIOUS },
755 { KEY_DOWN, REQ_NEXT },
756 { 'R', REQ_REFRESH },
758 /* Cursor navigation */
759 { 'k', REQ_MOVE_UP },
760 { 'j', REQ_MOVE_DOWN },
761 { KEY_HOME, REQ_MOVE_FIRST_LINE },
762 { KEY_END, REQ_MOVE_LAST_LINE },
763 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
764 { ' ', REQ_MOVE_PAGE_DOWN },
765 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
766 { 'b', REQ_MOVE_PAGE_UP },
767 { '-', REQ_MOVE_PAGE_UP },
770 { KEY_IC, REQ_SCROLL_LINE_UP },
771 { KEY_DC, REQ_SCROLL_LINE_DOWN },
772 { 'w', REQ_SCROLL_PAGE_UP },
773 { 's', REQ_SCROLL_PAGE_DOWN },
777 { '?', REQ_SEARCH_BACK },
778 { 'n', REQ_FIND_NEXT },
779 { 'N', REQ_FIND_PREV },
783 { 'z', REQ_STOP_LOADING },
784 { 'v', REQ_SHOW_VERSION },
785 { 'r', REQ_SCREEN_REDRAW },
786 { '.', REQ_TOGGLE_LINENO },
787 { 'g', REQ_TOGGLE_REV_GRAPH },
789 { 'u', REQ_STATUS_UPDATE },
790 { 'M', REQ_STATUS_MERGE },
792 { 'C', REQ_CHERRY_PICK },
794 /* Using the ncurses SIGWINCH handler. */
795 { KEY_RESIZE, REQ_SCREEN_RESIZE },
798 #define KEYMAP_INFO \
811 #define KEYMAP_(name) KEYMAP_##name
816 static struct int_map keymap_table[] = {
817 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
822 #define set_keymap(map, name) \
823 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
825 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
828 add_keybinding(enum keymap keymap, enum request request, int key)
830 struct keybinding *keybinding;
832 keybinding = calloc(1, sizeof(*keybinding));
834 die("Failed to allocate keybinding");
836 keybinding->alias = key;
837 keybinding->request = request;
838 keybinding->next = keybindings[keymap];
839 keybindings[keymap] = keybinding;
842 /* Looks for a key binding first in the given map, then in the generic map, and
843 * lastly in the default keybindings. */
845 get_keybinding(enum keymap keymap, int key)
847 struct keybinding *kbd;
850 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
851 if (kbd->alias == key)
854 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
855 if (kbd->alias == key)
858 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
859 if (default_keybindings[i].alias == key)
860 return default_keybindings[i].request;
862 return (enum request) key;
871 static struct key key_table[] = {
872 { "Enter", KEY_RETURN },
874 { "Backspace", KEY_BACKSPACE },
876 { "Escape", KEY_ESC },
877 { "Left", KEY_LEFT },
878 { "Right", KEY_RIGHT },
880 { "Down", KEY_DOWN },
881 { "Insert", KEY_IC },
882 { "Delete", KEY_DC },
884 { "Home", KEY_HOME },
886 { "PageUp", KEY_PPAGE },
887 { "PageDown", KEY_NPAGE },
897 { "F10", KEY_F(10) },
898 { "F11", KEY_F(11) },
899 { "F12", KEY_F(12) },
903 get_key_value(const char *name)
907 for (i = 0; i < ARRAY_SIZE(key_table); i++)
908 if (!strcasecmp(key_table[i].name, name))
909 return key_table[i].value;
911 if (strlen(name) == 1 && isprint(*name))
918 get_key(enum request request)
920 static char buf[BUFSIZ];
921 static char key_char[] = "'X'";
928 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
929 struct keybinding *keybinding = &default_keybindings[i];
933 if (keybinding->request != request)
936 for (key = 0; key < ARRAY_SIZE(key_table); key++)
937 if (key_table[key].value == keybinding->alias)
938 seq = key_table[key].name;
941 keybinding->alias < 127 &&
942 isprint(keybinding->alias)) {
943 key_char[1] = (char) keybinding->alias;
950 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
951 return "Too many keybindings!";
960 * User config file handling.
963 static struct int_map color_map[] = {
964 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
976 #define set_color(color, name) \
977 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
979 static struct int_map attr_map[] = {
980 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
990 #define set_attribute(attr, name) \
991 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
993 static int config_lineno;
994 static bool config_errors;
995 static char *config_msg;
997 /* Wants: object fgcolor bgcolor [attr] */
999 option_color_command(int argc, char *argv[])
1001 struct line_info *info;
1003 if (argc != 3 && argc != 4) {
1004 config_msg = "Wrong number of arguments given to color command";
1008 info = get_line_info(argv[0], strlen(argv[0]));
1010 config_msg = "Unknown color name";
1014 if (set_color(&info->fg, argv[1]) == ERR ||
1015 set_color(&info->bg, argv[2]) == ERR) {
1016 config_msg = "Unknown color";
1020 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1021 config_msg = "Unknown attribute";
1028 /* Wants: name = value */
1030 option_set_command(int argc, char *argv[])
1033 config_msg = "Wrong number of arguments given to set command";
1037 if (strcmp(argv[1], "=")) {
1038 config_msg = "No value assigned";
1042 if (!strcmp(argv[0], "show-rev-graph")) {
1043 opt_rev_graph = (!strcmp(argv[2], "1") ||
1044 !strcmp(argv[2], "true") ||
1045 !strcmp(argv[2], "yes"));
1049 if (!strcmp(argv[0], "line-number-interval")) {
1050 opt_num_interval = atoi(argv[2]);
1054 if (!strcmp(argv[0], "tab-size")) {
1055 opt_tab_size = atoi(argv[2]);
1059 if (!strcmp(argv[0], "commit-encoding")) {
1060 char *arg = argv[2];
1061 int delimiter = *arg;
1064 switch (delimiter) {
1067 for (arg++, i = 0; arg[i]; i++)
1068 if (arg[i] == delimiter) {
1073 string_ncopy(opt_encoding, arg, strlen(arg));
1078 config_msg = "Unknown variable name";
1082 /* Wants: mode request key */
1084 option_bind_command(int argc, char *argv[])
1086 enum request request;
1091 config_msg = "Wrong number of arguments given to bind command";
1095 if (set_keymap(&keymap, argv[0]) == ERR) {
1096 config_msg = "Unknown key map";
1100 key = get_key_value(argv[1]);
1102 config_msg = "Unknown key";
1106 request = get_request(argv[2]);
1107 if (request == REQ_NONE) {
1108 config_msg = "Unknown request name";
1112 add_keybinding(keymap, request, key);
1118 set_option(char *opt, char *value)
1125 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1126 argv[argc++] = value;
1133 while (isspace(*value))
1137 if (!strcmp(opt, "color"))
1138 return option_color_command(argc, argv);
1140 if (!strcmp(opt, "set"))
1141 return option_set_command(argc, argv);
1143 if (!strcmp(opt, "bind"))
1144 return option_bind_command(argc, argv);
1146 config_msg = "Unknown option command";
1151 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1156 config_msg = "Internal error";
1158 /* Check for comment markers, since read_properties() will
1159 * only ensure opt and value are split at first " \t". */
1160 optlen = strcspn(opt, "#");
1164 if (opt[optlen] != 0) {
1165 config_msg = "No option value";
1169 /* Look for comment endings in the value. */
1170 size_t len = strcspn(value, "#");
1172 if (len < valuelen) {
1174 value[valuelen] = 0;
1177 status = set_option(opt, value);
1180 if (status == ERR) {
1181 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1182 config_lineno, (int) optlen, opt, config_msg);
1183 config_errors = TRUE;
1186 /* Always keep going if errors are encountered. */
1193 char *home = getenv("HOME");
1194 char buf[SIZEOF_STR];
1198 config_errors = FALSE;
1200 if (!home || !string_format(buf, "%s/.tigrc", home))
1203 /* It's ok that the file doesn't exist. */
1204 file = fopen(buf, "r");
1208 if (read_properties(file, " \t", read_option) == ERR ||
1209 config_errors == TRUE)
1210 fprintf(stderr, "Errors while loading %s.\n", buf);
1223 /* The display array of active views and the index of the current view. */
1224 static struct view *display[2];
1225 static unsigned int current_view;
1227 /* Reading from the prompt? */
1228 static bool input_mode = FALSE;
1230 #define foreach_displayed_view(view, i) \
1231 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1233 #define displayed_views() (display[1] != NULL ? 2 : 1)
1235 /* Current head and commit ID */
1236 static char ref_blob[SIZEOF_REF] = "";
1237 static char ref_commit[SIZEOF_REF] = "HEAD";
1238 static char ref_head[SIZEOF_REF] = "HEAD";
1241 const char *name; /* View name */
1242 const char *cmd_fmt; /* Default command line format */
1243 const char *cmd_env; /* Command line set via environment */
1244 const char *id; /* Points to either of ref_{head,commit,blob} */
1246 struct view_ops *ops; /* View operations */
1248 enum keymap keymap; /* What keymap does this view have */
1250 char cmd[SIZEOF_STR]; /* Command buffer */
1251 char ref[SIZEOF_REF]; /* Hovered commit reference */
1252 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1254 int height, width; /* The width and height of the main window */
1255 WINDOW *win; /* The main window */
1256 WINDOW *title; /* The title window living below the main window */
1259 unsigned long offset; /* Offset of the window top */
1260 unsigned long lineno; /* Current line number */
1263 char grep[SIZEOF_STR]; /* Search string */
1264 regex_t *regex; /* Pre-compiled regex */
1266 /* If non-NULL, points to the view that opened this view. If this view
1267 * is closed tig will switch back to the parent view. */
1268 struct view *parent;
1271 unsigned long lines; /* Total number of lines */
1272 struct line *line; /* Line index */
1273 unsigned long line_size;/* Total number of allocated lines */
1274 unsigned int digits; /* Number of digits in the lines member. */
1282 /* What type of content being displayed. Used in the title bar. */
1284 /* Open and reads in all view content. */
1285 bool (*open)(struct view *view);
1286 /* Read one line; updates view->line. */
1287 bool (*read)(struct view *view, char *data);
1288 /* Draw one line; @lineno must be < view->height. */
1289 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1290 /* Depending on view handle a special requests. */
1291 enum request (*request)(struct view *view, enum request request, struct line *line);
1292 /* Search for regex in a line. */
1293 bool (*grep)(struct view *view, struct line *line);
1295 void (*select)(struct view *view, struct line *line);
1298 static struct view_ops pager_ops;
1299 static struct view_ops main_ops;
1300 static struct view_ops tree_ops;
1301 static struct view_ops blob_ops;
1302 static struct view_ops help_ops;
1303 static struct view_ops status_ops;
1304 static struct view_ops stage_ops;
1306 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1307 { name, cmd, #env, ref, ops, map}
1309 #define VIEW_(id, name, ops, ref) \
1310 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1313 static struct view views[] = {
1314 VIEW_(MAIN, "main", &main_ops, ref_head),
1315 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1316 VIEW_(LOG, "log", &pager_ops, ref_head),
1317 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1318 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1319 VIEW_(HELP, "help", &help_ops, ""),
1320 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1321 VIEW_(STATUS, "status", &status_ops, ""),
1322 VIEW_(STAGE, "stage", &stage_ops, ""),
1325 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1327 #define foreach_view(view, i) \
1328 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1330 #define view_is_displayed(view) \
1331 (view == display[0] || view == display[1])
1334 draw_view_line(struct view *view, unsigned int lineno)
1337 bool selected = (view->offset + lineno == view->lineno);
1340 assert(view_is_displayed(view));
1342 if (view->offset + lineno >= view->lines)
1345 line = &view->line[view->offset + lineno];
1348 line->selected = TRUE;
1349 view->ops->select(view, line);
1350 } else if (line->selected) {
1351 line->selected = FALSE;
1352 wmove(view->win, lineno, 0);
1353 wclrtoeol(view->win);
1356 scrollok(view->win, FALSE);
1357 draw_ok = view->ops->draw(view, line, lineno, selected);
1358 scrollok(view->win, TRUE);
1364 redraw_view_from(struct view *view, int lineno)
1366 assert(0 <= lineno && lineno < view->height);
1368 for (; lineno < view->height; lineno++) {
1369 if (!draw_view_line(view, lineno))
1373 redrawwin(view->win);
1375 wnoutrefresh(view->win);
1377 wrefresh(view->win);
1381 redraw_view(struct view *view)
1384 redraw_view_from(view, 0);
1389 update_view_title(struct view *view)
1391 char buf[SIZEOF_STR];
1392 char state[SIZEOF_STR];
1393 size_t bufpos = 0, statelen = 0;
1395 assert(view_is_displayed(view));
1397 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1398 unsigned int view_lines = view->offset + view->height;
1399 unsigned int lines = view->lines
1400 ? MIN(view_lines, view->lines) * 100 / view->lines
1403 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1410 time_t secs = time(NULL) - view->start_time;
1412 /* Three git seconds are a long time ... */
1414 string_format_from(state, &statelen, " %lds", secs);
1418 string_format_from(buf, &bufpos, "[%s]", view->name);
1419 if (*view->ref && bufpos < view->width) {
1420 size_t refsize = strlen(view->ref);
1421 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1423 if (minsize < view->width)
1424 refsize = view->width - minsize + 7;
1425 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1428 if (statelen && bufpos < view->width) {
1429 string_format_from(buf, &bufpos, " %s", state);
1432 if (view == display[current_view])
1433 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1435 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1437 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1438 wclrtoeol(view->title);
1439 wmove(view->title, 0, view->width - 1);
1442 wnoutrefresh(view->title);
1444 wrefresh(view->title);
1448 resize_display(void)
1451 struct view *base = display[0];
1452 struct view *view = display[1] ? display[1] : display[0];
1454 /* Setup window dimensions */
1456 getmaxyx(stdscr, base->height, base->width);
1458 /* Make room for the status window. */
1462 /* Horizontal split. */
1463 view->width = base->width;
1464 view->height = SCALE_SPLIT_VIEW(base->height);
1465 base->height -= view->height;
1467 /* Make room for the title bar. */
1471 /* Make room for the title bar. */
1476 foreach_displayed_view (view, i) {
1478 view->win = newwin(view->height, 0, offset, 0);
1480 die("Failed to create %s view", view->name);
1482 scrollok(view->win, TRUE);
1484 view->title = newwin(1, 0, offset + view->height, 0);
1486 die("Failed to create title window");
1489 wresize(view->win, view->height, view->width);
1490 mvwin(view->win, offset, 0);
1491 mvwin(view->title, offset + view->height, 0);
1494 offset += view->height + 1;
1499 redraw_display(void)
1504 foreach_displayed_view (view, i) {
1506 update_view_title(view);
1511 update_display_cursor(struct view *view)
1513 /* Move the cursor to the right-most column of the cursor line.
1515 * XXX: This could turn out to be a bit expensive, but it ensures that
1516 * the cursor does not jump around. */
1518 wmove(view->win, view->lineno - view->offset, view->width - 1);
1519 wrefresh(view->win);
1527 /* Scrolling backend */
1529 do_scroll_view(struct view *view, int lines)
1531 bool redraw_current_line = FALSE;
1533 /* The rendering expects the new offset. */
1534 view->offset += lines;
1536 assert(0 <= view->offset && view->offset < view->lines);
1539 /* Move current line into the view. */
1540 if (view->lineno < view->offset) {
1541 view->lineno = view->offset;
1542 redraw_current_line = TRUE;
1543 } else if (view->lineno >= view->offset + view->height) {
1544 view->lineno = view->offset + view->height - 1;
1545 redraw_current_line = TRUE;
1548 assert(view->offset <= view->lineno && view->lineno < view->lines);
1550 /* Redraw the whole screen if scrolling is pointless. */
1551 if (view->height < ABS(lines)) {
1555 int line = lines > 0 ? view->height - lines : 0;
1556 int end = line + ABS(lines);
1558 wscrl(view->win, lines);
1560 for (; line < end; line++) {
1561 if (!draw_view_line(view, line))
1565 if (redraw_current_line)
1566 draw_view_line(view, view->lineno - view->offset);
1569 redrawwin(view->win);
1570 wrefresh(view->win);
1574 /* Scroll frontend */
1576 scroll_view(struct view *view, enum request request)
1580 assert(view_is_displayed(view));
1583 case REQ_SCROLL_PAGE_DOWN:
1584 lines = view->height;
1585 case REQ_SCROLL_LINE_DOWN:
1586 if (view->offset + lines > view->lines)
1587 lines = view->lines - view->offset;
1589 if (lines == 0 || view->offset + view->height >= view->lines) {
1590 report("Cannot scroll beyond the last line");
1595 case REQ_SCROLL_PAGE_UP:
1596 lines = view->height;
1597 case REQ_SCROLL_LINE_UP:
1598 if (lines > view->offset)
1599 lines = view->offset;
1602 report("Cannot scroll beyond the first line");
1610 die("request %d not handled in switch", request);
1613 do_scroll_view(view, lines);
1618 move_view(struct view *view, enum request request)
1620 int scroll_steps = 0;
1624 case REQ_MOVE_FIRST_LINE:
1625 steps = -view->lineno;
1628 case REQ_MOVE_LAST_LINE:
1629 steps = view->lines - view->lineno - 1;
1632 case REQ_MOVE_PAGE_UP:
1633 steps = view->height > view->lineno
1634 ? -view->lineno : -view->height;
1637 case REQ_MOVE_PAGE_DOWN:
1638 steps = view->lineno + view->height >= view->lines
1639 ? view->lines - view->lineno - 1 : view->height;
1651 die("request %d not handled in switch", request);
1654 if (steps <= 0 && view->lineno == 0) {
1655 report("Cannot move beyond the first line");
1658 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1659 report("Cannot move beyond the last line");
1663 /* Move the current line */
1664 view->lineno += steps;
1665 assert(0 <= view->lineno && view->lineno < view->lines);
1667 /* Check whether the view needs to be scrolled */
1668 if (view->lineno < view->offset ||
1669 view->lineno >= view->offset + view->height) {
1670 scroll_steps = steps;
1671 if (steps < 0 && -steps > view->offset) {
1672 scroll_steps = -view->offset;
1674 } else if (steps > 0) {
1675 if (view->lineno == view->lines - 1 &&
1676 view->lines > view->height) {
1677 scroll_steps = view->lines - view->offset - 1;
1678 if (scroll_steps >= view->height)
1679 scroll_steps -= view->height - 1;
1684 if (!view_is_displayed(view)) {
1685 view->offset += scroll_steps;
1686 assert(0 <= view->offset && view->offset < view->lines);
1687 view->ops->select(view, &view->line[view->lineno]);
1691 /* Repaint the old "current" line if we be scrolling */
1692 if (ABS(steps) < view->height)
1693 draw_view_line(view, view->lineno - steps - view->offset);
1696 do_scroll_view(view, scroll_steps);
1700 /* Draw the current line */
1701 draw_view_line(view, view->lineno - view->offset);
1703 redrawwin(view->win);
1704 wrefresh(view->win);
1713 static void search_view(struct view *view, enum request request);
1716 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1718 assert(view_is_displayed(view));
1720 if (!view->ops->grep(view, line))
1723 if (lineno - view->offset >= view->height) {
1724 view->offset = lineno;
1725 view->lineno = lineno;
1729 unsigned long old_lineno = view->lineno - view->offset;
1731 view->lineno = lineno;
1732 draw_view_line(view, old_lineno);
1734 draw_view_line(view, view->lineno - view->offset);
1735 redrawwin(view->win);
1736 wrefresh(view->win);
1739 report("Line %ld matches '%s'", lineno + 1, view->grep);
1744 find_next(struct view *view, enum request request)
1746 unsigned long lineno = view->lineno;
1751 report("No previous search");
1753 search_view(view, request);
1763 case REQ_SEARCH_BACK:
1772 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1773 lineno += direction;
1775 /* Note, lineno is unsigned long so will wrap around in which case it
1776 * will become bigger than view->lines. */
1777 for (; lineno < view->lines; lineno += direction) {
1778 struct line *line = &view->line[lineno];
1780 if (find_next_line(view, lineno, line))
1784 report("No match found for '%s'", view->grep);
1788 search_view(struct view *view, enum request request)
1793 regfree(view->regex);
1796 view->regex = calloc(1, sizeof(*view->regex));
1801 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1802 if (regex_err != 0) {
1803 char buf[SIZEOF_STR] = "unknown error";
1805 regerror(regex_err, view->regex, buf, sizeof(buf));
1806 report("Search failed: %s", buf);
1810 string_copy(view->grep, opt_search);
1812 find_next(view, request);
1816 * Incremental updating
1820 end_update(struct view *view)
1824 set_nonblocking_input(FALSE);
1825 if (view->pipe == stdin)
1833 begin_update(struct view *view)
1839 string_copy(view->cmd, opt_cmd);
1841 /* When running random commands, initially show the
1842 * command in the title. However, it maybe later be
1843 * overwritten if a commit line is selected. */
1844 if (view == VIEW(REQ_VIEW_PAGER))
1845 string_copy(view->ref, view->cmd);
1849 } else if (view == VIEW(REQ_VIEW_TREE)) {
1850 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1851 char path[SIZEOF_STR];
1853 if (strcmp(view->vid, view->id))
1854 opt_path[0] = path[0] = 0;
1855 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1858 if (!string_format(view->cmd, format, view->id, path))
1862 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1863 const char *id = view->id;
1865 if (!string_format(view->cmd, format, id, id, id, id, id))
1868 /* Put the current ref_* value to the view title ref
1869 * member. This is needed by the blob view. Most other
1870 * views sets it automatically after loading because the
1871 * first line is a commit line. */
1872 string_copy_rev(view->ref, view->id);
1875 /* Special case for the pager view. */
1877 view->pipe = opt_pipe;
1880 view->pipe = popen(view->cmd, "r");
1886 set_nonblocking_input(TRUE);
1891 string_copy_rev(view->vid, view->id);
1896 for (i = 0; i < view->lines; i++)
1897 if (view->line[i].data)
1898 free(view->line[i].data);
1904 view->start_time = time(NULL);
1909 static struct line *
1910 realloc_lines(struct view *view, size_t line_size)
1912 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1918 view->line_size = line_size;
1923 update_view(struct view *view)
1925 char in_buffer[BUFSIZ];
1926 char out_buffer[BUFSIZ * 2];
1928 /* The number of lines to read. If too low it will cause too much
1929 * redrawing (and possible flickering), if too high responsiveness
1931 unsigned long lines = view->height;
1932 int redraw_from = -1;
1937 /* Only redraw if lines are visible. */
1938 if (view->offset + view->height >= view->lines)
1939 redraw_from = view->lines - view->offset;
1941 /* FIXME: This is probably not perfect for backgrounded views. */
1942 if (!realloc_lines(view, view->lines + lines))
1945 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1946 size_t linelen = strlen(line);
1949 line[linelen - 1] = 0;
1951 if (opt_iconv != ICONV_NONE) {
1952 ICONV_CONST char *inbuf = line;
1953 size_t inlen = linelen;
1955 char *outbuf = out_buffer;
1956 size_t outlen = sizeof(out_buffer);
1960 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1961 if (ret != (size_t) -1) {
1963 linelen = strlen(out_buffer);
1967 if (!view->ops->read(view, line))
1977 lines = view->lines;
1978 for (digits = 0; lines; digits++)
1981 /* Keep the displayed view in sync with line number scaling. */
1982 if (digits != view->digits) {
1983 view->digits = digits;
1988 if (!view_is_displayed(view))
1991 if (view == VIEW(REQ_VIEW_TREE)) {
1992 /* Clear the view and redraw everything since the tree sorting
1993 * might have rearranged things. */
1996 } else if (redraw_from >= 0) {
1997 /* If this is an incremental update, redraw the previous line
1998 * since for commits some members could have changed when
1999 * loading the main view. */
2000 if (redraw_from > 0)
2003 /* Since revision graph visualization requires knowledge
2004 * about the parent commit, it causes a further one-off
2005 * needed to be redrawn for incremental updates. */
2006 if (redraw_from > 0 && opt_rev_graph)
2009 /* Incrementally draw avoids flickering. */
2010 redraw_view_from(view, redraw_from);
2013 /* Update the title _after_ the redraw so that if the redraw picks up a
2014 * commit reference in view->ref it'll be available here. */
2015 update_view_title(view);
2018 if (ferror(view->pipe)) {
2019 report("Failed to read: %s", strerror(errno));
2022 } else if (feof(view->pipe)) {
2030 report("Allocation failure");
2033 view->ops->read(view, NULL);
2038 static struct line *
2039 add_line_data(struct view *view, void *data, enum line_type type)
2041 struct line *line = &view->line[view->lines++];
2043 memset(line, 0, sizeof(*line));
2050 static struct line *
2051 add_line_text(struct view *view, char *data, enum line_type type)
2054 data = strdup(data);
2056 return data ? add_line_data(view, data, type) : NULL;
2065 OPEN_DEFAULT = 0, /* Use default view switching. */
2066 OPEN_SPLIT = 1, /* Split current view. */
2067 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2068 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2072 open_view(struct view *prev, enum request request, enum open_flags flags)
2074 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2075 bool split = !!(flags & OPEN_SPLIT);
2076 bool reload = !!(flags & OPEN_RELOAD);
2077 struct view *view = VIEW(request);
2078 int nviews = displayed_views();
2079 struct view *base_view = display[0];
2081 if (view == prev && nviews == 1 && !reload) {
2082 report("Already in %s view", view->name);
2086 if (view->ops->open) {
2087 if (!view->ops->open(view)) {
2088 report("Failed to load %s view", view->name);
2092 } else if ((reload || strcmp(view->vid, view->id)) &&
2093 !begin_update(view)) {
2094 report("Failed to load %s view", view->name);
2103 /* Maximize the current view. */
2104 memset(display, 0, sizeof(display));
2106 display[current_view] = view;
2109 /* Resize the view when switching between split- and full-screen,
2110 * or when switching between two different full-screen views. */
2111 if (nviews != displayed_views() ||
2112 (nviews == 1 && base_view != display[0]))
2115 if (split && prev->lineno - prev->offset >= prev->height) {
2116 /* Take the title line into account. */
2117 int lines = prev->lineno - prev->offset - prev->height + 1;
2119 /* Scroll the view that was split if the current line is
2120 * outside the new limited view. */
2121 do_scroll_view(prev, lines);
2124 if (prev && view != prev) {
2125 if (split && !backgrounded) {
2126 /* "Blur" the previous view. */
2127 update_view_title(prev);
2130 view->parent = prev;
2133 if (view->pipe && view->lines == 0) {
2134 /* Clear the old view and let the incremental updating refill
2143 /* If the view is backgrounded the above calls to report()
2144 * won't redraw the view title. */
2146 update_view_title(view);
2150 open_external_viewer(const char *cmd)
2152 def_prog_mode(); /* save current tty modes */
2153 endwin(); /* restore original tty modes */
2155 fprintf(stderr, "Press Enter to continue");
2162 open_mergetool(const char *file)
2164 char cmd[SIZEOF_STR];
2165 char file_sq[SIZEOF_STR];
2167 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2168 string_format(cmd, "git mergetool %s", file_sq)) {
2169 open_external_viewer(cmd);
2174 open_editor(bool from_root, const char *file)
2176 char cmd[SIZEOF_STR];
2177 char file_sq[SIZEOF_STR];
2179 char *prefix = from_root ? opt_cdup : "";
2181 editor = getenv("GIT_EDITOR");
2182 if (!editor && *opt_editor)
2183 editor = opt_editor;
2185 editor = getenv("VISUAL");
2187 editor = getenv("EDITOR");
2191 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2192 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2193 open_external_viewer(cmd);
2198 * User request switch noodle
2202 view_driver(struct view *view, enum request request)
2206 if (request == REQ_NONE) {
2211 if (view && view->lines) {
2212 request = view->ops->request(view, request, &view->line[view->lineno]);
2213 if (request == REQ_NONE)
2220 case REQ_MOVE_PAGE_UP:
2221 case REQ_MOVE_PAGE_DOWN:
2222 case REQ_MOVE_FIRST_LINE:
2223 case REQ_MOVE_LAST_LINE:
2224 move_view(view, request);
2227 case REQ_SCROLL_LINE_DOWN:
2228 case REQ_SCROLL_LINE_UP:
2229 case REQ_SCROLL_PAGE_DOWN:
2230 case REQ_SCROLL_PAGE_UP:
2231 scroll_view(view, request);
2236 report("No file chosen, press %s to open tree view",
2237 get_key(REQ_VIEW_TREE));
2240 open_view(view, request, OPEN_DEFAULT);
2243 case REQ_VIEW_PAGER:
2244 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2245 report("No pager content, press %s to run command from prompt",
2246 get_key(REQ_PROMPT));
2249 open_view(view, request, OPEN_DEFAULT);
2252 case REQ_VIEW_STAGE:
2253 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2254 report("No stage content, press %s to open the status view and choose file",
2255 get_key(REQ_VIEW_STATUS));
2258 open_view(view, request, OPEN_DEFAULT);
2261 case REQ_VIEW_STATUS:
2262 if (opt_is_inside_work_tree == FALSE) {
2263 report("The status view requires a working tree");
2266 open_view(view, request, OPEN_DEFAULT);
2274 open_view(view, request, OPEN_DEFAULT);
2279 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2281 if ((view == VIEW(REQ_VIEW_DIFF) &&
2282 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2283 (view == VIEW(REQ_VIEW_STAGE) &&
2284 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2285 (view == VIEW(REQ_VIEW_BLOB) &&
2286 view->parent == VIEW(REQ_VIEW_TREE))) {
2289 view = view->parent;
2290 line = view->lineno;
2291 move_view(view, request);
2292 if (view_is_displayed(view))
2293 update_view_title(view);
2294 if (line != view->lineno)
2295 view->ops->request(view, REQ_ENTER,
2296 &view->line[view->lineno]);
2299 move_view(view, request);
2305 int nviews = displayed_views();
2306 int next_view = (current_view + 1) % nviews;
2308 if (next_view == current_view) {
2309 report("Only one view is displayed");
2313 current_view = next_view;
2314 /* Blur out the title of the previous view. */
2315 update_view_title(view);
2320 report("Refreshing is not yet supported for the %s view", view->name);
2323 case REQ_TOGGLE_LINENO:
2324 opt_line_number = !opt_line_number;
2328 case REQ_TOGGLE_REV_GRAPH:
2329 opt_rev_graph = !opt_rev_graph;
2334 /* Always reload^Wrerun commands from the prompt. */
2335 open_view(view, opt_request, OPEN_RELOAD);
2339 case REQ_SEARCH_BACK:
2340 search_view(view, request);
2345 find_next(view, request);
2348 case REQ_STOP_LOADING:
2349 for (i = 0; i < ARRAY_SIZE(views); i++) {
2352 report("Stopped loading the %s view", view->name),
2357 case REQ_SHOW_VERSION:
2358 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2361 case REQ_SCREEN_RESIZE:
2364 case REQ_SCREEN_REDRAW:
2369 report("Nothing to edit");
2372 case REQ_CHERRY_PICK:
2373 report("Nothing to cherry-pick");
2377 report("Nothing to enter");
2381 case REQ_VIEW_CLOSE:
2382 /* XXX: Mark closed views by letting view->parent point to the
2383 * view itself. Parents to closed view should never be
2386 view->parent->parent != view->parent) {
2387 memset(display, 0, sizeof(display));
2389 display[current_view] = view->parent;
2390 view->parent = view;
2400 /* An unknown key will show most commonly used commands. */
2401 report("Unknown key, press 'h' for help");
2414 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2416 char *text = line->data;
2417 enum line_type type = line->type;
2418 int textlen = strlen(text);
2421 wmove(view->win, lineno, 0);
2425 wchgat(view->win, -1, 0, type, NULL);
2428 attr = get_line_attr(type);
2429 wattrset(view->win, attr);
2431 if (opt_line_number || opt_tab_size < TABSIZE) {
2432 static char spaces[] = " ";
2433 int col_offset = 0, col = 0;
2435 if (opt_line_number) {
2436 unsigned long real_lineno = view->offset + lineno + 1;
2438 if (real_lineno == 1 ||
2439 (real_lineno % opt_num_interval) == 0) {
2440 wprintw(view->win, "%.*d", view->digits, real_lineno);
2443 waddnstr(view->win, spaces,
2444 MIN(view->digits, STRING_SIZE(spaces)));
2446 waddstr(view->win, ": ");
2447 col_offset = view->digits + 2;
2450 while (text && col_offset + col < view->width) {
2451 int cols_max = view->width - col_offset - col;
2455 if (*text == '\t') {
2457 assert(sizeof(spaces) > TABSIZE);
2459 cols = opt_tab_size - (col % opt_tab_size);
2462 text = strchr(text, '\t');
2463 cols = line ? text - pos : strlen(pos);
2466 waddnstr(view->win, pos, MIN(cols, cols_max));
2471 int col = 0, pos = 0;
2473 for (; pos < textlen && col < view->width; pos++, col++)
2474 if (text[pos] == '\t')
2475 col += TABSIZE - (col % TABSIZE) - 1;
2477 waddnstr(view->win, text, pos);
2484 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2486 char refbuf[SIZEOF_STR];
2490 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2493 pipe = popen(refbuf, "r");
2497 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2498 ref = chomp_string(ref);
2504 /* This is the only fatal call, since it can "corrupt" the buffer. */
2505 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2512 add_pager_refs(struct view *view, struct line *line)
2514 char buf[SIZEOF_STR];
2515 char *commit_id = line->data + STRING_SIZE("commit ");
2517 size_t bufpos = 0, refpos = 0;
2518 const char *sep = "Refs: ";
2519 bool is_tag = FALSE;
2521 assert(line->type == LINE_COMMIT);
2523 refs = get_refs(commit_id);
2525 if (view == VIEW(REQ_VIEW_DIFF))
2526 goto try_add_describe_ref;
2531 struct ref *ref = refs[refpos];
2532 char *fmt = ref->tag ? "%s[%s]" :
2533 ref->remote ? "%s<%s>" : "%s%s";
2535 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2540 } while (refs[refpos++]->next);
2542 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2543 try_add_describe_ref:
2544 /* Add <tag>-g<commit_id> "fake" reference. */
2545 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2552 if (!realloc_lines(view, view->line_size + 1))
2555 add_line_text(view, buf, LINE_PP_REFS);
2559 pager_read(struct view *view, char *data)
2566 line = add_line_text(view, data, get_line_type(data));
2570 if (line->type == LINE_COMMIT &&
2571 (view == VIEW(REQ_VIEW_DIFF) ||
2572 view == VIEW(REQ_VIEW_LOG)))
2573 add_pager_refs(view, line);
2579 pager_request(struct view *view, enum request request, struct line *line)
2583 if (request != REQ_ENTER)
2586 if (line->type == LINE_COMMIT &&
2587 (view == VIEW(REQ_VIEW_LOG) ||
2588 view == VIEW(REQ_VIEW_PAGER))) {
2589 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2593 /* Always scroll the view even if it was split. That way
2594 * you can use Enter to scroll through the log view and
2595 * split open each commit diff. */
2596 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2598 /* FIXME: A minor workaround. Scrolling the view will call report("")
2599 * but if we are scrolling a non-current view this won't properly
2600 * update the view title. */
2602 update_view_title(view);
2608 pager_grep(struct view *view, struct line *line)
2611 char *text = line->data;
2616 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2623 pager_select(struct view *view, struct line *line)
2625 if (line->type == LINE_COMMIT) {
2626 char *text = line->data + STRING_SIZE("commit ");
2628 if (view != VIEW(REQ_VIEW_PAGER))
2629 string_copy_rev(view->ref, text);
2630 string_copy_rev(ref_commit, text);
2634 static struct view_ops pager_ops = {
2650 help_open(struct view *view)
2653 int lines = ARRAY_SIZE(req_info) + 2;
2656 if (view->lines > 0)
2659 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2660 if (!req_info[i].request)
2663 view->line = calloc(lines, sizeof(*view->line));
2667 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2669 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2672 if (req_info[i].request == REQ_NONE)
2675 if (!req_info[i].request) {
2676 add_line_text(view, "", LINE_DEFAULT);
2677 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2681 key = get_key(req_info[i].request);
2683 key = "(no key defined)";
2685 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2688 add_line_text(view, buf, LINE_DEFAULT);
2694 static struct view_ops help_ops = {
2709 struct tree_stack_entry {
2710 struct tree_stack_entry *prev; /* Entry below this in the stack */
2711 unsigned long lineno; /* Line number to restore */
2712 char *name; /* Position of name in opt_path */
2715 /* The top of the path stack. */
2716 static struct tree_stack_entry *tree_stack = NULL;
2717 unsigned long tree_lineno = 0;
2720 pop_tree_stack_entry(void)
2722 struct tree_stack_entry *entry = tree_stack;
2724 tree_lineno = entry->lineno;
2726 tree_stack = entry->prev;
2731 push_tree_stack_entry(char *name, unsigned long lineno)
2733 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2734 size_t pathlen = strlen(opt_path);
2739 entry->prev = tree_stack;
2740 entry->name = opt_path + pathlen;
2743 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2744 pop_tree_stack_entry();
2748 /* Move the current line to the first tree entry. */
2750 entry->lineno = lineno;
2753 /* Parse output from git-ls-tree(1):
2755 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2756 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2757 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2758 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2761 #define SIZEOF_TREE_ATTR \
2762 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2764 #define TREE_UP_FORMAT "040000 tree %s\t.."
2767 tree_compare_entry(enum line_type type1, char *name1,
2768 enum line_type type2, char *name2)
2770 if (type1 != type2) {
2771 if (type1 == LINE_TREE_DIR)
2776 return strcmp(name1, name2);
2780 tree_read(struct view *view, char *text)
2782 size_t textlen = text ? strlen(text) : 0;
2783 char buf[SIZEOF_STR];
2785 enum line_type type;
2786 bool first_read = view->lines == 0;
2788 if (textlen <= SIZEOF_TREE_ATTR)
2791 type = text[STRING_SIZE("100644 ")] == 't'
2792 ? LINE_TREE_DIR : LINE_TREE_FILE;
2795 /* Add path info line */
2796 if (!string_format(buf, "Directory path /%s", opt_path) ||
2797 !realloc_lines(view, view->line_size + 1) ||
2798 !add_line_text(view, buf, LINE_DEFAULT))
2801 /* Insert "link" to parent directory. */
2803 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2804 !realloc_lines(view, view->line_size + 1) ||
2805 !add_line_text(view, buf, LINE_TREE_DIR))
2810 /* Strip the path part ... */
2812 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2813 size_t striplen = strlen(opt_path);
2814 char *path = text + SIZEOF_TREE_ATTR;
2816 if (pathlen > striplen)
2817 memmove(path, path + striplen,
2818 pathlen - striplen + 1);
2821 /* Skip "Directory ..." and ".." line. */
2822 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2823 struct line *line = &view->line[pos];
2824 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2825 char *path2 = text + SIZEOF_TREE_ATTR;
2826 int cmp = tree_compare_entry(line->type, path1, type, path2);
2831 text = strdup(text);
2835 if (view->lines > pos)
2836 memmove(&view->line[pos + 1], &view->line[pos],
2837 (view->lines - pos) * sizeof(*line));
2839 line = &view->line[pos];
2846 if (!add_line_text(view, text, type))
2849 if (tree_lineno > view->lineno) {
2850 view->lineno = tree_lineno;
2858 tree_request(struct view *view, enum request request, struct line *line)
2860 enum open_flags flags;
2862 if (request != REQ_ENTER)
2865 /* Cleanup the stack if the tree view is at a different tree. */
2866 while (!*opt_path && tree_stack)
2867 pop_tree_stack_entry();
2869 switch (line->type) {
2871 /* Depending on whether it is a subdir or parent (updir?) link
2872 * mangle the path buffer. */
2873 if (line == &view->line[1] && *opt_path) {
2874 pop_tree_stack_entry();
2877 char *data = line->data;
2878 char *basename = data + SIZEOF_TREE_ATTR;
2880 push_tree_stack_entry(basename, view->lineno);
2883 /* Trees and subtrees share the same ID, so they are not not
2884 * unique like blobs. */
2885 flags = OPEN_RELOAD;
2886 request = REQ_VIEW_TREE;
2889 case LINE_TREE_FILE:
2890 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2891 request = REQ_VIEW_BLOB;
2898 open_view(view, request, flags);
2899 if (request == REQ_VIEW_TREE) {
2900 view->lineno = tree_lineno;
2907 tree_select(struct view *view, struct line *line)
2909 char *text = line->data + STRING_SIZE("100644 blob ");
2911 if (line->type == LINE_TREE_FILE) {
2912 string_copy_rev(ref_blob, text);
2914 } else if (line->type != LINE_TREE_DIR) {
2918 string_copy_rev(view->ref, text);
2921 static struct view_ops tree_ops = {
2932 blob_read(struct view *view, char *line)
2934 return add_line_text(view, line, LINE_DEFAULT) != NULL;
2937 static struct view_ops blob_ops = {
2956 char rev[SIZEOF_REV];
2960 char rev[SIZEOF_REV];
2962 char name[SIZEOF_STR];
2965 static struct status stage_status;
2966 static enum line_type stage_line_type;
2968 /* Get fields from the diff line:
2969 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2972 status_get_diff(struct status *file, char *buf, size_t bufsize)
2974 char *old_mode = buf + 1;
2975 char *new_mode = buf + 8;
2976 char *old_rev = buf + 15;
2977 char *new_rev = buf + 56;
2978 char *status = buf + 97;
2980 if (bufsize != 99 ||
2981 old_mode[-1] != ':' ||
2982 new_mode[-1] != ' ' ||
2983 old_rev[-1] != ' ' ||
2984 new_rev[-1] != ' ' ||
2988 file->status = *status;
2990 string_copy_rev(file->old.rev, old_rev);
2991 string_copy_rev(file->new.rev, new_rev);
2993 file->old.mode = strtoul(old_mode, NULL, 8);
2994 file->new.mode = strtoul(new_mode, NULL, 8);
3002 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3004 struct status *file = NULL;
3005 struct status *unmerged = NULL;
3006 char buf[SIZEOF_STR * 4];
3010 pipe = popen(cmd, "r");
3014 add_line_data(view, NULL, type);
3016 while (!feof(pipe) && !ferror(pipe)) {
3020 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3023 bufsize += readsize;
3025 /* Process while we have NUL chars. */
3026 while ((sep = memchr(buf, 0, bufsize))) {
3027 size_t sepsize = sep - buf + 1;
3030 if (!realloc_lines(view, view->line_size + 1))
3033 file = calloc(1, sizeof(*file));
3037 add_line_data(view, file, type);
3040 /* Parse diff info part. */
3044 } else if (!file->status) {
3045 if (!status_get_diff(file, buf, sepsize))
3049 memmove(buf, sep + 1, bufsize);
3051 sep = memchr(buf, 0, bufsize);
3054 sepsize = sep - buf + 1;
3056 /* Collapse all 'M'odified entries that
3057 * follow a associated 'U'nmerged entry.
3059 if (file->status == 'U') {
3062 } else if (unmerged) {
3063 int collapse = !strcmp(buf, unmerged->name);
3074 /* git-ls-files just delivers a NUL separated
3075 * list of file names similar to the second half
3076 * of the git-diff-* output. */
3077 string_ncopy(file->name, buf, sepsize);
3079 memmove(buf, sep + 1, bufsize);
3090 if (!view->line[view->lines - 1].data)
3091 add_line_data(view, NULL, LINE_STAT_NONE);
3097 /* Don't show unmerged entries in the staged section. */
3098 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3099 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3100 #define STATUS_LIST_OTHER_CMD \
3101 "git ls-files -z --others --exclude-per-directory=.gitignore"
3103 #define STATUS_DIFF_SHOW_CMD \
3104 "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3106 /* First parse staged info using git-diff-index(1), then parse unstaged
3107 * info using git-diff-files(1), and finally untracked files using
3108 * git-ls-files(1). */
3110 status_open(struct view *view)
3112 struct stat statbuf;
3113 char exclude[SIZEOF_STR];
3114 char cmd[SIZEOF_STR];
3115 unsigned long prev_lineno = view->lineno;
3118 for (i = 0; i < view->lines; i++)
3119 free(view->line[i].data);
3121 view->lines = view->line_size = view->lineno = 0;
3124 if (!realloc_lines(view, view->line_size + 6))
3127 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3130 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3132 if (stat(exclude, &statbuf) >= 0) {
3133 size_t cmdsize = strlen(cmd);
3135 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3136 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3140 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3141 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3142 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3145 /* If all went well restore the previous line number to stay in
3147 if (prev_lineno < view->lines)
3148 view->lineno = prev_lineno;
3150 view->lineno = view->lines - 1;
3156 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3158 struct status *status = line->data;
3160 wmove(view->win, lineno, 0);
3163 wattrset(view->win, get_line_attr(LINE_CURSOR));
3164 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3166 } else if (!status && line->type != LINE_STAT_NONE) {
3167 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3168 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3171 wattrset(view->win, get_line_attr(line->type));
3177 switch (line->type) {
3178 case LINE_STAT_STAGED:
3179 text = "Changes to be committed:";
3182 case LINE_STAT_UNSTAGED:
3183 text = "Changed but not updated:";
3186 case LINE_STAT_UNTRACKED:
3187 text = "Untracked files:";
3190 case LINE_STAT_NONE:
3191 text = " (no files)";
3198 waddstr(view->win, text);
3202 waddch(view->win, status->status);
3204 wattrset(view->win, A_NORMAL);
3205 wmove(view->win, lineno, 4);
3206 waddstr(view->win, status->name);
3212 status_enter(struct view *view, struct line *line)
3214 struct status *status = line->data;
3215 char path[SIZEOF_STR] = "";
3219 if (line->type == LINE_STAT_NONE ||
3220 (!status && line[1].type == LINE_STAT_NONE)) {
3221 report("No file to diff");
3225 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3229 line->type != LINE_STAT_UNTRACKED &&
3230 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3233 switch (line->type) {
3234 case LINE_STAT_STAGED:
3235 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3239 info = "Staged changes to %s";
3241 info = "Staged changes";
3244 case LINE_STAT_UNSTAGED:
3245 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3249 info = "Unstaged changes to %s";
3251 info = "Unstaged changes";
3254 case LINE_STAT_UNTRACKED:
3260 report("No file to show");
3264 opt_pipe = fopen(status->name, "r");
3265 info = "Untracked file %s";
3272 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3273 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3275 stage_status = *status;
3277 memset(&stage_status, 0, sizeof(stage_status));
3280 stage_line_type = line->type;
3281 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3289 status_update_file(struct view *view, struct status *status, enum line_type type)
3291 char cmd[SIZEOF_STR];
3292 char buf[SIZEOF_STR];
3299 type != LINE_STAT_UNTRACKED &&
3300 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3304 case LINE_STAT_STAGED:
3305 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3311 string_add(cmd, cmdsize, "git update-index -z --index-info");
3314 case LINE_STAT_UNSTAGED:
3315 case LINE_STAT_UNTRACKED:
3316 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3319 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3326 pipe = popen(cmd, "w");
3330 while (!ferror(pipe) && written < bufsize) {
3331 written += fwrite(buf + written, 1, bufsize - written, pipe);
3336 if (written != bufsize)
3343 status_update(struct view *view)
3345 struct line *line = &view->line[view->lineno];
3347 assert(view->lines);
3350 while (++line < view->line + view->lines && line->data) {
3351 if (!status_update_file(view, line->data, line->type))
3352 report("Failed to update file status");
3355 if (!line[-1].data) {
3356 report("Nothing to update");
3360 } else if (!status_update_file(view, line->data, line->type)) {
3361 report("Failed to update file status");
3366 status_request(struct view *view, enum request request, struct line *line)
3368 struct status *status = line->data;
3371 case REQ_STATUS_UPDATE:
3372 status_update(view);
3375 case REQ_STATUS_MERGE:
3376 open_mergetool(status->name);
3383 open_editor(status->status != '?', status->name);
3387 /* After returning the status view has been split to
3388 * show the stage view. No further reloading is
3390 status_enter(view, line);
3394 /* Simply reload the view. */
3401 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3407 status_select(struct view *view, struct line *line)
3409 struct status *status = line->data;
3410 char file[SIZEOF_STR] = "all files";
3414 if (status && !string_format(file, "'%s'", status->name))
3417 if (!status && line[1].type == LINE_STAT_NONE)
3420 switch (line->type) {
3421 case LINE_STAT_STAGED:
3422 text = "Press %s to unstage %s for commit";
3425 case LINE_STAT_UNSTAGED:
3426 text = "Press %s to stage %s for commit";
3429 case LINE_STAT_UNTRACKED:
3430 text = "Press %s to stage %s for addition";
3433 case LINE_STAT_NONE:
3434 text = "Nothing to update";
3441 if (status && status->status == 'U') {
3442 text = "Press %s to resolve conflict in %s";
3443 key = get_key(REQ_STATUS_MERGE);
3446 key = get_key(REQ_STATUS_UPDATE);
3449 string_format(view->ref, text, key, file);
3453 status_grep(struct view *view, struct line *line)
3455 struct status *status = line->data;
3456 enum { S_STATUS, S_NAME, S_END } state;
3463 for (state = S_STATUS; state < S_END; state++) {
3467 case S_NAME: text = status->name; break;
3469 buf[0] = status->status;
3477 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3484 static struct view_ops status_ops = {
3496 stage_diff_line(FILE *pipe, struct line *line)
3498 char *buf = line->data;
3499 size_t bufsize = strlen(buf);
3502 while (!ferror(pipe) && written < bufsize) {
3503 written += fwrite(buf + written, 1, bufsize - written, pipe);
3508 return written == bufsize;
3511 static struct line *
3512 stage_diff_hdr(struct view *view, struct line *line)
3514 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3515 struct line *diff_hdr;
3517 if (line->type == LINE_DIFF_CHUNK)
3518 diff_hdr = line - 1;
3520 diff_hdr = view->line + 1;
3522 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3523 if (diff_hdr->type == LINE_DIFF_HEADER)
3526 diff_hdr += diff_hdr_dir;
3533 stage_update_chunk(struct view *view, struct line *line)
3535 char cmd[SIZEOF_STR];
3537 struct line *diff_hdr, *diff_chunk, *diff_end;
3540 diff_hdr = stage_diff_hdr(view, line);
3545 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3548 if (!string_format_from(cmd, &cmdsize,
3549 "git apply --cached %s - && "
3550 "git update-index -q --unmerged --refresh 2>/dev/null",
3551 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3554 pipe = popen(cmd, "w");
3558 diff_end = view->line + view->lines;
3559 if (line->type != LINE_DIFF_CHUNK) {
3560 diff_chunk = diff_hdr;
3563 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3564 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3565 diff_chunk->type == LINE_DIFF_HEADER)
3566 diff_end = diff_chunk;
3570 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3571 switch (diff_hdr->type) {
3572 case LINE_DIFF_HEADER:
3573 case LINE_DIFF_INDEX:
3583 if (!stage_diff_line(pipe, diff_hdr++)) {
3590 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3595 if (diff_chunk != diff_end)
3602 stage_update(struct view *view, struct line *line)
3604 if (stage_line_type != LINE_STAT_UNTRACKED &&
3605 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3606 if (!stage_update_chunk(view, line)) {
3607 report("Failed to apply chunk");
3611 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3612 report("Failed to update file");
3616 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3618 view = VIEW(REQ_VIEW_STATUS);
3619 if (view_is_displayed(view))
3620 status_enter(view, &view->line[view->lineno]);
3624 stage_request(struct view *view, enum request request, struct line *line)
3627 case REQ_STATUS_UPDATE:
3628 stage_update(view, line);
3632 if (!stage_status.name[0])
3635 open_editor(stage_status.status != '?', stage_status.name);
3639 pager_request(view, request, line);
3649 static struct view_ops stage_ops = {
3665 char id[SIZEOF_REV]; /* SHA1 ID. */
3666 char title[128]; /* First line of the commit message. */
3667 char author[75]; /* Author of the commit. */
3668 struct tm time; /* Date from the author ident. */
3669 struct ref **refs; /* Repository references. */
3670 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3671 size_t graph_size; /* The width of the graph array. */
3674 /* Size of rev graph with no "padding" columns */
3675 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3678 struct rev_graph *prev, *next, *parents;
3679 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3681 struct commit *commit;
3685 /* Parents of the commit being visualized. */
3686 static struct rev_graph graph_parents[4];
3688 /* The current stack of revisions on the graph. */
3689 static struct rev_graph graph_stacks[4] = {
3690 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3691 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3692 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3693 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3697 graph_parent_is_merge(struct rev_graph *graph)
3699 return graph->parents->size > 1;
3703 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3705 struct commit *commit = graph->commit;
3707 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3708 commit->graph[commit->graph_size++] = symbol;
3712 done_rev_graph(struct rev_graph *graph)
3714 if (graph_parent_is_merge(graph) &&
3715 graph->pos < graph->size - 1 &&
3716 graph->next->size == graph->size + graph->parents->size - 1) {
3717 size_t i = graph->pos + graph->parents->size - 1;
3719 graph->commit->graph_size = i * 2;
3720 while (i < graph->next->size - 1) {
3721 append_to_rev_graph(graph, ' ');
3722 append_to_rev_graph(graph, '\\');
3727 graph->size = graph->pos = 0;
3728 graph->commit = NULL;
3729 memset(graph->parents, 0, sizeof(*graph->parents));
3733 push_rev_graph(struct rev_graph *graph, char *parent)
3737 /* "Collapse" duplicate parents lines.
3739 * FIXME: This needs to also update update the drawn graph but
3740 * for now it just serves as a method for pruning graph lines. */
3741 for (i = 0; i < graph->size; i++)
3742 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3745 if (graph->size < SIZEOF_REVITEMS) {
3746 string_copy_rev(graph->rev[graph->size++], parent);
3751 get_rev_graph_symbol(struct rev_graph *graph)
3755 if (graph->parents->size == 0)
3756 symbol = REVGRAPH_INIT;
3757 else if (graph_parent_is_merge(graph))
3758 symbol = REVGRAPH_MERGE;
3759 else if (graph->pos >= graph->size)
3760 symbol = REVGRAPH_BRANCH;
3762 symbol = REVGRAPH_COMMIT;
3768 draw_rev_graph(struct rev_graph *graph)
3771 chtype separator, line;
3773 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3774 static struct rev_filler fillers[] = {
3775 { ' ', REVGRAPH_LINE },
3780 chtype symbol = get_rev_graph_symbol(graph);
3781 struct rev_filler *filler;
3784 filler = &fillers[DEFAULT];
3786 for (i = 0; i < graph->pos; i++) {
3787 append_to_rev_graph(graph, filler->line);
3788 if (graph_parent_is_merge(graph->prev) &&
3789 graph->prev->pos == i)
3790 filler = &fillers[RSHARP];
3792 append_to_rev_graph(graph, filler->separator);
3795 /* Place the symbol for this revision. */
3796 append_to_rev_graph(graph, symbol);
3798 if (graph->prev->size > graph->size)
3799 filler = &fillers[RDIAG];
3801 filler = &fillers[DEFAULT];
3805 for (; i < graph->size; i++) {
3806 append_to_rev_graph(graph, filler->separator);
3807 append_to_rev_graph(graph, filler->line);
3808 if (graph_parent_is_merge(graph->prev) &&
3809 i < graph->prev->pos + graph->parents->size)
3810 filler = &fillers[RSHARP];
3811 if (graph->prev->size > graph->size)
3812 filler = &fillers[LDIAG];
3815 if (graph->prev->size > graph->size) {
3816 append_to_rev_graph(graph, filler->separator);
3817 if (filler->line != ' ')
3818 append_to_rev_graph(graph, filler->line);
3822 /* Prepare the next rev graph */
3824 prepare_rev_graph(struct rev_graph *graph)
3828 /* First, traverse all lines of revisions up to the active one. */
3829 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3830 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3833 push_rev_graph(graph->next, graph->rev[graph->pos]);
3836 /* Interleave the new revision parent(s). */
3837 for (i = 0; i < graph->parents->size; i++)
3838 push_rev_graph(graph->next, graph->parents->rev[i]);
3840 /* Lastly, put any remaining revisions. */
3841 for (i = graph->pos + 1; i < graph->size; i++)
3842 push_rev_graph(graph->next, graph->rev[i]);
3846 update_rev_graph(struct rev_graph *graph)
3848 /* If this is the finalizing update ... */
3850 prepare_rev_graph(graph);
3852 /* Graph visualization needs a one rev look-ahead,
3853 * so the first update doesn't visualize anything. */
3854 if (!graph->prev->commit)
3857 draw_rev_graph(graph->prev);
3858 done_rev_graph(graph->prev->prev);
3867 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3869 char buf[DATE_COLS + 1];
3870 struct commit *commit = line->data;
3871 enum line_type type;
3877 if (!*commit->author)
3880 wmove(view->win, lineno, col);
3884 wattrset(view->win, get_line_attr(type));
3885 wchgat(view->win, -1, 0, type, NULL);
3888 type = LINE_MAIN_COMMIT;
3889 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3892 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3893 waddnstr(view->win, buf, timelen);
3894 waddstr(view->win, " ");
3897 wmove(view->win, lineno, col);
3898 if (type != LINE_CURSOR)
3899 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3902 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3904 authorlen = strlen(commit->author);
3905 if (authorlen > AUTHOR_COLS - 2) {
3906 authorlen = AUTHOR_COLS - 2;
3912 waddnstr(view->win, commit->author, authorlen);
3913 if (type != LINE_CURSOR)
3914 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3915 waddch(view->win, '~');
3917 waddstr(view->win, commit->author);
3921 if (type != LINE_CURSOR)
3922 wattrset(view->win, A_NORMAL);
3924 if (opt_rev_graph && commit->graph_size) {
3927 wmove(view->win, lineno, col);
3928 /* Using waddch() instead of waddnstr() ensures that
3929 * they'll be rendered correctly for the cursor line. */
3930 for (i = 0; i < commit->graph_size; i++)
3931 waddch(view->win, commit->graph[i]);
3933 waddch(view->win, ' ');
3934 col += commit->graph_size + 1;
3937 wmove(view->win, lineno, col);
3943 if (type == LINE_CURSOR)
3945 else if (commit->refs[i]->tag)
3946 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3947 else if (commit->refs[i]->remote)
3948 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3950 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3951 waddstr(view->win, "[");
3952 waddstr(view->win, commit->refs[i]->name);
3953 waddstr(view->win, "]");
3954 if (type != LINE_CURSOR)
3955 wattrset(view->win, A_NORMAL);
3956 waddstr(view->win, " ");
3957 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3958 } while (commit->refs[i++]->next);
3961 if (type != LINE_CURSOR)
3962 wattrset(view->win, get_line_attr(type));
3965 int titlelen = strlen(commit->title);
3967 if (col + titlelen > view->width)
3968 titlelen = view->width - col;
3970 waddnstr(view->win, commit->title, titlelen);
3976 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3978 main_read(struct view *view, char *line)
3980 static struct rev_graph *graph = graph_stacks;
3981 enum line_type type;
3982 struct commit *commit;
3985 update_rev_graph(graph);
3989 type = get_line_type(line);
3990 if (type == LINE_COMMIT) {
3991 commit = calloc(1, sizeof(struct commit));
3995 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3996 commit->refs = get_refs(commit->id);
3997 graph->commit = commit;
3998 add_line_data(view, commit, LINE_MAIN_COMMIT);
4004 commit = view->line[view->lines - 1].data;
4008 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4013 /* Parse author lines where the name may be empty:
4014 * author <email@address.tld> 1138474660 +0100
4016 char *ident = line + STRING_SIZE("author ");
4017 char *nameend = strchr(ident, '<');
4018 char *emailend = strchr(ident, '>');
4020 if (!nameend || !emailend)
4023 update_rev_graph(graph);
4024 graph = graph->next;
4026 *nameend = *emailend = 0;
4027 ident = chomp_string(ident);
4029 ident = chomp_string(nameend + 1);
4034 string_ncopy(commit->author, ident, strlen(ident));
4036 /* Parse epoch and timezone */
4037 if (emailend[1] == ' ') {
4038 char *secs = emailend + 2;
4039 char *zone = strchr(secs, ' ');
4040 time_t time = (time_t) atol(secs);
4042 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4046 tz = ('0' - zone[1]) * 60 * 60 * 10;
4047 tz += ('0' - zone[2]) * 60 * 60;
4048 tz += ('0' - zone[3]) * 60;
4049 tz += ('0' - zone[4]) * 60;
4057 gmtime_r(&time, &commit->time);
4062 /* Fill in the commit title if it has not already been set. */
4063 if (commit->title[0])
4066 /* Require titles to start with a non-space character at the
4067 * offset used by git log. */
4068 if (strncmp(line, " ", 4))
4071 /* Well, if the title starts with a whitespace character,
4072 * try to be forgiving. Otherwise we end up with no title. */
4073 while (isspace(*line))
4077 /* FIXME: More graceful handling of titles; append "..." to
4078 * shortened titles, etc. */
4080 string_ncopy(commit->title, line, strlen(line));
4087 cherry_pick_commit(struct commit *commit)
4089 char cmd[SIZEOF_STR];
4090 char *cherry_pick = getenv("TIG_CHERRY_PICK");
4093 cherry_pick = "git cherry-pick";
4095 if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4096 open_external_viewer(cmd);
4101 main_request(struct view *view, enum request request, struct line *line)
4103 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4105 if (request == REQ_ENTER)
4106 open_view(view, REQ_VIEW_DIFF, flags);
4107 else if (request == REQ_CHERRY_PICK)
4108 cherry_pick_commit(line->data);
4116 main_grep(struct view *view, struct line *line)
4118 struct commit *commit = line->data;
4119 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4120 char buf[DATE_COLS + 1];
4123 for (state = S_TITLE; state < S_END; state++) {
4127 case S_TITLE: text = commit->title; break;
4128 case S_AUTHOR: text = commit->author; break;
4130 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4139 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4147 main_select(struct view *view, struct line *line)
4149 struct commit *commit = line->data;
4151 string_copy_rev(view->ref, commit->id);
4152 string_copy_rev(ref_commit, view->ref);
4155 static struct view_ops main_ops = {
4167 * Unicode / UTF-8 handling
4169 * NOTE: Much of the following code for dealing with unicode is derived from
4170 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4171 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4174 /* I've (over)annotated a lot of code snippets because I am not entirely
4175 * confident that the approach taken by this small UTF-8 interface is correct.
4179 unicode_width(unsigned long c)
4182 (c <= 0x115f /* Hangul Jamo */
4185 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4187 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4188 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4189 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4190 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4191 || (c >= 0xffe0 && c <= 0xffe6)
4192 || (c >= 0x20000 && c <= 0x2fffd)
4193 || (c >= 0x30000 && c <= 0x3fffd)))
4199 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4200 * Illegal bytes are set one. */
4201 static const unsigned char utf8_bytes[256] = {
4202 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,
4203 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,
4204 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,
4205 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,
4206 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,
4207 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,
4208 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,
4209 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,
4212 /* Decode UTF-8 multi-byte representation into a unicode character. */
4213 static inline unsigned long
4214 utf8_to_unicode(const char *string, size_t length)
4216 unsigned long unicode;
4220 unicode = string[0];
4223 unicode = (string[0] & 0x1f) << 6;
4224 unicode += (string[1] & 0x3f);
4227 unicode = (string[0] & 0x0f) << 12;
4228 unicode += ((string[1] & 0x3f) << 6);
4229 unicode += (string[2] & 0x3f);
4232 unicode = (string[0] & 0x0f) << 18;
4233 unicode += ((string[1] & 0x3f) << 12);
4234 unicode += ((string[2] & 0x3f) << 6);
4235 unicode += (string[3] & 0x3f);
4238 unicode = (string[0] & 0x0f) << 24;
4239 unicode += ((string[1] & 0x3f) << 18);
4240 unicode += ((string[2] & 0x3f) << 12);
4241 unicode += ((string[3] & 0x3f) << 6);
4242 unicode += (string[4] & 0x3f);
4245 unicode = (string[0] & 0x01) << 30;
4246 unicode += ((string[1] & 0x3f) << 24);
4247 unicode += ((string[2] & 0x3f) << 18);
4248 unicode += ((string[3] & 0x3f) << 12);
4249 unicode += ((string[4] & 0x3f) << 6);
4250 unicode += (string[5] & 0x3f);
4253 die("Invalid unicode length");
4256 /* Invalid characters could return the special 0xfffd value but NUL
4257 * should be just as good. */
4258 return unicode > 0xffff ? 0 : unicode;
4261 /* Calculates how much of string can be shown within the given maximum width
4262 * and sets trimmed parameter to non-zero value if all of string could not be
4265 * Additionally, adds to coloffset how many many columns to move to align with
4266 * the expected position. Takes into account how multi-byte and double-width
4267 * characters will effect the cursor position.
4269 * Returns the number of bytes to output from string to satisfy max_width. */
4271 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4273 const char *start = string;
4274 const char *end = strchr(string, '\0');
4280 while (string < end) {
4281 int c = *(unsigned char *) string;
4282 unsigned char bytes = utf8_bytes[c];
4284 unsigned long unicode;
4286 if (string + bytes > end)
4289 /* Change representation to figure out whether
4290 * it is a single- or double-width character. */
4292 unicode = utf8_to_unicode(string, bytes);
4293 /* FIXME: Graceful handling of invalid unicode character. */
4297 ucwidth = unicode_width(unicode);
4299 if (width > max_width) {
4304 /* The column offset collects the differences between the
4305 * number of bytes encoding a character and the number of
4306 * columns will be used for rendering said character.
4308 * So if some character A is encoded in 2 bytes, but will be
4309 * represented on the screen using only 1 byte this will and up
4310 * adding 1 to the multi-byte column offset.
4312 * Assumes that no double-width character can be encoding in
4313 * less than two bytes. */
4314 if (bytes > ucwidth)
4315 mbwidth += bytes - ucwidth;
4320 *coloffset += mbwidth;
4322 return string - start;
4330 /* Whether or not the curses interface has been initialized. */
4331 static bool cursed = FALSE;
4333 /* The status window is used for polling keystrokes. */
4334 static WINDOW *status_win;
4336 static bool status_empty = TRUE;
4338 /* Update status and title window. */
4340 report(const char *msg, ...)
4342 struct view *view = display[current_view];
4348 char buf[SIZEOF_STR];
4351 va_start(args, msg);
4352 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4353 buf[sizeof(buf) - 1] = 0;
4354 buf[sizeof(buf) - 2] = '.';
4355 buf[sizeof(buf) - 3] = '.';
4356 buf[sizeof(buf) - 4] = '.';
4362 if (!status_empty || *msg) {
4365 va_start(args, msg);
4367 wmove(status_win, 0, 0);
4369 vwprintw(status_win, msg, args);
4370 status_empty = FALSE;
4372 status_empty = TRUE;
4374 wclrtoeol(status_win);
4375 wrefresh(status_win);
4380 update_view_title(view);
4381 update_display_cursor(view);
4384 /* Controls when nodelay should be in effect when polling user input. */
4386 set_nonblocking_input(bool loading)
4388 static unsigned int loading_views;
4390 if ((loading == FALSE && loading_views-- == 1) ||
4391 (loading == TRUE && loading_views++ == 0))
4392 nodelay(status_win, loading);
4400 /* Initialize the curses library */
4401 if (isatty(STDIN_FILENO)) {
4402 cursed = !!initscr();
4404 /* Leave stdin and stdout alone when acting as a pager. */
4405 FILE *io = fopen("/dev/tty", "r+");
4408 die("Failed to open /dev/tty");
4409 cursed = !!newterm(NULL, io, io);
4413 die("Failed to initialize curses");
4415 nonl(); /* Tell curses not to do NL->CR/NL on output */
4416 cbreak(); /* Take input chars one at a time, no wait for \n */
4417 noecho(); /* Don't echo input */
4418 leaveok(stdscr, TRUE);
4423 getmaxyx(stdscr, y, x);
4424 status_win = newwin(1, 0, y - 1, 0);
4426 die("Failed to create status window");
4428 /* Enable keyboard mapping */
4429 keypad(status_win, TRUE);
4430 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4434 read_prompt(const char *prompt)
4436 enum { READING, STOP, CANCEL } status = READING;
4437 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4440 while (status == READING) {
4446 foreach_view (view, i)
4451 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4452 wclrtoeol(status_win);
4454 /* Refresh, accept single keystroke of input */
4455 key = wgetch(status_win);
4460 status = pos ? STOP : CANCEL;
4478 if (pos >= sizeof(buf)) {
4479 report("Input string too long");
4484 buf[pos++] = (char) key;
4488 /* Clear the status window */
4489 status_empty = FALSE;
4492 if (status == CANCEL)
4501 * Repository references
4504 static struct ref *refs;
4505 static size_t refs_size;
4507 /* Id <-> ref store */
4508 static struct ref ***id_refs;
4509 static size_t id_refs_size;
4511 static struct ref **
4514 struct ref ***tmp_id_refs;
4515 struct ref **ref_list = NULL;
4516 size_t ref_list_size = 0;
4519 for (i = 0; i < id_refs_size; i++)
4520 if (!strcmp(id, id_refs[i][0]->id))
4523 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4527 id_refs = tmp_id_refs;
4529 for (i = 0; i < refs_size; i++) {
4532 if (strcmp(id, refs[i].id))
4535 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4543 if (ref_list_size > 0)
4544 ref_list[ref_list_size - 1]->next = 1;
4545 ref_list[ref_list_size] = &refs[i];
4547 /* XXX: The properties of the commit chains ensures that we can
4548 * safely modify the shared ref. The repo references will
4549 * always be similar for the same id. */
4550 ref_list[ref_list_size]->next = 0;
4555 id_refs[id_refs_size++] = ref_list;
4561 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4565 bool remote = FALSE;
4567 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4568 /* Commits referenced by tags has "^{}" appended. */
4569 if (name[namelen - 1] != '}')
4572 while (namelen > 0 && name[namelen] != '^')
4576 namelen -= STRING_SIZE("refs/tags/");
4577 name += STRING_SIZE("refs/tags/");
4579 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4581 namelen -= STRING_SIZE("refs/remotes/");
4582 name += STRING_SIZE("refs/remotes/");
4584 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4585 namelen -= STRING_SIZE("refs/heads/");
4586 name += STRING_SIZE("refs/heads/");
4588 } else if (!strcmp(name, "HEAD")) {
4592 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4596 ref = &refs[refs_size++];
4597 ref->name = malloc(namelen + 1);
4601 strncpy(ref->name, name, namelen);
4602 ref->name[namelen] = 0;
4604 ref->remote = remote;
4605 string_copy_rev(ref->id, id);
4613 const char *cmd_env = getenv("TIG_LS_REMOTE");
4614 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4616 return read_properties(popen(cmd, "r"), "\t", read_ref);
4620 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4622 if (!strcmp(name, "i18n.commitencoding"))
4623 string_ncopy(opt_encoding, value, valuelen);
4625 if (!strcmp(name, "core.editor"))
4626 string_ncopy(opt_editor, value, valuelen);
4632 load_repo_config(void)
4634 return read_properties(popen(GIT_CONFIG " --list", "r"),
4635 "=", read_repo_config_option);
4639 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4641 if (!opt_git_dir[0]) {
4642 string_ncopy(opt_git_dir, name, namelen);
4644 } else if (opt_is_inside_work_tree == -1) {
4645 /* This can be 3 different values depending on the
4646 * version of git being used. If git-rev-parse does not
4647 * understand --is-inside-work-tree it will simply echo
4648 * the option else either "true" or "false" is printed.
4649 * Default to true for the unknown case. */
4650 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4653 string_ncopy(opt_cdup, name, namelen);
4659 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4660 * must be the last one! */
4662 load_repo_info(void)
4664 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4665 "=", read_repo_info);
4669 read_properties(FILE *pipe, const char *separators,
4670 int (*read_property)(char *, size_t, char *, size_t))
4672 char buffer[BUFSIZ];
4679 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4684 name = chomp_string(name);
4685 namelen = strcspn(name, separators);
4687 if (name[namelen]) {
4689 value = chomp_string(name + namelen + 1);
4690 valuelen = strlen(value);
4697 state = read_property(name, namelen, value, valuelen);
4700 if (state != ERR && ferror(pipe))
4713 static void __NORETURN
4716 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4722 static void __NORETURN
4723 die(const char *err, ...)
4729 va_start(args, err);
4730 fputs("tig: ", stderr);
4731 vfprintf(stderr, err, args);
4732 fputs("\n", stderr);
4739 main(int argc, char *argv[])
4742 enum request request;
4745 signal(SIGINT, quit);
4747 if (setlocale(LC_ALL, "")) {
4748 char *codeset = nl_langinfo(CODESET);
4750 string_ncopy(opt_codeset, codeset, strlen(codeset));
4753 if (load_repo_info() == ERR)
4754 die("Failed to load repo info.");
4756 if (load_options() == ERR)
4757 die("Failed to load user config.");
4759 /* Load the repo config file so options can be overwritten from
4760 * the command line. */
4761 if (load_repo_config() == ERR)
4762 die("Failed to load repo config.");
4764 if (!parse_options(argc, argv))
4767 /* Require a git repository unless when running in pager mode. */
4768 if (!opt_git_dir[0])
4769 die("Not a git repository");
4771 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4772 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4773 if (opt_iconv == ICONV_NONE)
4774 die("Failed to initialize character set conversion");
4777 if (load_refs() == ERR)
4778 die("Failed to load refs.");
4780 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4781 view->cmd_env = getenv(view->cmd_env);
4783 request = opt_request;
4787 while (view_driver(display[current_view], request)) {
4791 foreach_view (view, i)
4794 /* Refresh, accept single keystroke of input */
4795 key = wgetch(status_win);
4797 /* wgetch() with nodelay() enabled returns ERR when there's no
4804 request = get_keybinding(display[current_view]->keymap, key);
4806 /* Some low-level request handling. This keeps access to
4807 * status_win restricted. */
4811 char *cmd = read_prompt(":");
4813 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4814 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4815 opt_request = REQ_VIEW_DIFF;
4817 opt_request = REQ_VIEW_PAGER;
4826 case REQ_SEARCH_BACK:
4828 const char *prompt = request == REQ_SEARCH
4830 char *search = read_prompt(prompt);
4833 string_ncopy(opt_search, search, strlen(search));
4838 case REQ_SCREEN_RESIZE:
4842 getmaxyx(stdscr, height, width);
4844 /* Resize the status view and let the view driver take
4845 * care of resizing the displayed views. */
4846 wresize(status_win, 1, width);
4847 mvwin(status_win, height - 1, 0);
4848 wrefresh(status_win);