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_(NONE, "Do nothing"), \
347 REQ_(PROMPT, "Bring up the prompt"), \
348 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
349 REQ_(SCREEN_RESIZE, "Resize the screen"), \
350 REQ_(SHOW_VERSION, "Show version information"), \
351 REQ_(STOP_LOADING, "Stop all loading views"), \
352 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
353 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
354 REQ_(STATUS_UPDATE, "Update file status"), \
355 REQ_(EDIT, "Open in editor"), \
356 REQ_(CHERRY_PICK, "Cherry-pick commit to current branch")
359 /* User action requests. */
361 #define REQ_GROUP(help)
362 #define REQ_(req, help) REQ_##req
364 /* Offset all requests to avoid conflicts with ncurses getch values. */
365 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 },
791 { 'C', REQ_CHERRY_PICK },
793 /* Using the ncurses SIGWINCH handler. */
794 { KEY_RESIZE, REQ_SCREEN_RESIZE },
797 #define KEYMAP_INFO \
810 #define KEYMAP_(name) KEYMAP_##name
815 static struct int_map keymap_table[] = {
816 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
821 #define set_keymap(map, name) \
822 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
824 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
827 add_keybinding(enum keymap keymap, enum request request, int key)
829 struct keybinding *keybinding;
831 keybinding = calloc(1, sizeof(*keybinding));
833 die("Failed to allocate keybinding");
835 keybinding->alias = key;
836 keybinding->request = request;
837 keybinding->next = keybindings[keymap];
838 keybindings[keymap] = keybinding;
841 /* Looks for a key binding first in the given map, then in the generic map, and
842 * lastly in the default keybindings. */
844 get_keybinding(enum keymap keymap, int key)
846 struct keybinding *kbd;
849 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
850 if (kbd->alias == key)
853 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
854 if (kbd->alias == key)
857 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
858 if (default_keybindings[i].alias == key)
859 return default_keybindings[i].request;
861 return (enum request) key;
870 static struct key key_table[] = {
871 { "Enter", KEY_RETURN },
873 { "Backspace", KEY_BACKSPACE },
875 { "Escape", KEY_ESC },
876 { "Left", KEY_LEFT },
877 { "Right", KEY_RIGHT },
879 { "Down", KEY_DOWN },
880 { "Insert", KEY_IC },
881 { "Delete", KEY_DC },
883 { "Home", KEY_HOME },
885 { "PageUp", KEY_PPAGE },
886 { "PageDown", KEY_NPAGE },
896 { "F10", KEY_F(10) },
897 { "F11", KEY_F(11) },
898 { "F12", KEY_F(12) },
902 get_key_value(const char *name)
906 for (i = 0; i < ARRAY_SIZE(key_table); i++)
907 if (!strcasecmp(key_table[i].name, name))
908 return key_table[i].value;
910 if (strlen(name) == 1 && isprint(*name))
917 get_key(enum request request)
919 static char buf[BUFSIZ];
920 static char key_char[] = "'X'";
927 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
928 struct keybinding *keybinding = &default_keybindings[i];
932 if (keybinding->request != request)
935 for (key = 0; key < ARRAY_SIZE(key_table); key++)
936 if (key_table[key].value == keybinding->alias)
937 seq = key_table[key].name;
940 keybinding->alias < 127 &&
941 isprint(keybinding->alias)) {
942 key_char[1] = (char) keybinding->alias;
949 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
950 return "Too many keybindings!";
959 * User config file handling.
962 static struct int_map color_map[] = {
963 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
975 #define set_color(color, name) \
976 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
978 static struct int_map attr_map[] = {
979 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
989 #define set_attribute(attr, name) \
990 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
992 static int config_lineno;
993 static bool config_errors;
994 static char *config_msg;
996 /* Wants: object fgcolor bgcolor [attr] */
998 option_color_command(int argc, char *argv[])
1000 struct line_info *info;
1002 if (argc != 3 && argc != 4) {
1003 config_msg = "Wrong number of arguments given to color command";
1007 info = get_line_info(argv[0], strlen(argv[0]));
1009 config_msg = "Unknown color name";
1013 if (set_color(&info->fg, argv[1]) == ERR ||
1014 set_color(&info->bg, argv[2]) == ERR) {
1015 config_msg = "Unknown color";
1019 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1020 config_msg = "Unknown attribute";
1027 /* Wants: name = value */
1029 option_set_command(int argc, char *argv[])
1032 config_msg = "Wrong number of arguments given to set command";
1036 if (strcmp(argv[1], "=")) {
1037 config_msg = "No value assigned";
1041 if (!strcmp(argv[0], "show-rev-graph")) {
1042 opt_rev_graph = (!strcmp(argv[2], "1") ||
1043 !strcmp(argv[2], "true") ||
1044 !strcmp(argv[2], "yes"));
1048 if (!strcmp(argv[0], "line-number-interval")) {
1049 opt_num_interval = atoi(argv[2]);
1053 if (!strcmp(argv[0], "tab-size")) {
1054 opt_tab_size = atoi(argv[2]);
1058 if (!strcmp(argv[0], "commit-encoding")) {
1059 char *arg = argv[2];
1060 int delimiter = *arg;
1063 switch (delimiter) {
1066 for (arg++, i = 0; arg[i]; i++)
1067 if (arg[i] == delimiter) {
1072 string_ncopy(opt_encoding, arg, strlen(arg));
1077 config_msg = "Unknown variable name";
1081 /* Wants: mode request key */
1083 option_bind_command(int argc, char *argv[])
1085 enum request request;
1090 config_msg = "Wrong number of arguments given to bind command";
1094 if (set_keymap(&keymap, argv[0]) == ERR) {
1095 config_msg = "Unknown key map";
1099 key = get_key_value(argv[1]);
1101 config_msg = "Unknown key";
1105 request = get_request(argv[2]);
1106 if (request == REQ_UNKNOWN) {
1107 config_msg = "Unknown request name";
1111 add_keybinding(keymap, request, key);
1117 set_option(char *opt, char *value)
1124 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1125 argv[argc++] = value;
1132 while (isspace(*value))
1136 if (!strcmp(opt, "color"))
1137 return option_color_command(argc, argv);
1139 if (!strcmp(opt, "set"))
1140 return option_set_command(argc, argv);
1142 if (!strcmp(opt, "bind"))
1143 return option_bind_command(argc, argv);
1145 config_msg = "Unknown option command";
1150 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1155 config_msg = "Internal error";
1157 /* Check for comment markers, since read_properties() will
1158 * only ensure opt and value are split at first " \t". */
1159 optlen = strcspn(opt, "#");
1163 if (opt[optlen] != 0) {
1164 config_msg = "No option value";
1168 /* Look for comment endings in the value. */
1169 size_t len = strcspn(value, "#");
1171 if (len < valuelen) {
1173 value[valuelen] = 0;
1176 status = set_option(opt, value);
1179 if (status == ERR) {
1180 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1181 config_lineno, (int) optlen, opt, config_msg);
1182 config_errors = TRUE;
1185 /* Always keep going if errors are encountered. */
1192 char *home = getenv("HOME");
1193 char buf[SIZEOF_STR];
1197 config_errors = FALSE;
1199 if (!home || !string_format(buf, "%s/.tigrc", home))
1202 /* It's ok that the file doesn't exist. */
1203 file = fopen(buf, "r");
1207 if (read_properties(file, " \t", read_option) == ERR ||
1208 config_errors == TRUE)
1209 fprintf(stderr, "Errors while loading %s.\n", buf);
1222 /* The display array of active views and the index of the current view. */
1223 static struct view *display[2];
1224 static unsigned int current_view;
1226 /* Reading from the prompt? */
1227 static bool input_mode = FALSE;
1229 #define foreach_displayed_view(view, i) \
1230 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1232 #define displayed_views() (display[1] != NULL ? 2 : 1)
1234 /* Current head and commit ID */
1235 static char ref_blob[SIZEOF_REF] = "";
1236 static char ref_commit[SIZEOF_REF] = "HEAD";
1237 static char ref_head[SIZEOF_REF] = "HEAD";
1240 const char *name; /* View name */
1241 const char *cmd_fmt; /* Default command line format */
1242 const char *cmd_env; /* Command line set via environment */
1243 const char *id; /* Points to either of ref_{head,commit,blob} */
1245 struct view_ops *ops; /* View operations */
1247 enum keymap keymap; /* What keymap does this view have */
1249 char cmd[SIZEOF_STR]; /* Command buffer */
1250 char ref[SIZEOF_REF]; /* Hovered commit reference */
1251 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1253 int height, width; /* The width and height of the main window */
1254 WINDOW *win; /* The main window */
1255 WINDOW *title; /* The title window living below the main window */
1258 unsigned long offset; /* Offset of the window top */
1259 unsigned long lineno; /* Current line number */
1262 char grep[SIZEOF_STR]; /* Search string */
1263 regex_t *regex; /* Pre-compiled regex */
1265 /* If non-NULL, points to the view that opened this view. If this view
1266 * is closed tig will switch back to the parent view. */
1267 struct view *parent;
1270 unsigned long lines; /* Total number of lines */
1271 struct line *line; /* Line index */
1272 unsigned long line_size;/* Total number of allocated lines */
1273 unsigned int digits; /* Number of digits in the lines member. */
1281 /* What type of content being displayed. Used in the title bar. */
1283 /* Open and reads in all view content. */
1284 bool (*open)(struct view *view);
1285 /* Read one line; updates view->line. */
1286 bool (*read)(struct view *view, char *data);
1287 /* Draw one line; @lineno must be < view->height. */
1288 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1289 /* Depending on view handle a special requests. */
1290 enum request (*request)(struct view *view, enum request request, struct line *line);
1291 /* Search for regex in a line. */
1292 bool (*grep)(struct view *view, struct line *line);
1294 void (*select)(struct view *view, struct line *line);
1297 static struct view_ops pager_ops;
1298 static struct view_ops main_ops;
1299 static struct view_ops tree_ops;
1300 static struct view_ops blob_ops;
1301 static struct view_ops help_ops;
1302 static struct view_ops status_ops;
1303 static struct view_ops stage_ops;
1305 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1306 { name, cmd, #env, ref, ops, map}
1308 #define VIEW_(id, name, ops, ref) \
1309 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1312 static struct view views[] = {
1313 VIEW_(MAIN, "main", &main_ops, ref_head),
1314 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1315 VIEW_(LOG, "log", &pager_ops, ref_head),
1316 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1317 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1318 VIEW_(HELP, "help", &help_ops, ""),
1319 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1320 VIEW_(STATUS, "status", &status_ops, ""),
1321 VIEW_(STAGE, "stage", &stage_ops, ""),
1324 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1326 #define foreach_view(view, i) \
1327 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1329 #define view_is_displayed(view) \
1330 (view == display[0] || view == display[1])
1333 draw_view_line(struct view *view, unsigned int lineno)
1336 bool selected = (view->offset + lineno == view->lineno);
1339 assert(view_is_displayed(view));
1341 if (view->offset + lineno >= view->lines)
1344 line = &view->line[view->offset + lineno];
1347 line->selected = TRUE;
1348 view->ops->select(view, line);
1349 } else if (line->selected) {
1350 line->selected = FALSE;
1351 wmove(view->win, lineno, 0);
1352 wclrtoeol(view->win);
1355 scrollok(view->win, FALSE);
1356 draw_ok = view->ops->draw(view, line, lineno, selected);
1357 scrollok(view->win, TRUE);
1363 redraw_view_from(struct view *view, int lineno)
1365 assert(0 <= lineno && lineno < view->height);
1367 for (; lineno < view->height; lineno++) {
1368 if (!draw_view_line(view, lineno))
1372 redrawwin(view->win);
1374 wnoutrefresh(view->win);
1376 wrefresh(view->win);
1380 redraw_view(struct view *view)
1383 redraw_view_from(view, 0);
1388 update_view_title(struct view *view)
1390 char buf[SIZEOF_STR];
1391 char state[SIZEOF_STR];
1392 size_t bufpos = 0, statelen = 0;
1394 assert(view_is_displayed(view));
1396 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1397 unsigned int view_lines = view->offset + view->height;
1398 unsigned int lines = view->lines
1399 ? MIN(view_lines, view->lines) * 100 / view->lines
1402 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1409 time_t secs = time(NULL) - view->start_time;
1411 /* Three git seconds are a long time ... */
1413 string_format_from(state, &statelen, " %lds", secs);
1417 string_format_from(buf, &bufpos, "[%s]", view->name);
1418 if (*view->ref && bufpos < view->width) {
1419 size_t refsize = strlen(view->ref);
1420 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1422 if (minsize < view->width)
1423 refsize = view->width - minsize + 7;
1424 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1427 if (statelen && bufpos < view->width) {
1428 string_format_from(buf, &bufpos, " %s", state);
1431 if (view == display[current_view])
1432 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1434 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1436 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1437 wclrtoeol(view->title);
1438 wmove(view->title, 0, view->width - 1);
1441 wnoutrefresh(view->title);
1443 wrefresh(view->title);
1447 resize_display(void)
1450 struct view *base = display[0];
1451 struct view *view = display[1] ? display[1] : display[0];
1453 /* Setup window dimensions */
1455 getmaxyx(stdscr, base->height, base->width);
1457 /* Make room for the status window. */
1461 /* Horizontal split. */
1462 view->width = base->width;
1463 view->height = SCALE_SPLIT_VIEW(base->height);
1464 base->height -= view->height;
1466 /* Make room for the title bar. */
1470 /* Make room for the title bar. */
1475 foreach_displayed_view (view, i) {
1477 view->win = newwin(view->height, 0, offset, 0);
1479 die("Failed to create %s view", view->name);
1481 scrollok(view->win, TRUE);
1483 view->title = newwin(1, 0, offset + view->height, 0);
1485 die("Failed to create title window");
1488 wresize(view->win, view->height, view->width);
1489 mvwin(view->win, offset, 0);
1490 mvwin(view->title, offset + view->height, 0);
1493 offset += view->height + 1;
1498 redraw_display(void)
1503 foreach_displayed_view (view, i) {
1505 update_view_title(view);
1510 update_display_cursor(struct view *view)
1512 /* Move the cursor to the right-most column of the cursor line.
1514 * XXX: This could turn out to be a bit expensive, but it ensures that
1515 * the cursor does not jump around. */
1517 wmove(view->win, view->lineno - view->offset, view->width - 1);
1518 wrefresh(view->win);
1526 /* Scrolling backend */
1528 do_scroll_view(struct view *view, int lines)
1530 bool redraw_current_line = FALSE;
1532 /* The rendering expects the new offset. */
1533 view->offset += lines;
1535 assert(0 <= view->offset && view->offset < view->lines);
1538 /* Move current line into the view. */
1539 if (view->lineno < view->offset) {
1540 view->lineno = view->offset;
1541 redraw_current_line = TRUE;
1542 } else if (view->lineno >= view->offset + view->height) {
1543 view->lineno = view->offset + view->height - 1;
1544 redraw_current_line = TRUE;
1547 assert(view->offset <= view->lineno && view->lineno < view->lines);
1549 /* Redraw the whole screen if scrolling is pointless. */
1550 if (view->height < ABS(lines)) {
1554 int line = lines > 0 ? view->height - lines : 0;
1555 int end = line + ABS(lines);
1557 wscrl(view->win, lines);
1559 for (; line < end; line++) {
1560 if (!draw_view_line(view, line))
1564 if (redraw_current_line)
1565 draw_view_line(view, view->lineno - view->offset);
1568 redrawwin(view->win);
1569 wrefresh(view->win);
1573 /* Scroll frontend */
1575 scroll_view(struct view *view, enum request request)
1579 assert(view_is_displayed(view));
1582 case REQ_SCROLL_PAGE_DOWN:
1583 lines = view->height;
1584 case REQ_SCROLL_LINE_DOWN:
1585 if (view->offset + lines > view->lines)
1586 lines = view->lines - view->offset;
1588 if (lines == 0 || view->offset + view->height >= view->lines) {
1589 report("Cannot scroll beyond the last line");
1594 case REQ_SCROLL_PAGE_UP:
1595 lines = view->height;
1596 case REQ_SCROLL_LINE_UP:
1597 if (lines > view->offset)
1598 lines = view->offset;
1601 report("Cannot scroll beyond the first line");
1609 die("request %d not handled in switch", request);
1612 do_scroll_view(view, lines);
1617 move_view(struct view *view, enum request request)
1619 int scroll_steps = 0;
1623 case REQ_MOVE_FIRST_LINE:
1624 steps = -view->lineno;
1627 case REQ_MOVE_LAST_LINE:
1628 steps = view->lines - view->lineno - 1;
1631 case REQ_MOVE_PAGE_UP:
1632 steps = view->height > view->lineno
1633 ? -view->lineno : -view->height;
1636 case REQ_MOVE_PAGE_DOWN:
1637 steps = view->lineno + view->height >= view->lines
1638 ? view->lines - view->lineno - 1 : view->height;
1650 die("request %d not handled in switch", request);
1653 if (steps <= 0 && view->lineno == 0) {
1654 report("Cannot move beyond the first line");
1657 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1658 report("Cannot move beyond the last line");
1662 /* Move the current line */
1663 view->lineno += steps;
1664 assert(0 <= view->lineno && view->lineno < view->lines);
1666 /* Check whether the view needs to be scrolled */
1667 if (view->lineno < view->offset ||
1668 view->lineno >= view->offset + view->height) {
1669 scroll_steps = steps;
1670 if (steps < 0 && -steps > view->offset) {
1671 scroll_steps = -view->offset;
1673 } else if (steps > 0) {
1674 if (view->lineno == view->lines - 1 &&
1675 view->lines > view->height) {
1676 scroll_steps = view->lines - view->offset - 1;
1677 if (scroll_steps >= view->height)
1678 scroll_steps -= view->height - 1;
1683 if (!view_is_displayed(view)) {
1684 view->offset += scroll_steps;
1685 assert(0 <= view->offset && view->offset < view->lines);
1686 view->ops->select(view, &view->line[view->lineno]);
1690 /* Repaint the old "current" line if we be scrolling */
1691 if (ABS(steps) < view->height)
1692 draw_view_line(view, view->lineno - steps - view->offset);
1695 do_scroll_view(view, scroll_steps);
1699 /* Draw the current line */
1700 draw_view_line(view, view->lineno - view->offset);
1702 redrawwin(view->win);
1703 wrefresh(view->win);
1712 static void search_view(struct view *view, enum request request);
1715 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1717 assert(view_is_displayed(view));
1719 if (!view->ops->grep(view, line))
1722 if (lineno - view->offset >= view->height) {
1723 view->offset = lineno;
1724 view->lineno = lineno;
1728 unsigned long old_lineno = view->lineno - view->offset;
1730 view->lineno = lineno;
1731 draw_view_line(view, old_lineno);
1733 draw_view_line(view, view->lineno - view->offset);
1734 redrawwin(view->win);
1735 wrefresh(view->win);
1738 report("Line %ld matches '%s'", lineno + 1, view->grep);
1743 find_next(struct view *view, enum request request)
1745 unsigned long lineno = view->lineno;
1750 report("No previous search");
1752 search_view(view, request);
1762 case REQ_SEARCH_BACK:
1771 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1772 lineno += direction;
1774 /* Note, lineno is unsigned long so will wrap around in which case it
1775 * will become bigger than view->lines. */
1776 for (; lineno < view->lines; lineno += direction) {
1777 struct line *line = &view->line[lineno];
1779 if (find_next_line(view, lineno, line))
1783 report("No match found for '%s'", view->grep);
1787 search_view(struct view *view, enum request request)
1792 regfree(view->regex);
1795 view->regex = calloc(1, sizeof(*view->regex));
1800 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1801 if (regex_err != 0) {
1802 char buf[SIZEOF_STR] = "unknown error";
1804 regerror(regex_err, view->regex, buf, sizeof(buf));
1805 report("Search failed: %s", buf);
1809 string_copy(view->grep, opt_search);
1811 find_next(view, request);
1815 * Incremental updating
1819 end_update(struct view *view)
1823 set_nonblocking_input(FALSE);
1824 if (view->pipe == stdin)
1832 begin_update(struct view *view)
1838 string_copy(view->cmd, opt_cmd);
1840 /* When running random commands, initially show the
1841 * command in the title. However, it maybe later be
1842 * overwritten if a commit line is selected. */
1843 if (view == VIEW(REQ_VIEW_PAGER))
1844 string_copy(view->ref, view->cmd);
1848 } else if (view == VIEW(REQ_VIEW_TREE)) {
1849 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1850 char path[SIZEOF_STR];
1852 if (strcmp(view->vid, view->id))
1853 opt_path[0] = path[0] = 0;
1854 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1857 if (!string_format(view->cmd, format, view->id, path))
1861 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1862 const char *id = view->id;
1864 if (!string_format(view->cmd, format, id, id, id, id, id))
1867 /* Put the current ref_* value to the view title ref
1868 * member. This is needed by the blob view. Most other
1869 * views sets it automatically after loading because the
1870 * first line is a commit line. */
1871 string_copy_rev(view->ref, view->id);
1874 /* Special case for the pager view. */
1876 view->pipe = opt_pipe;
1879 view->pipe = popen(view->cmd, "r");
1885 set_nonblocking_input(TRUE);
1890 string_copy_rev(view->vid, view->id);
1895 for (i = 0; i < view->lines; i++)
1896 if (view->line[i].data)
1897 free(view->line[i].data);
1903 view->start_time = time(NULL);
1908 static struct line *
1909 realloc_lines(struct view *view, size_t line_size)
1911 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1917 view->line_size = line_size;
1922 update_view(struct view *view)
1924 char in_buffer[BUFSIZ];
1925 char out_buffer[BUFSIZ * 2];
1927 /* The number of lines to read. If too low it will cause too much
1928 * redrawing (and possible flickering), if too high responsiveness
1930 unsigned long lines = view->height;
1931 int redraw_from = -1;
1936 /* Only redraw if lines are visible. */
1937 if (view->offset + view->height >= view->lines)
1938 redraw_from = view->lines - view->offset;
1940 /* FIXME: This is probably not perfect for backgrounded views. */
1941 if (!realloc_lines(view, view->lines + lines))
1944 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1945 size_t linelen = strlen(line);
1948 line[linelen - 1] = 0;
1950 if (opt_iconv != ICONV_NONE) {
1951 ICONV_CONST char *inbuf = line;
1952 size_t inlen = linelen;
1954 char *outbuf = out_buffer;
1955 size_t outlen = sizeof(out_buffer);
1959 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1960 if (ret != (size_t) -1) {
1962 linelen = strlen(out_buffer);
1966 if (!view->ops->read(view, line))
1976 lines = view->lines;
1977 for (digits = 0; lines; digits++)
1980 /* Keep the displayed view in sync with line number scaling. */
1981 if (digits != view->digits) {
1982 view->digits = digits;
1987 if (!view_is_displayed(view))
1990 if (view == VIEW(REQ_VIEW_TREE)) {
1991 /* Clear the view and redraw everything since the tree sorting
1992 * might have rearranged things. */
1995 } else if (redraw_from >= 0) {
1996 /* If this is an incremental update, redraw the previous line
1997 * since for commits some members could have changed when
1998 * loading the main view. */
1999 if (redraw_from > 0)
2002 /* Since revision graph visualization requires knowledge
2003 * about the parent commit, it causes a further one-off
2004 * needed to be redrawn for incremental updates. */
2005 if (redraw_from > 0 && opt_rev_graph)
2008 /* Incrementally draw avoids flickering. */
2009 redraw_view_from(view, redraw_from);
2012 /* Update the title _after_ the redraw so that if the redraw picks up a
2013 * commit reference in view->ref it'll be available here. */
2014 update_view_title(view);
2017 if (ferror(view->pipe)) {
2018 report("Failed to read: %s", strerror(errno));
2021 } else if (feof(view->pipe)) {
2029 report("Allocation failure");
2032 view->ops->read(view, NULL);
2037 static struct line *
2038 add_line_data(struct view *view, void *data, enum line_type type)
2040 struct line *line = &view->line[view->lines++];
2042 memset(line, 0, sizeof(*line));
2049 static struct line *
2050 add_line_text(struct view *view, char *data, enum line_type type)
2053 data = strdup(data);
2055 return data ? add_line_data(view, data, type) : NULL;
2064 OPEN_DEFAULT = 0, /* Use default view switching. */
2065 OPEN_SPLIT = 1, /* Split current view. */
2066 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2067 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2071 open_view(struct view *prev, enum request request, enum open_flags flags)
2073 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2074 bool split = !!(flags & OPEN_SPLIT);
2075 bool reload = !!(flags & OPEN_RELOAD);
2076 struct view *view = VIEW(request);
2077 int nviews = displayed_views();
2078 struct view *base_view = display[0];
2080 if (view == prev && nviews == 1 && !reload) {
2081 report("Already in %s view", view->name);
2085 if (view->ops->open) {
2086 if (!view->ops->open(view)) {
2087 report("Failed to load %s view", view->name);
2091 } else if ((reload || strcmp(view->vid, view->id)) &&
2092 !begin_update(view)) {
2093 report("Failed to load %s view", view->name);
2102 /* Maximize the current view. */
2103 memset(display, 0, sizeof(display));
2105 display[current_view] = view;
2108 /* Resize the view when switching between split- and full-screen,
2109 * or when switching between two different full-screen views. */
2110 if (nviews != displayed_views() ||
2111 (nviews == 1 && base_view != display[0]))
2114 if (split && prev->lineno - prev->offset >= prev->height) {
2115 /* Take the title line into account. */
2116 int lines = prev->lineno - prev->offset - prev->height + 1;
2118 /* Scroll the view that was split if the current line is
2119 * outside the new limited view. */
2120 do_scroll_view(prev, lines);
2123 if (prev && view != prev) {
2124 if (split && !backgrounded) {
2125 /* "Blur" the previous view. */
2126 update_view_title(prev);
2129 view->parent = prev;
2132 if (view->pipe && view->lines == 0) {
2133 /* Clear the old view and let the incremental updating refill
2142 /* If the view is backgrounded the above calls to report()
2143 * won't redraw the view title. */
2145 update_view_title(view);
2149 open_external_viewer(const char *cmd)
2151 def_prog_mode(); /* save current tty modes */
2152 endwin(); /* restore original tty modes */
2154 fprintf(stderr, "Press Enter to continue");
2161 open_editor(bool from_root, const char *file)
2163 char cmd[SIZEOF_STR];
2164 char file_sq[SIZEOF_STR];
2166 char *prefix = from_root ? opt_cdup : "";
2168 editor = getenv("GIT_EDITOR");
2169 if (!editor && *opt_editor)
2170 editor = opt_editor;
2172 editor = getenv("VISUAL");
2174 editor = getenv("EDITOR");
2178 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2179 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2180 open_external_viewer(cmd);
2185 * User request switch noodle
2189 view_driver(struct view *view, enum request request)
2193 if (request == REQ_NONE) {
2198 if (view && view->lines) {
2199 request = view->ops->request(view, request, &view->line[view->lineno]);
2200 if (request == REQ_NONE)
2207 case REQ_MOVE_PAGE_UP:
2208 case REQ_MOVE_PAGE_DOWN:
2209 case REQ_MOVE_FIRST_LINE:
2210 case REQ_MOVE_LAST_LINE:
2211 move_view(view, request);
2214 case REQ_SCROLL_LINE_DOWN:
2215 case REQ_SCROLL_LINE_UP:
2216 case REQ_SCROLL_PAGE_DOWN:
2217 case REQ_SCROLL_PAGE_UP:
2218 scroll_view(view, request);
2223 report("No file chosen, press %s to open tree view",
2224 get_key(REQ_VIEW_TREE));
2227 open_view(view, request, OPEN_DEFAULT);
2230 case REQ_VIEW_PAGER:
2231 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2232 report("No pager content, press %s to run command from prompt",
2233 get_key(REQ_PROMPT));
2236 open_view(view, request, OPEN_DEFAULT);
2239 case REQ_VIEW_STAGE:
2240 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2241 report("No stage content, press %s to open the status view and choose file",
2242 get_key(REQ_VIEW_STATUS));
2245 open_view(view, request, OPEN_DEFAULT);
2248 case REQ_VIEW_STATUS:
2249 if (opt_is_inside_work_tree == FALSE) {
2250 report("The status view requires a working tree");
2253 open_view(view, request, OPEN_DEFAULT);
2261 open_view(view, request, OPEN_DEFAULT);
2266 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2268 if ((view == VIEW(REQ_VIEW_DIFF) &&
2269 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2270 (view == VIEW(REQ_VIEW_STAGE) &&
2271 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2272 (view == VIEW(REQ_VIEW_BLOB) &&
2273 view->parent == VIEW(REQ_VIEW_TREE))) {
2276 view = view->parent;
2277 line = view->lineno;
2278 move_view(view, request);
2279 if (view_is_displayed(view))
2280 update_view_title(view);
2281 if (line != view->lineno)
2282 view->ops->request(view, REQ_ENTER,
2283 &view->line[view->lineno]);
2286 move_view(view, request);
2292 int nviews = displayed_views();
2293 int next_view = (current_view + 1) % nviews;
2295 if (next_view == current_view) {
2296 report("Only one view is displayed");
2300 current_view = next_view;
2301 /* Blur out the title of the previous view. */
2302 update_view_title(view);
2307 report("Refreshing is not yet supported for the %s view", view->name);
2310 case REQ_TOGGLE_LINENO:
2311 opt_line_number = !opt_line_number;
2315 case REQ_TOGGLE_REV_GRAPH:
2316 opt_rev_graph = !opt_rev_graph;
2321 /* Always reload^Wrerun commands from the prompt. */
2322 open_view(view, opt_request, OPEN_RELOAD);
2326 case REQ_SEARCH_BACK:
2327 search_view(view, request);
2332 find_next(view, request);
2335 case REQ_STOP_LOADING:
2336 for (i = 0; i < ARRAY_SIZE(views); i++) {
2339 report("Stopped loading the %s view", view->name),
2344 case REQ_SHOW_VERSION:
2345 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2348 case REQ_SCREEN_RESIZE:
2351 case REQ_SCREEN_REDRAW:
2356 report("Nothing to edit");
2359 case REQ_CHERRY_PICK:
2360 report("Nothing to cherry-pick");
2364 report("Nothing to enter");
2368 case REQ_VIEW_CLOSE:
2369 /* XXX: Mark closed views by letting view->parent point to the
2370 * view itself. Parents to closed view should never be
2373 view->parent->parent != view->parent) {
2374 memset(display, 0, sizeof(display));
2376 display[current_view] = view->parent;
2377 view->parent = view;
2387 /* An unknown key will show most commonly used commands. */
2388 report("Unknown key, press 'h' for help");
2401 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2403 char *text = line->data;
2404 enum line_type type = line->type;
2405 int textlen = strlen(text);
2408 wmove(view->win, lineno, 0);
2412 wchgat(view->win, -1, 0, type, NULL);
2415 attr = get_line_attr(type);
2416 wattrset(view->win, attr);
2418 if (opt_line_number || opt_tab_size < TABSIZE) {
2419 static char spaces[] = " ";
2420 int col_offset = 0, col = 0;
2422 if (opt_line_number) {
2423 unsigned long real_lineno = view->offset + lineno + 1;
2425 if (real_lineno == 1 ||
2426 (real_lineno % opt_num_interval) == 0) {
2427 wprintw(view->win, "%.*d", view->digits, real_lineno);
2430 waddnstr(view->win, spaces,
2431 MIN(view->digits, STRING_SIZE(spaces)));
2433 waddstr(view->win, ": ");
2434 col_offset = view->digits + 2;
2437 while (text && col_offset + col < view->width) {
2438 int cols_max = view->width - col_offset - col;
2442 if (*text == '\t') {
2444 assert(sizeof(spaces) > TABSIZE);
2446 cols = opt_tab_size - (col % opt_tab_size);
2449 text = strchr(text, '\t');
2450 cols = line ? text - pos : strlen(pos);
2453 waddnstr(view->win, pos, MIN(cols, cols_max));
2458 int col = 0, pos = 0;
2460 for (; pos < textlen && col < view->width; pos++, col++)
2461 if (text[pos] == '\t')
2462 col += TABSIZE - (col % TABSIZE) - 1;
2464 waddnstr(view->win, text, pos);
2471 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2473 char refbuf[SIZEOF_STR];
2477 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2480 pipe = popen(refbuf, "r");
2484 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2485 ref = chomp_string(ref);
2491 /* This is the only fatal call, since it can "corrupt" the buffer. */
2492 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2499 add_pager_refs(struct view *view, struct line *line)
2501 char buf[SIZEOF_STR];
2502 char *commit_id = line->data + STRING_SIZE("commit ");
2504 size_t bufpos = 0, refpos = 0;
2505 const char *sep = "Refs: ";
2506 bool is_tag = FALSE;
2508 assert(line->type == LINE_COMMIT);
2510 refs = get_refs(commit_id);
2512 if (view == VIEW(REQ_VIEW_DIFF))
2513 goto try_add_describe_ref;
2518 struct ref *ref = refs[refpos];
2519 char *fmt = ref->tag ? "%s[%s]" :
2520 ref->remote ? "%s<%s>" : "%s%s";
2522 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2527 } while (refs[refpos++]->next);
2529 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2530 try_add_describe_ref:
2531 /* Add <tag>-g<commit_id> "fake" reference. */
2532 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2539 if (!realloc_lines(view, view->line_size + 1))
2542 add_line_text(view, buf, LINE_PP_REFS);
2546 pager_read(struct view *view, char *data)
2553 line = add_line_text(view, data, get_line_type(data));
2557 if (line->type == LINE_COMMIT &&
2558 (view == VIEW(REQ_VIEW_DIFF) ||
2559 view == VIEW(REQ_VIEW_LOG)))
2560 add_pager_refs(view, line);
2566 pager_request(struct view *view, enum request request, struct line *line)
2570 if (request != REQ_ENTER)
2573 if (line->type == LINE_COMMIT &&
2574 (view == VIEW(REQ_VIEW_LOG) ||
2575 view == VIEW(REQ_VIEW_PAGER))) {
2576 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2580 /* Always scroll the view even if it was split. That way
2581 * you can use Enter to scroll through the log view and
2582 * split open each commit diff. */
2583 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2585 /* FIXME: A minor workaround. Scrolling the view will call report("")
2586 * but if we are scrolling a non-current view this won't properly
2587 * update the view title. */
2589 update_view_title(view);
2595 pager_grep(struct view *view, struct line *line)
2598 char *text = line->data;
2603 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2610 pager_select(struct view *view, struct line *line)
2612 if (line->type == LINE_COMMIT) {
2613 char *text = line->data + STRING_SIZE("commit ");
2615 if (view != VIEW(REQ_VIEW_PAGER))
2616 string_copy_rev(view->ref, text);
2617 string_copy_rev(ref_commit, text);
2621 static struct view_ops pager_ops = {
2637 help_open(struct view *view)
2640 int lines = ARRAY_SIZE(req_info) + 2;
2643 if (view->lines > 0)
2646 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2647 if (!req_info[i].request)
2650 view->line = calloc(lines, sizeof(*view->line));
2654 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2656 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2659 if (req_info[i].request == REQ_NONE)
2662 if (!req_info[i].request) {
2663 add_line_text(view, "", LINE_DEFAULT);
2664 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2668 key = get_key(req_info[i].request);
2670 key = "(no key defined)";
2672 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2675 add_line_text(view, buf, LINE_DEFAULT);
2681 static struct view_ops help_ops = {
2696 struct tree_stack_entry {
2697 struct tree_stack_entry *prev; /* Entry below this in the stack */
2698 unsigned long lineno; /* Line number to restore */
2699 char *name; /* Position of name in opt_path */
2702 /* The top of the path stack. */
2703 static struct tree_stack_entry *tree_stack = NULL;
2704 unsigned long tree_lineno = 0;
2707 pop_tree_stack_entry(void)
2709 struct tree_stack_entry *entry = tree_stack;
2711 tree_lineno = entry->lineno;
2713 tree_stack = entry->prev;
2718 push_tree_stack_entry(char *name, unsigned long lineno)
2720 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2721 size_t pathlen = strlen(opt_path);
2726 entry->prev = tree_stack;
2727 entry->name = opt_path + pathlen;
2730 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2731 pop_tree_stack_entry();
2735 /* Move the current line to the first tree entry. */
2737 entry->lineno = lineno;
2740 /* Parse output from git-ls-tree(1):
2742 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2743 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2744 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2745 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2748 #define SIZEOF_TREE_ATTR \
2749 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2751 #define TREE_UP_FORMAT "040000 tree %s\t.."
2754 tree_compare_entry(enum line_type type1, char *name1,
2755 enum line_type type2, char *name2)
2757 if (type1 != type2) {
2758 if (type1 == LINE_TREE_DIR)
2763 return strcmp(name1, name2);
2767 tree_read(struct view *view, char *text)
2769 size_t textlen = text ? strlen(text) : 0;
2770 char buf[SIZEOF_STR];
2772 enum line_type type;
2773 bool first_read = view->lines == 0;
2775 if (textlen <= SIZEOF_TREE_ATTR)
2778 type = text[STRING_SIZE("100644 ")] == 't'
2779 ? LINE_TREE_DIR : LINE_TREE_FILE;
2782 /* Add path info line */
2783 if (!string_format(buf, "Directory path /%s", opt_path) ||
2784 !realloc_lines(view, view->line_size + 1) ||
2785 !add_line_text(view, buf, LINE_DEFAULT))
2788 /* Insert "link" to parent directory. */
2790 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2791 !realloc_lines(view, view->line_size + 1) ||
2792 !add_line_text(view, buf, LINE_TREE_DIR))
2797 /* Strip the path part ... */
2799 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2800 size_t striplen = strlen(opt_path);
2801 char *path = text + SIZEOF_TREE_ATTR;
2803 if (pathlen > striplen)
2804 memmove(path, path + striplen,
2805 pathlen - striplen + 1);
2808 /* Skip "Directory ..." and ".." line. */
2809 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2810 struct line *line = &view->line[pos];
2811 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2812 char *path2 = text + SIZEOF_TREE_ATTR;
2813 int cmp = tree_compare_entry(line->type, path1, type, path2);
2818 text = strdup(text);
2822 if (view->lines > pos)
2823 memmove(&view->line[pos + 1], &view->line[pos],
2824 (view->lines - pos) * sizeof(*line));
2826 line = &view->line[pos];
2833 if (!add_line_text(view, text, type))
2836 if (tree_lineno > view->lineno) {
2837 view->lineno = tree_lineno;
2845 tree_request(struct view *view, enum request request, struct line *line)
2847 enum open_flags flags;
2849 if (request != REQ_ENTER)
2852 /* Cleanup the stack if the tree view is at a different tree. */
2853 while (!*opt_path && tree_stack)
2854 pop_tree_stack_entry();
2856 switch (line->type) {
2858 /* Depending on whether it is a subdir or parent (updir?) link
2859 * mangle the path buffer. */
2860 if (line == &view->line[1] && *opt_path) {
2861 pop_tree_stack_entry();
2864 char *data = line->data;
2865 char *basename = data + SIZEOF_TREE_ATTR;
2867 push_tree_stack_entry(basename, view->lineno);
2870 /* Trees and subtrees share the same ID, so they are not not
2871 * unique like blobs. */
2872 flags = OPEN_RELOAD;
2873 request = REQ_VIEW_TREE;
2876 case LINE_TREE_FILE:
2877 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2878 request = REQ_VIEW_BLOB;
2885 open_view(view, request, flags);
2886 if (request == REQ_VIEW_TREE) {
2887 view->lineno = tree_lineno;
2894 tree_select(struct view *view, struct line *line)
2896 char *text = line->data + STRING_SIZE("100644 blob ");
2898 if (line->type == LINE_TREE_FILE) {
2899 string_copy_rev(ref_blob, text);
2901 } else if (line->type != LINE_TREE_DIR) {
2905 string_copy_rev(view->ref, text);
2908 static struct view_ops tree_ops = {
2919 blob_read(struct view *view, char *line)
2921 return add_line_text(view, line, LINE_DEFAULT) != NULL;
2924 static struct view_ops blob_ops = {
2943 char rev[SIZEOF_REV];
2947 char rev[SIZEOF_REV];
2949 char name[SIZEOF_STR];
2952 static struct status stage_status;
2953 static enum line_type stage_line_type;
2955 /* Get fields from the diff line:
2956 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2959 status_get_diff(struct status *file, char *buf, size_t bufsize)
2961 char *old_mode = buf + 1;
2962 char *new_mode = buf + 8;
2963 char *old_rev = buf + 15;
2964 char *new_rev = buf + 56;
2965 char *status = buf + 97;
2967 if (bufsize != 99 ||
2968 old_mode[-1] != ':' ||
2969 new_mode[-1] != ' ' ||
2970 old_rev[-1] != ' ' ||
2971 new_rev[-1] != ' ' ||
2975 file->status = *status;
2977 string_copy_rev(file->old.rev, old_rev);
2978 string_copy_rev(file->new.rev, new_rev);
2980 file->old.mode = strtoul(old_mode, NULL, 8);
2981 file->new.mode = strtoul(new_mode, NULL, 8);
2989 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2991 struct status *file = NULL;
2992 char buf[SIZEOF_STR * 4];
2996 pipe = popen(cmd, "r");
3000 add_line_data(view, NULL, type);
3002 while (!feof(pipe) && !ferror(pipe)) {
3006 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3009 bufsize += readsize;
3011 /* Process while we have NUL chars. */
3012 while ((sep = memchr(buf, 0, bufsize))) {
3013 size_t sepsize = sep - buf + 1;
3016 if (!realloc_lines(view, view->line_size + 1))
3019 file = calloc(1, sizeof(*file));
3023 add_line_data(view, file, type);
3026 /* Parse diff info part. */
3030 } else if (!file->status) {
3031 if (!status_get_diff(file, buf, sepsize))
3035 memmove(buf, sep + 1, bufsize);
3037 sep = memchr(buf, 0, bufsize);
3040 sepsize = sep - buf + 1;
3043 /* git-ls-files just delivers a NUL separated
3044 * list of file names similar to the second half
3045 * of the git-diff-* output. */
3046 string_ncopy(file->name, buf, sepsize);
3048 memmove(buf, sep + 1, bufsize);
3059 if (!view->line[view->lines - 1].data)
3060 add_line_data(view, NULL, LINE_STAT_NONE);
3066 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
3067 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3068 #define STATUS_LIST_OTHER_CMD \
3069 "git ls-files -z --others --exclude-per-directory=.gitignore"
3071 #define STATUS_DIFF_SHOW_CMD \
3072 "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3074 /* First parse staged info using git-diff-index(1), then parse unstaged
3075 * info using git-diff-files(1), and finally untracked files using
3076 * git-ls-files(1). */
3078 status_open(struct view *view)
3080 struct stat statbuf;
3081 char exclude[SIZEOF_STR];
3082 char cmd[SIZEOF_STR];
3083 unsigned long prev_lineno = view->lineno;
3086 for (i = 0; i < view->lines; i++)
3087 free(view->line[i].data);
3089 view->lines = view->line_size = view->lineno = 0;
3092 if (!realloc_lines(view, view->line_size + 6))
3095 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3098 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3100 if (stat(exclude, &statbuf) >= 0) {
3101 size_t cmdsize = strlen(cmd);
3103 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3104 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3108 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3109 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3110 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3113 /* If all went well restore the previous line number to stay in
3115 if (prev_lineno < view->lines)
3116 view->lineno = prev_lineno;
3118 view->lineno = view->lines - 1;
3124 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3126 struct status *status = line->data;
3128 wmove(view->win, lineno, 0);
3131 wattrset(view->win, get_line_attr(LINE_CURSOR));
3132 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3134 } else if (!status && line->type != LINE_STAT_NONE) {
3135 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3136 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3139 wattrset(view->win, get_line_attr(line->type));
3145 switch (line->type) {
3146 case LINE_STAT_STAGED:
3147 text = "Changes to be committed:";
3150 case LINE_STAT_UNSTAGED:
3151 text = "Changed but not updated:";
3154 case LINE_STAT_UNTRACKED:
3155 text = "Untracked files:";
3158 case LINE_STAT_NONE:
3159 text = " (no files)";
3166 waddstr(view->win, text);
3170 waddch(view->win, status->status);
3172 wattrset(view->win, A_NORMAL);
3173 wmove(view->win, lineno, 4);
3174 waddstr(view->win, status->name);
3180 status_enter(struct view *view, struct line *line)
3182 struct status *status = line->data;
3183 char path[SIZEOF_STR] = "";
3187 if (line->type == LINE_STAT_NONE ||
3188 (!status && line[1].type == LINE_STAT_NONE)) {
3189 report("No file to diff");
3193 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3197 line->type != LINE_STAT_UNTRACKED &&
3198 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3201 switch (line->type) {
3202 case LINE_STAT_STAGED:
3203 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3207 info = "Staged changes to %s";
3209 info = "Staged changes";
3212 case LINE_STAT_UNSTAGED:
3213 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3217 info = "Unstaged changes to %s";
3219 info = "Unstaged changes";
3222 case LINE_STAT_UNTRACKED:
3228 report("No file to show");
3232 opt_pipe = fopen(status->name, "r");
3233 info = "Untracked file %s";
3240 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3241 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3243 stage_status = *status;
3245 memset(&stage_status, 0, sizeof(stage_status));
3248 stage_line_type = line->type;
3249 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3257 status_update_file(struct view *view, struct status *status, enum line_type type)
3259 char cmd[SIZEOF_STR];
3260 char buf[SIZEOF_STR];
3267 type != LINE_STAT_UNTRACKED &&
3268 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3272 case LINE_STAT_STAGED:
3273 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3279 string_add(cmd, cmdsize, "git update-index -z --index-info");
3282 case LINE_STAT_UNSTAGED:
3283 case LINE_STAT_UNTRACKED:
3284 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3287 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3294 pipe = popen(cmd, "w");
3298 while (!ferror(pipe) && written < bufsize) {
3299 written += fwrite(buf + written, 1, bufsize - written, pipe);
3304 if (written != bufsize)
3311 status_update(struct view *view)
3313 struct line *line = &view->line[view->lineno];
3315 assert(view->lines);
3318 while (++line < view->line + view->lines && line->data) {
3319 if (!status_update_file(view, line->data, line->type))
3320 report("Failed to update file status");
3323 if (!line[-1].data) {
3324 report("Nothing to update");
3328 } else if (!status_update_file(view, line->data, line->type)) {
3329 report("Failed to update file status");
3332 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3336 status_request(struct view *view, enum request request, struct line *line)
3338 struct status *status = line->data;
3341 case REQ_STATUS_UPDATE:
3342 status_update(view);
3349 open_editor(status->status != '?', status->name);
3350 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3354 status_enter(view, line);
3358 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3369 status_select(struct view *view, struct line *line)
3371 struct status *status = line->data;
3372 char file[SIZEOF_STR] = "all files";
3375 if (status && !string_format(file, "'%s'", status->name))
3378 if (!status && line[1].type == LINE_STAT_NONE)
3381 switch (line->type) {
3382 case LINE_STAT_STAGED:
3383 text = "Press %s to unstage %s for commit";
3386 case LINE_STAT_UNSTAGED:
3387 text = "Press %s to stage %s for commit";
3390 case LINE_STAT_UNTRACKED:
3391 text = "Press %s to stage %s for addition";
3394 case LINE_STAT_NONE:
3395 text = "Nothing to update";
3402 string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
3406 status_grep(struct view *view, struct line *line)
3408 struct status *status = line->data;
3409 enum { S_STATUS, S_NAME, S_END } state;
3416 for (state = S_STATUS; state < S_END; state++) {
3420 case S_NAME: text = status->name; break;
3422 buf[0] = status->status;
3430 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3437 static struct view_ops status_ops = {
3449 stage_diff_line(FILE *pipe, struct line *line)
3451 char *buf = line->data;
3452 size_t bufsize = strlen(buf);
3455 while (!ferror(pipe) && written < bufsize) {
3456 written += fwrite(buf + written, 1, bufsize - written, pipe);
3461 return written == bufsize;
3464 static struct line *
3465 stage_diff_hdr(struct view *view, struct line *line)
3467 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3468 struct line *diff_hdr;
3470 if (line->type == LINE_DIFF_CHUNK)
3471 diff_hdr = line - 1;
3473 diff_hdr = view->line + 1;
3475 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3476 if (diff_hdr->type == LINE_DIFF_HEADER)
3479 diff_hdr += diff_hdr_dir;
3486 stage_update_chunk(struct view *view, struct line *line)
3488 char cmd[SIZEOF_STR];
3490 struct line *diff_hdr, *diff_chunk, *diff_end;
3493 diff_hdr = stage_diff_hdr(view, line);
3498 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3501 if (!string_format_from(cmd, &cmdsize,
3502 "git apply --cached %s - && "
3503 "git update-index -q --unmerged --refresh 2>/dev/null",
3504 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3507 pipe = popen(cmd, "w");
3511 diff_end = view->line + view->lines;
3512 if (line->type != LINE_DIFF_CHUNK) {
3513 diff_chunk = diff_hdr;
3516 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3517 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3518 diff_chunk->type == LINE_DIFF_HEADER)
3519 diff_end = diff_chunk;
3523 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3524 switch (diff_hdr->type) {
3525 case LINE_DIFF_HEADER:
3526 case LINE_DIFF_INDEX:
3536 if (!stage_diff_line(pipe, diff_hdr++)) {
3543 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3548 if (diff_chunk != diff_end)
3555 stage_update(struct view *view, struct line *line)
3557 if (stage_line_type != LINE_STAT_UNTRACKED &&
3558 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3559 if (!stage_update_chunk(view, line)) {
3560 report("Failed to apply chunk");
3564 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3565 report("Failed to update file");
3569 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3571 view = VIEW(REQ_VIEW_STATUS);
3572 if (view_is_displayed(view))
3573 status_enter(view, &view->line[view->lineno]);
3577 stage_request(struct view *view, enum request request, struct line *line)
3580 case REQ_STATUS_UPDATE:
3581 stage_update(view, line);
3585 if (!stage_status.name[0])
3588 open_editor(stage_status.status != '?', stage_status.name);
3592 pager_request(view, request, line);
3602 static struct view_ops stage_ops = {
3618 char id[SIZEOF_REV]; /* SHA1 ID. */
3619 char title[128]; /* First line of the commit message. */
3620 char author[75]; /* Author of the commit. */
3621 struct tm time; /* Date from the author ident. */
3622 struct ref **refs; /* Repository references. */
3623 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3624 size_t graph_size; /* The width of the graph array. */
3627 /* Size of rev graph with no "padding" columns */
3628 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3631 struct rev_graph *prev, *next, *parents;
3632 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3634 struct commit *commit;
3638 /* Parents of the commit being visualized. */
3639 static struct rev_graph graph_parents[4];
3641 /* The current stack of revisions on the graph. */
3642 static struct rev_graph graph_stacks[4] = {
3643 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3644 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3645 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3646 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3650 graph_parent_is_merge(struct rev_graph *graph)
3652 return graph->parents->size > 1;
3656 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3658 struct commit *commit = graph->commit;
3660 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3661 commit->graph[commit->graph_size++] = symbol;
3665 done_rev_graph(struct rev_graph *graph)
3667 if (graph_parent_is_merge(graph) &&
3668 graph->pos < graph->size - 1 &&
3669 graph->next->size == graph->size + graph->parents->size - 1) {
3670 size_t i = graph->pos + graph->parents->size - 1;
3672 graph->commit->graph_size = i * 2;
3673 while (i < graph->next->size - 1) {
3674 append_to_rev_graph(graph, ' ');
3675 append_to_rev_graph(graph, '\\');
3680 graph->size = graph->pos = 0;
3681 graph->commit = NULL;
3682 memset(graph->parents, 0, sizeof(*graph->parents));
3686 push_rev_graph(struct rev_graph *graph, char *parent)
3690 /* "Collapse" duplicate parents lines.
3692 * FIXME: This needs to also update update the drawn graph but
3693 * for now it just serves as a method for pruning graph lines. */
3694 for (i = 0; i < graph->size; i++)
3695 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3698 if (graph->size < SIZEOF_REVITEMS) {
3699 string_copy_rev(graph->rev[graph->size++], parent);
3704 get_rev_graph_symbol(struct rev_graph *graph)
3708 if (graph->parents->size == 0)
3709 symbol = REVGRAPH_INIT;
3710 else if (graph_parent_is_merge(graph))
3711 symbol = REVGRAPH_MERGE;
3712 else if (graph->pos >= graph->size)
3713 symbol = REVGRAPH_BRANCH;
3715 symbol = REVGRAPH_COMMIT;
3721 draw_rev_graph(struct rev_graph *graph)
3724 chtype separator, line;
3726 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3727 static struct rev_filler fillers[] = {
3728 { ' ', REVGRAPH_LINE },
3733 chtype symbol = get_rev_graph_symbol(graph);
3734 struct rev_filler *filler;
3737 filler = &fillers[DEFAULT];
3739 for (i = 0; i < graph->pos; i++) {
3740 append_to_rev_graph(graph, filler->line);
3741 if (graph_parent_is_merge(graph->prev) &&
3742 graph->prev->pos == i)
3743 filler = &fillers[RSHARP];
3745 append_to_rev_graph(graph, filler->separator);
3748 /* Place the symbol for this revision. */
3749 append_to_rev_graph(graph, symbol);
3751 if (graph->prev->size > graph->size)
3752 filler = &fillers[RDIAG];
3754 filler = &fillers[DEFAULT];
3758 for (; i < graph->size; i++) {
3759 append_to_rev_graph(graph, filler->separator);
3760 append_to_rev_graph(graph, filler->line);
3761 if (graph_parent_is_merge(graph->prev) &&
3762 i < graph->prev->pos + graph->parents->size)
3763 filler = &fillers[RSHARP];
3764 if (graph->prev->size > graph->size)
3765 filler = &fillers[LDIAG];
3768 if (graph->prev->size > graph->size) {
3769 append_to_rev_graph(graph, filler->separator);
3770 if (filler->line != ' ')
3771 append_to_rev_graph(graph, filler->line);
3775 /* Prepare the next rev graph */
3777 prepare_rev_graph(struct rev_graph *graph)
3781 /* First, traverse all lines of revisions up to the active one. */
3782 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3783 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3786 push_rev_graph(graph->next, graph->rev[graph->pos]);
3789 /* Interleave the new revision parent(s). */
3790 for (i = 0; i < graph->parents->size; i++)
3791 push_rev_graph(graph->next, graph->parents->rev[i]);
3793 /* Lastly, put any remaining revisions. */
3794 for (i = graph->pos + 1; i < graph->size; i++)
3795 push_rev_graph(graph->next, graph->rev[i]);
3799 update_rev_graph(struct rev_graph *graph)
3801 /* If this is the finalizing update ... */
3803 prepare_rev_graph(graph);
3805 /* Graph visualization needs a one rev look-ahead,
3806 * so the first update doesn't visualize anything. */
3807 if (!graph->prev->commit)
3810 draw_rev_graph(graph->prev);
3811 done_rev_graph(graph->prev->prev);
3820 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3822 char buf[DATE_COLS + 1];
3823 struct commit *commit = line->data;
3824 enum line_type type;
3830 if (!*commit->author)
3833 wmove(view->win, lineno, col);
3837 wattrset(view->win, get_line_attr(type));
3838 wchgat(view->win, -1, 0, type, NULL);
3841 type = LINE_MAIN_COMMIT;
3842 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3845 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3846 waddnstr(view->win, buf, timelen);
3847 waddstr(view->win, " ");
3850 wmove(view->win, lineno, col);
3851 if (type != LINE_CURSOR)
3852 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3855 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3857 authorlen = strlen(commit->author);
3858 if (authorlen > AUTHOR_COLS - 2) {
3859 authorlen = AUTHOR_COLS - 2;
3865 waddnstr(view->win, commit->author, authorlen);
3866 if (type != LINE_CURSOR)
3867 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3868 waddch(view->win, '~');
3870 waddstr(view->win, commit->author);
3874 if (type != LINE_CURSOR)
3875 wattrset(view->win, A_NORMAL);
3877 if (opt_rev_graph && commit->graph_size) {
3880 wmove(view->win, lineno, col);
3881 /* Using waddch() instead of waddnstr() ensures that
3882 * they'll be rendered correctly for the cursor line. */
3883 for (i = 0; i < commit->graph_size; i++)
3884 waddch(view->win, commit->graph[i]);
3886 waddch(view->win, ' ');
3887 col += commit->graph_size + 1;
3890 wmove(view->win, lineno, col);
3896 if (type == LINE_CURSOR)
3898 else if (commit->refs[i]->tag)
3899 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3900 else if (commit->refs[i]->remote)
3901 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3903 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3904 waddstr(view->win, "[");
3905 waddstr(view->win, commit->refs[i]->name);
3906 waddstr(view->win, "]");
3907 if (type != LINE_CURSOR)
3908 wattrset(view->win, A_NORMAL);
3909 waddstr(view->win, " ");
3910 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3911 } while (commit->refs[i++]->next);
3914 if (type != LINE_CURSOR)
3915 wattrset(view->win, get_line_attr(type));
3918 int titlelen = strlen(commit->title);
3920 if (col + titlelen > view->width)
3921 titlelen = view->width - col;
3923 waddnstr(view->win, commit->title, titlelen);
3929 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3931 main_read(struct view *view, char *line)
3933 static struct rev_graph *graph = graph_stacks;
3934 enum line_type type;
3935 struct commit *commit;
3938 update_rev_graph(graph);
3942 type = get_line_type(line);
3943 if (type == LINE_COMMIT) {
3944 commit = calloc(1, sizeof(struct commit));
3948 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3949 commit->refs = get_refs(commit->id);
3950 graph->commit = commit;
3951 add_line_data(view, commit, LINE_MAIN_COMMIT);
3957 commit = view->line[view->lines - 1].data;
3961 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3966 /* Parse author lines where the name may be empty:
3967 * author <email@address.tld> 1138474660 +0100
3969 char *ident = line + STRING_SIZE("author ");
3970 char *nameend = strchr(ident, '<');
3971 char *emailend = strchr(ident, '>');
3973 if (!nameend || !emailend)
3976 update_rev_graph(graph);
3977 graph = graph->next;
3979 *nameend = *emailend = 0;
3980 ident = chomp_string(ident);
3982 ident = chomp_string(nameend + 1);
3987 string_ncopy(commit->author, ident, strlen(ident));
3989 /* Parse epoch and timezone */
3990 if (emailend[1] == ' ') {
3991 char *secs = emailend + 2;
3992 char *zone = strchr(secs, ' ');
3993 time_t time = (time_t) atol(secs);
3995 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3999 tz = ('0' - zone[1]) * 60 * 60 * 10;
4000 tz += ('0' - zone[2]) * 60 * 60;
4001 tz += ('0' - zone[3]) * 60;
4002 tz += ('0' - zone[4]) * 60;
4010 gmtime_r(&time, &commit->time);
4015 /* Fill in the commit title if it has not already been set. */
4016 if (commit->title[0])
4019 /* Require titles to start with a non-space character at the
4020 * offset used by git log. */
4021 if (strncmp(line, " ", 4))
4024 /* Well, if the title starts with a whitespace character,
4025 * try to be forgiving. Otherwise we end up with no title. */
4026 while (isspace(*line))
4030 /* FIXME: More graceful handling of titles; append "..." to
4031 * shortened titles, etc. */
4033 string_ncopy(commit->title, line, strlen(line));
4040 cherry_pick_commit(struct commit *commit)
4042 char cmd[SIZEOF_STR];
4043 char *cherry_pick = getenv("TIG_CHERRY_PICK");
4046 cherry_pick = "git cherry-pick";
4048 if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4049 open_external_viewer(cmd);
4054 main_request(struct view *view, enum request request, struct line *line)
4056 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4058 if (request == REQ_ENTER)
4059 open_view(view, REQ_VIEW_DIFF, flags);
4060 else if (request == REQ_CHERRY_PICK)
4061 cherry_pick_commit(line->data);
4069 main_grep(struct view *view, struct line *line)
4071 struct commit *commit = line->data;
4072 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4073 char buf[DATE_COLS + 1];
4076 for (state = S_TITLE; state < S_END; state++) {
4080 case S_TITLE: text = commit->title; break;
4081 case S_AUTHOR: text = commit->author; break;
4083 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4092 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4100 main_select(struct view *view, struct line *line)
4102 struct commit *commit = line->data;
4104 string_copy_rev(view->ref, commit->id);
4105 string_copy_rev(ref_commit, view->ref);
4108 static struct view_ops main_ops = {
4120 * Unicode / UTF-8 handling
4122 * NOTE: Much of the following code for dealing with unicode is derived from
4123 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4124 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4127 /* I've (over)annotated a lot of code snippets because I am not entirely
4128 * confident that the approach taken by this small UTF-8 interface is correct.
4132 unicode_width(unsigned long c)
4135 (c <= 0x115f /* Hangul Jamo */
4138 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4140 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4141 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4142 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4143 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4144 || (c >= 0xffe0 && c <= 0xffe6)
4145 || (c >= 0x20000 && c <= 0x2fffd)
4146 || (c >= 0x30000 && c <= 0x3fffd)))
4152 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4153 * Illegal bytes are set one. */
4154 static const unsigned char utf8_bytes[256] = {
4155 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,
4156 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,
4157 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,
4158 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,
4159 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,
4160 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,
4161 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,
4162 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,
4165 /* Decode UTF-8 multi-byte representation into a unicode character. */
4166 static inline unsigned long
4167 utf8_to_unicode(const char *string, size_t length)
4169 unsigned long unicode;
4173 unicode = string[0];
4176 unicode = (string[0] & 0x1f) << 6;
4177 unicode += (string[1] & 0x3f);
4180 unicode = (string[0] & 0x0f) << 12;
4181 unicode += ((string[1] & 0x3f) << 6);
4182 unicode += (string[2] & 0x3f);
4185 unicode = (string[0] & 0x0f) << 18;
4186 unicode += ((string[1] & 0x3f) << 12);
4187 unicode += ((string[2] & 0x3f) << 6);
4188 unicode += (string[3] & 0x3f);
4191 unicode = (string[0] & 0x0f) << 24;
4192 unicode += ((string[1] & 0x3f) << 18);
4193 unicode += ((string[2] & 0x3f) << 12);
4194 unicode += ((string[3] & 0x3f) << 6);
4195 unicode += (string[4] & 0x3f);
4198 unicode = (string[0] & 0x01) << 30;
4199 unicode += ((string[1] & 0x3f) << 24);
4200 unicode += ((string[2] & 0x3f) << 18);
4201 unicode += ((string[3] & 0x3f) << 12);
4202 unicode += ((string[4] & 0x3f) << 6);
4203 unicode += (string[5] & 0x3f);
4206 die("Invalid unicode length");
4209 /* Invalid characters could return the special 0xfffd value but NUL
4210 * should be just as good. */
4211 return unicode > 0xffff ? 0 : unicode;
4214 /* Calculates how much of string can be shown within the given maximum width
4215 * and sets trimmed parameter to non-zero value if all of string could not be
4218 * Additionally, adds to coloffset how many many columns to move to align with
4219 * the expected position. Takes into account how multi-byte and double-width
4220 * characters will effect the cursor position.
4222 * Returns the number of bytes to output from string to satisfy max_width. */
4224 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4226 const char *start = string;
4227 const char *end = strchr(string, '\0');
4233 while (string < end) {
4234 int c = *(unsigned char *) string;
4235 unsigned char bytes = utf8_bytes[c];
4237 unsigned long unicode;
4239 if (string + bytes > end)
4242 /* Change representation to figure out whether
4243 * it is a single- or double-width character. */
4245 unicode = utf8_to_unicode(string, bytes);
4246 /* FIXME: Graceful handling of invalid unicode character. */
4250 ucwidth = unicode_width(unicode);
4252 if (width > max_width) {
4257 /* The column offset collects the differences between the
4258 * number of bytes encoding a character and the number of
4259 * columns will be used for rendering said character.
4261 * So if some character A is encoded in 2 bytes, but will be
4262 * represented on the screen using only 1 byte this will and up
4263 * adding 1 to the multi-byte column offset.
4265 * Assumes that no double-width character can be encoding in
4266 * less than two bytes. */
4267 if (bytes > ucwidth)
4268 mbwidth += bytes - ucwidth;
4273 *coloffset += mbwidth;
4275 return string - start;
4283 /* Whether or not the curses interface has been initialized. */
4284 static bool cursed = FALSE;
4286 /* The status window is used for polling keystrokes. */
4287 static WINDOW *status_win;
4289 static bool status_empty = TRUE;
4291 /* Update status and title window. */
4293 report(const char *msg, ...)
4295 struct view *view = display[current_view];
4301 char buf[SIZEOF_STR];
4304 va_start(args, msg);
4305 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4306 buf[sizeof(buf) - 1] = 0;
4307 buf[sizeof(buf) - 2] = '.';
4308 buf[sizeof(buf) - 3] = '.';
4309 buf[sizeof(buf) - 4] = '.';
4315 if (!status_empty || *msg) {
4318 va_start(args, msg);
4320 wmove(status_win, 0, 0);
4322 vwprintw(status_win, msg, args);
4323 status_empty = FALSE;
4325 status_empty = TRUE;
4327 wclrtoeol(status_win);
4328 wrefresh(status_win);
4333 update_view_title(view);
4334 update_display_cursor(view);
4337 /* Controls when nodelay should be in effect when polling user input. */
4339 set_nonblocking_input(bool loading)
4341 static unsigned int loading_views;
4343 if ((loading == FALSE && loading_views-- == 1) ||
4344 (loading == TRUE && loading_views++ == 0))
4345 nodelay(status_win, loading);
4353 /* Initialize the curses library */
4354 if (isatty(STDIN_FILENO)) {
4355 cursed = !!initscr();
4357 /* Leave stdin and stdout alone when acting as a pager. */
4358 FILE *io = fopen("/dev/tty", "r+");
4361 die("Failed to open /dev/tty");
4362 cursed = !!newterm(NULL, io, io);
4366 die("Failed to initialize curses");
4368 nonl(); /* Tell curses not to do NL->CR/NL on output */
4369 cbreak(); /* Take input chars one at a time, no wait for \n */
4370 noecho(); /* Don't echo input */
4371 leaveok(stdscr, TRUE);
4376 getmaxyx(stdscr, y, x);
4377 status_win = newwin(1, 0, y - 1, 0);
4379 die("Failed to create status window");
4381 /* Enable keyboard mapping */
4382 keypad(status_win, TRUE);
4383 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4387 read_prompt(const char *prompt)
4389 enum { READING, STOP, CANCEL } status = READING;
4390 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4393 while (status == READING) {
4399 foreach_view (view, i)
4404 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4405 wclrtoeol(status_win);
4407 /* Refresh, accept single keystroke of input */
4408 key = wgetch(status_win);
4413 status = pos ? STOP : CANCEL;
4431 if (pos >= sizeof(buf)) {
4432 report("Input string too long");
4437 buf[pos++] = (char) key;
4441 /* Clear the status window */
4442 status_empty = FALSE;
4445 if (status == CANCEL)
4454 * Repository references
4457 static struct ref *refs;
4458 static size_t refs_size;
4460 /* Id <-> ref store */
4461 static struct ref ***id_refs;
4462 static size_t id_refs_size;
4464 static struct ref **
4467 struct ref ***tmp_id_refs;
4468 struct ref **ref_list = NULL;
4469 size_t ref_list_size = 0;
4472 for (i = 0; i < id_refs_size; i++)
4473 if (!strcmp(id, id_refs[i][0]->id))
4476 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4480 id_refs = tmp_id_refs;
4482 for (i = 0; i < refs_size; i++) {
4485 if (strcmp(id, refs[i].id))
4488 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4496 if (ref_list_size > 0)
4497 ref_list[ref_list_size - 1]->next = 1;
4498 ref_list[ref_list_size] = &refs[i];
4500 /* XXX: The properties of the commit chains ensures that we can
4501 * safely modify the shared ref. The repo references will
4502 * always be similar for the same id. */
4503 ref_list[ref_list_size]->next = 0;
4508 id_refs[id_refs_size++] = ref_list;
4514 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4518 bool remote = FALSE;
4520 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4521 /* Commits referenced by tags has "^{}" appended. */
4522 if (name[namelen - 1] != '}')
4525 while (namelen > 0 && name[namelen] != '^')
4529 namelen -= STRING_SIZE("refs/tags/");
4530 name += STRING_SIZE("refs/tags/");
4532 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4534 namelen -= STRING_SIZE("refs/remotes/");
4535 name += STRING_SIZE("refs/remotes/");
4537 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4538 namelen -= STRING_SIZE("refs/heads/");
4539 name += STRING_SIZE("refs/heads/");
4541 } else if (!strcmp(name, "HEAD")) {
4545 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4549 ref = &refs[refs_size++];
4550 ref->name = malloc(namelen + 1);
4554 strncpy(ref->name, name, namelen);
4555 ref->name[namelen] = 0;
4557 ref->remote = remote;
4558 string_copy_rev(ref->id, id);
4566 const char *cmd_env = getenv("TIG_LS_REMOTE");
4567 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4569 return read_properties(popen(cmd, "r"), "\t", read_ref);
4573 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4575 if (!strcmp(name, "i18n.commitencoding"))
4576 string_ncopy(opt_encoding, value, valuelen);
4578 if (!strcmp(name, "core.editor"))
4579 string_ncopy(opt_editor, value, valuelen);
4585 load_repo_config(void)
4587 return read_properties(popen(GIT_CONFIG " --list", "r"),
4588 "=", read_repo_config_option);
4592 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4594 if (!opt_git_dir[0]) {
4595 string_ncopy(opt_git_dir, name, namelen);
4597 } else if (opt_is_inside_work_tree == -1) {
4598 /* This can be 3 different values depending on the
4599 * version of git being used. If git-rev-parse does not
4600 * understand --is-inside-work-tree it will simply echo
4601 * the option else either "true" or "false" is printed.
4602 * Default to true for the unknown case. */
4603 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4606 string_ncopy(opt_cdup, name, namelen);
4612 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4613 * must be the last one! */
4615 load_repo_info(void)
4617 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4618 "=", read_repo_info);
4622 read_properties(FILE *pipe, const char *separators,
4623 int (*read_property)(char *, size_t, char *, size_t))
4625 char buffer[BUFSIZ];
4632 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4637 name = chomp_string(name);
4638 namelen = strcspn(name, separators);
4640 if (name[namelen]) {
4642 value = chomp_string(name + namelen + 1);
4643 valuelen = strlen(value);
4650 state = read_property(name, namelen, value, valuelen);
4653 if (state != ERR && ferror(pipe))
4666 static void __NORETURN
4669 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4675 static void __NORETURN
4676 die(const char *err, ...)
4682 va_start(args, err);
4683 fputs("tig: ", stderr);
4684 vfprintf(stderr, err, args);
4685 fputs("\n", stderr);
4692 main(int argc, char *argv[])
4695 enum request request;
4698 signal(SIGINT, quit);
4700 if (setlocale(LC_ALL, "")) {
4701 char *codeset = nl_langinfo(CODESET);
4703 string_ncopy(opt_codeset, codeset, strlen(codeset));
4706 if (load_repo_info() == ERR)
4707 die("Failed to load repo info.");
4709 if (load_options() == ERR)
4710 die("Failed to load user config.");
4712 /* Load the repo config file so options can be overwritten from
4713 * the command line. */
4714 if (load_repo_config() == ERR)
4715 die("Failed to load repo config.");
4717 if (!parse_options(argc, argv))
4720 /* Require a git repository unless when running in pager mode. */
4721 if (!opt_git_dir[0])
4722 die("Not a git repository");
4724 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4725 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4726 if (opt_iconv == ICONV_NONE)
4727 die("Failed to initialize character set conversion");
4730 if (load_refs() == ERR)
4731 die("Failed to load refs.");
4733 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4734 view->cmd_env = getenv(view->cmd_env);
4736 request = opt_request;
4740 while (view_driver(display[current_view], request)) {
4744 foreach_view (view, i)
4747 /* Refresh, accept single keystroke of input */
4748 key = wgetch(status_win);
4750 /* wgetch() with nodelay() enabled returns ERR when there's no
4757 request = get_keybinding(display[current_view]->keymap, key);
4759 /* Some low-level request handling. This keeps access to
4760 * status_win restricted. */
4764 char *cmd = read_prompt(":");
4766 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4767 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4768 opt_request = REQ_VIEW_DIFF;
4770 opt_request = REQ_VIEW_PAGER;
4779 case REQ_SEARCH_BACK:
4781 const char *prompt = request == REQ_SEARCH
4783 char *search = read_prompt(prompt);
4786 string_ncopy(opt_search, search, strlen(search));
4791 case REQ_SCREEN_RESIZE:
4795 getmaxyx(stdscr, height, width);
4797 /* Resize the status view and let the view driver take
4798 * care of resizing the displayed views. */
4799 wresize(status_win, 1, width);
4800 mvwin(status_win, height - 1, 0);
4801 wrefresh(status_win);