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_editor[SIZEOF_STR] = "";
451 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
461 int namelen = strlen(name);
465 if (strncmp(opt, name, namelen))
468 if (opt[namelen] == '=')
469 value = opt + namelen + 1;
472 if (!short_name || opt[1] != short_name)
477 va_start(args, type);
478 if (type == OPT_INT) {
479 number = va_arg(args, int *);
481 *number = atoi(value);
488 /* Returns the index of log or diff command or -1 to exit. */
490 parse_options(int argc, char *argv[])
494 for (i = 1; i < argc; i++) {
497 if (!strcmp(opt, "log") ||
498 !strcmp(opt, "diff") ||
499 !strcmp(opt, "show")) {
500 opt_request = opt[0] == 'l'
501 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
505 if (opt[0] && opt[0] != '-')
508 if (!strcmp(opt, "-l")) {
509 opt_request = REQ_VIEW_LOG;
513 if (!strcmp(opt, "-d")) {
514 opt_request = REQ_VIEW_DIFF;
518 if (!strcmp(opt, "-S")) {
519 opt_request = REQ_VIEW_STATUS;
523 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
524 opt_line_number = TRUE;
528 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
529 opt_tab_size = MIN(opt_tab_size, TABSIZE);
533 if (check_option(opt, 'v', "version", OPT_NONE)) {
534 printf("tig version %s\n", TIG_VERSION);
538 if (check_option(opt, 'h', "help", OPT_NONE)) {
543 if (!strcmp(opt, "--")) {
548 die("unknown option '%s'\n\n%s", opt, usage);
551 if (!isatty(STDIN_FILENO)) {
552 opt_request = REQ_VIEW_PAGER;
555 } else if (i < argc) {
558 if (opt_request == REQ_VIEW_MAIN)
559 /* XXX: This is vulnerable to the user overriding
560 * options required for the main view parser. */
561 string_copy(opt_cmd, "git log --pretty=raw");
563 string_copy(opt_cmd, "git");
564 buf_size = strlen(opt_cmd);
566 while (buf_size < sizeof(opt_cmd) && i < argc) {
567 opt_cmd[buf_size++] = ' ';
568 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
571 if (buf_size >= sizeof(opt_cmd))
572 die("command too long");
574 opt_cmd[buf_size] = 0;
577 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
585 * Line-oriented content detection.
589 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
590 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
591 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
592 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
593 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
594 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
595 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
599 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
600 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
603 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
604 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
605 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
606 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
607 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
608 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
609 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
610 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
611 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
612 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
613 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
614 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
615 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
616 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
618 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
619 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
620 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
621 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
622 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
623 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
624 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
625 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
626 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
627 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
628 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
629 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
630 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
631 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
632 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
633 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
634 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
635 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
638 #define LINE(type, line, fg, bg, attr) \
645 const char *name; /* Option name. */
646 int namelen; /* Size of option name. */
647 const char *line; /* The start of line to match. */
648 int linelen; /* Size of string to match. */
649 int fg, bg, attr; /* Color and text attributes for the lines. */
652 static struct line_info line_info[] = {
653 #define LINE(type, line, fg, bg, attr) \
654 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
659 static enum line_type
660 get_line_type(char *line)
662 int linelen = strlen(line);
665 for (type = 0; type < ARRAY_SIZE(line_info); type++)
666 /* Case insensitive search matches Signed-off-by lines better. */
667 if (linelen >= line_info[type].linelen &&
668 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
675 get_line_attr(enum line_type type)
677 assert(type < ARRAY_SIZE(line_info));
678 return COLOR_PAIR(type) | line_info[type].attr;
681 static struct line_info *
682 get_line_info(char *name, int namelen)
686 for (type = 0; type < ARRAY_SIZE(line_info); type++)
687 if (namelen == line_info[type].namelen &&
688 !string_enum_compare(line_info[type].name, name, namelen))
689 return &line_info[type];
697 int default_bg = COLOR_BLACK;
698 int default_fg = COLOR_WHITE;
703 if (use_default_colors() != ERR) {
708 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
709 struct line_info *info = &line_info[type];
710 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
711 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
713 init_pair(type, fg, bg);
721 unsigned int selected:1;
723 void *data; /* User data */
733 enum request request;
734 struct keybinding *next;
737 static struct keybinding default_keybindings[] = {
739 { 'm', REQ_VIEW_MAIN },
740 { 'd', REQ_VIEW_DIFF },
741 { 'l', REQ_VIEW_LOG },
742 { 't', REQ_VIEW_TREE },
743 { 'f', REQ_VIEW_BLOB },
744 { 'p', REQ_VIEW_PAGER },
745 { 'h', REQ_VIEW_HELP },
746 { 'S', REQ_VIEW_STATUS },
747 { 'c', REQ_VIEW_STAGE },
749 /* View manipulation */
750 { 'q', REQ_VIEW_CLOSE },
751 { KEY_TAB, REQ_VIEW_NEXT },
752 { KEY_RETURN, REQ_ENTER },
753 { KEY_UP, REQ_PREVIOUS },
754 { KEY_DOWN, REQ_NEXT },
755 { 'R', REQ_REFRESH },
757 /* Cursor navigation */
758 { 'k', REQ_MOVE_UP },
759 { 'j', REQ_MOVE_DOWN },
760 { KEY_HOME, REQ_MOVE_FIRST_LINE },
761 { KEY_END, REQ_MOVE_LAST_LINE },
762 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
763 { ' ', REQ_MOVE_PAGE_DOWN },
764 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
765 { 'b', REQ_MOVE_PAGE_UP },
766 { '-', REQ_MOVE_PAGE_UP },
769 { KEY_IC, REQ_SCROLL_LINE_UP },
770 { KEY_DC, REQ_SCROLL_LINE_DOWN },
771 { 'w', REQ_SCROLL_PAGE_UP },
772 { 's', REQ_SCROLL_PAGE_DOWN },
776 { '?', REQ_SEARCH_BACK },
777 { 'n', REQ_FIND_NEXT },
778 { 'N', REQ_FIND_PREV },
782 { 'z', REQ_STOP_LOADING },
783 { 'v', REQ_SHOW_VERSION },
784 { 'r', REQ_SCREEN_REDRAW },
785 { '.', REQ_TOGGLE_LINENO },
786 { 'g', REQ_TOGGLE_REV_GRAPH },
788 { 'u', REQ_STATUS_UPDATE },
790 { 'C', REQ_CHERRY_PICK },
792 /* Using the ncurses SIGWINCH handler. */
793 { KEY_RESIZE, REQ_SCREEN_RESIZE },
796 #define KEYMAP_INFO \
809 #define KEYMAP_(name) KEYMAP_##name
814 static struct int_map keymap_table[] = {
815 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
820 #define set_keymap(map, name) \
821 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
823 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
826 add_keybinding(enum keymap keymap, enum request request, int key)
828 struct keybinding *keybinding;
830 keybinding = calloc(1, sizeof(*keybinding));
832 die("Failed to allocate keybinding");
834 keybinding->alias = key;
835 keybinding->request = request;
836 keybinding->next = keybindings[keymap];
837 keybindings[keymap] = keybinding;
840 /* Looks for a key binding first in the given map, then in the generic map, and
841 * lastly in the default keybindings. */
843 get_keybinding(enum keymap keymap, int key)
845 struct keybinding *kbd;
848 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
849 if (kbd->alias == key)
852 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
853 if (kbd->alias == key)
856 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
857 if (default_keybindings[i].alias == key)
858 return default_keybindings[i].request;
860 return (enum request) key;
869 static struct key key_table[] = {
870 { "Enter", KEY_RETURN },
872 { "Backspace", KEY_BACKSPACE },
874 { "Escape", KEY_ESC },
875 { "Left", KEY_LEFT },
876 { "Right", KEY_RIGHT },
878 { "Down", KEY_DOWN },
879 { "Insert", KEY_IC },
880 { "Delete", KEY_DC },
882 { "Home", KEY_HOME },
884 { "PageUp", KEY_PPAGE },
885 { "PageDown", KEY_NPAGE },
895 { "F10", KEY_F(10) },
896 { "F11", KEY_F(11) },
897 { "F12", KEY_F(12) },
901 get_key_value(const char *name)
905 for (i = 0; i < ARRAY_SIZE(key_table); i++)
906 if (!strcasecmp(key_table[i].name, name))
907 return key_table[i].value;
909 if (strlen(name) == 1 && isprint(*name))
916 get_key(enum request request)
918 static char buf[BUFSIZ];
919 static char key_char[] = "'X'";
926 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
927 struct keybinding *keybinding = &default_keybindings[i];
931 if (keybinding->request != request)
934 for (key = 0; key < ARRAY_SIZE(key_table); key++)
935 if (key_table[key].value == keybinding->alias)
936 seq = key_table[key].name;
939 keybinding->alias < 127 &&
940 isprint(keybinding->alias)) {
941 key_char[1] = (char) keybinding->alias;
948 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
949 return "Too many keybindings!";
958 * User config file handling.
961 static struct int_map color_map[] = {
962 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
974 #define set_color(color, name) \
975 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
977 static struct int_map attr_map[] = {
978 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
988 #define set_attribute(attr, name) \
989 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
991 static int config_lineno;
992 static bool config_errors;
993 static char *config_msg;
995 /* Wants: object fgcolor bgcolor [attr] */
997 option_color_command(int argc, char *argv[])
999 struct line_info *info;
1001 if (argc != 3 && argc != 4) {
1002 config_msg = "Wrong number of arguments given to color command";
1006 info = get_line_info(argv[0], strlen(argv[0]));
1008 config_msg = "Unknown color name";
1012 if (set_color(&info->fg, argv[1]) == ERR ||
1013 set_color(&info->bg, argv[2]) == ERR) {
1014 config_msg = "Unknown color";
1018 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1019 config_msg = "Unknown attribute";
1026 /* Wants: name = value */
1028 option_set_command(int argc, char *argv[])
1031 config_msg = "Wrong number of arguments given to set command";
1035 if (strcmp(argv[1], "=")) {
1036 config_msg = "No value assigned";
1040 if (!strcmp(argv[0], "show-rev-graph")) {
1041 opt_rev_graph = (!strcmp(argv[2], "1") ||
1042 !strcmp(argv[2], "true") ||
1043 !strcmp(argv[2], "yes"));
1047 if (!strcmp(argv[0], "line-number-interval")) {
1048 opt_num_interval = atoi(argv[2]);
1052 if (!strcmp(argv[0], "tab-size")) {
1053 opt_tab_size = atoi(argv[2]);
1057 if (!strcmp(argv[0], "commit-encoding")) {
1058 char *arg = argv[2];
1059 int delimiter = *arg;
1062 switch (delimiter) {
1065 for (arg++, i = 0; arg[i]; i++)
1066 if (arg[i] == delimiter) {
1071 string_ncopy(opt_encoding, arg, strlen(arg));
1076 config_msg = "Unknown variable name";
1080 /* Wants: mode request key */
1082 option_bind_command(int argc, char *argv[])
1084 enum request request;
1089 config_msg = "Wrong number of arguments given to bind command";
1093 if (set_keymap(&keymap, argv[0]) == ERR) {
1094 config_msg = "Unknown key map";
1098 key = get_key_value(argv[1]);
1100 config_msg = "Unknown key";
1104 request = get_request(argv[2]);
1105 if (request == REQ_UNKNOWN) {
1106 config_msg = "Unknown request name";
1110 add_keybinding(keymap, request, key);
1116 set_option(char *opt, char *value)
1123 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1124 argv[argc++] = value;
1131 while (isspace(*value))
1135 if (!strcmp(opt, "color"))
1136 return option_color_command(argc, argv);
1138 if (!strcmp(opt, "set"))
1139 return option_set_command(argc, argv);
1141 if (!strcmp(opt, "bind"))
1142 return option_bind_command(argc, argv);
1144 config_msg = "Unknown option command";
1149 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1154 config_msg = "Internal error";
1156 /* Check for comment markers, since read_properties() will
1157 * only ensure opt and value are split at first " \t". */
1158 optlen = strcspn(opt, "#");
1162 if (opt[optlen] != 0) {
1163 config_msg = "No option value";
1167 /* Look for comment endings in the value. */
1168 size_t len = strcspn(value, "#");
1170 if (len < valuelen) {
1172 value[valuelen] = 0;
1175 status = set_option(opt, value);
1178 if (status == ERR) {
1179 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1180 config_lineno, (int) optlen, opt, config_msg);
1181 config_errors = TRUE;
1184 /* Always keep going if errors are encountered. */
1191 char *home = getenv("HOME");
1192 char buf[SIZEOF_STR];
1196 config_errors = FALSE;
1198 if (!home || !string_format(buf, "%s/.tigrc", home))
1201 /* It's ok that the file doesn't exist. */
1202 file = fopen(buf, "r");
1206 if (read_properties(file, " \t", read_option) == ERR ||
1207 config_errors == TRUE)
1208 fprintf(stderr, "Errors while loading %s.\n", buf);
1221 /* The display array of active views and the index of the current view. */
1222 static struct view *display[2];
1223 static unsigned int current_view;
1225 /* Reading from the prompt? */
1226 static bool input_mode = FALSE;
1228 #define foreach_displayed_view(view, i) \
1229 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1231 #define displayed_views() (display[1] != NULL ? 2 : 1)
1233 /* Current head and commit ID */
1234 static char ref_blob[SIZEOF_REF] = "";
1235 static char ref_commit[SIZEOF_REF] = "HEAD";
1236 static char ref_head[SIZEOF_REF] = "HEAD";
1239 const char *name; /* View name */
1240 const char *cmd_fmt; /* Default command line format */
1241 const char *cmd_env; /* Command line set via environment */
1242 const char *id; /* Points to either of ref_{head,commit,blob} */
1244 struct view_ops *ops; /* View operations */
1246 enum keymap keymap; /* What keymap does this view have */
1248 char cmd[SIZEOF_STR]; /* Command buffer */
1249 char ref[SIZEOF_REF]; /* Hovered commit reference */
1250 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1252 int height, width; /* The width and height of the main window */
1253 WINDOW *win; /* The main window */
1254 WINDOW *title; /* The title window living below the main window */
1257 unsigned long offset; /* Offset of the window top */
1258 unsigned long lineno; /* Current line number */
1261 char grep[SIZEOF_STR]; /* Search string */
1262 regex_t *regex; /* Pre-compiled regex */
1264 /* If non-NULL, points to the view that opened this view. If this view
1265 * is closed tig will switch back to the parent view. */
1266 struct view *parent;
1269 unsigned long lines; /* Total number of lines */
1270 struct line *line; /* Line index */
1271 unsigned long line_size;/* Total number of allocated lines */
1272 unsigned int digits; /* Number of digits in the lines member. */
1280 /* What type of content being displayed. Used in the title bar. */
1282 /* Open and reads in all view content. */
1283 bool (*open)(struct view *view);
1284 /* Read one line; updates view->line. */
1285 bool (*read)(struct view *view, char *data);
1286 /* Draw one line; @lineno must be < view->height. */
1287 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1288 /* Depending on view handle a special requests. */
1289 enum request (*request)(struct view *view, enum request request, struct line *line);
1290 /* Search for regex in a line. */
1291 bool (*grep)(struct view *view, struct line *line);
1293 void (*select)(struct view *view, struct line *line);
1296 static struct view_ops pager_ops;
1297 static struct view_ops main_ops;
1298 static struct view_ops tree_ops;
1299 static struct view_ops blob_ops;
1300 static struct view_ops help_ops;
1301 static struct view_ops status_ops;
1302 static struct view_ops stage_ops;
1304 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1305 { name, cmd, #env, ref, ops, map}
1307 #define VIEW_(id, name, ops, ref) \
1308 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1311 static struct view views[] = {
1312 VIEW_(MAIN, "main", &main_ops, ref_head),
1313 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1314 VIEW_(LOG, "log", &pager_ops, ref_head),
1315 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1316 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1317 VIEW_(HELP, "help", &help_ops, ""),
1318 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1319 VIEW_(STATUS, "status", &status_ops, ""),
1320 VIEW_(STAGE, "stage", &stage_ops, ""),
1323 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1325 #define foreach_view(view, i) \
1326 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1328 #define view_is_displayed(view) \
1329 (view == display[0] || view == display[1])
1332 draw_view_line(struct view *view, unsigned int lineno)
1335 bool selected = (view->offset + lineno == view->lineno);
1338 assert(view_is_displayed(view));
1340 if (view->offset + lineno >= view->lines)
1343 line = &view->line[view->offset + lineno];
1346 line->selected = TRUE;
1347 view->ops->select(view, line);
1348 } else if (line->selected) {
1349 line->selected = FALSE;
1350 wmove(view->win, lineno, 0);
1351 wclrtoeol(view->win);
1354 scrollok(view->win, FALSE);
1355 draw_ok = view->ops->draw(view, line, lineno, selected);
1356 scrollok(view->win, TRUE);
1362 redraw_view_from(struct view *view, int lineno)
1364 assert(0 <= lineno && lineno < view->height);
1366 for (; lineno < view->height; lineno++) {
1367 if (!draw_view_line(view, lineno))
1371 redrawwin(view->win);
1373 wnoutrefresh(view->win);
1375 wrefresh(view->win);
1379 redraw_view(struct view *view)
1382 redraw_view_from(view, 0);
1387 update_view_title(struct view *view)
1389 char buf[SIZEOF_STR];
1390 char state[SIZEOF_STR];
1391 size_t bufpos = 0, statelen = 0;
1393 assert(view_is_displayed(view));
1395 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1396 unsigned int view_lines = view->offset + view->height;
1397 unsigned int lines = view->lines
1398 ? MIN(view_lines, view->lines) * 100 / view->lines
1401 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1408 time_t secs = time(NULL) - view->start_time;
1410 /* Three git seconds are a long time ... */
1412 string_format_from(state, &statelen, " %lds", secs);
1416 string_format_from(buf, &bufpos, "[%s]", view->name);
1417 if (*view->ref && bufpos < view->width) {
1418 size_t refsize = strlen(view->ref);
1419 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1421 if (minsize < view->width)
1422 refsize = view->width - minsize + 7;
1423 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1426 if (statelen && bufpos < view->width) {
1427 string_format_from(buf, &bufpos, " %s", state);
1430 if (view == display[current_view])
1431 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1433 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1435 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1436 wclrtoeol(view->title);
1437 wmove(view->title, 0, view->width - 1);
1440 wnoutrefresh(view->title);
1442 wrefresh(view->title);
1446 resize_display(void)
1449 struct view *base = display[0];
1450 struct view *view = display[1] ? display[1] : display[0];
1452 /* Setup window dimensions */
1454 getmaxyx(stdscr, base->height, base->width);
1456 /* Make room for the status window. */
1460 /* Horizontal split. */
1461 view->width = base->width;
1462 view->height = SCALE_SPLIT_VIEW(base->height);
1463 base->height -= view->height;
1465 /* Make room for the title bar. */
1469 /* Make room for the title bar. */
1474 foreach_displayed_view (view, i) {
1476 view->win = newwin(view->height, 0, offset, 0);
1478 die("Failed to create %s view", view->name);
1480 scrollok(view->win, TRUE);
1482 view->title = newwin(1, 0, offset + view->height, 0);
1484 die("Failed to create title window");
1487 wresize(view->win, view->height, view->width);
1488 mvwin(view->win, offset, 0);
1489 mvwin(view->title, offset + view->height, 0);
1492 offset += view->height + 1;
1497 redraw_display(void)
1502 foreach_displayed_view (view, i) {
1504 update_view_title(view);
1509 update_display_cursor(struct view *view)
1511 /* Move the cursor to the right-most column of the cursor line.
1513 * XXX: This could turn out to be a bit expensive, but it ensures that
1514 * the cursor does not jump around. */
1516 wmove(view->win, view->lineno - view->offset, view->width - 1);
1517 wrefresh(view->win);
1525 /* Scrolling backend */
1527 do_scroll_view(struct view *view, int lines)
1529 bool redraw_current_line = FALSE;
1531 /* The rendering expects the new offset. */
1532 view->offset += lines;
1534 assert(0 <= view->offset && view->offset < view->lines);
1537 /* Move current line into the view. */
1538 if (view->lineno < view->offset) {
1539 view->lineno = view->offset;
1540 redraw_current_line = TRUE;
1541 } else if (view->lineno >= view->offset + view->height) {
1542 view->lineno = view->offset + view->height - 1;
1543 redraw_current_line = TRUE;
1546 assert(view->offset <= view->lineno && view->lineno < view->lines);
1548 /* Redraw the whole screen if scrolling is pointless. */
1549 if (view->height < ABS(lines)) {
1553 int line = lines > 0 ? view->height - lines : 0;
1554 int end = line + ABS(lines);
1556 wscrl(view->win, lines);
1558 for (; line < end; line++) {
1559 if (!draw_view_line(view, line))
1563 if (redraw_current_line)
1564 draw_view_line(view, view->lineno - view->offset);
1567 redrawwin(view->win);
1568 wrefresh(view->win);
1572 /* Scroll frontend */
1574 scroll_view(struct view *view, enum request request)
1578 assert(view_is_displayed(view));
1581 case REQ_SCROLL_PAGE_DOWN:
1582 lines = view->height;
1583 case REQ_SCROLL_LINE_DOWN:
1584 if (view->offset + lines > view->lines)
1585 lines = view->lines - view->offset;
1587 if (lines == 0 || view->offset + view->height >= view->lines) {
1588 report("Cannot scroll beyond the last line");
1593 case REQ_SCROLL_PAGE_UP:
1594 lines = view->height;
1595 case REQ_SCROLL_LINE_UP:
1596 if (lines > view->offset)
1597 lines = view->offset;
1600 report("Cannot scroll beyond the first line");
1608 die("request %d not handled in switch", request);
1611 do_scroll_view(view, lines);
1616 move_view(struct view *view, enum request request)
1618 int scroll_steps = 0;
1622 case REQ_MOVE_FIRST_LINE:
1623 steps = -view->lineno;
1626 case REQ_MOVE_LAST_LINE:
1627 steps = view->lines - view->lineno - 1;
1630 case REQ_MOVE_PAGE_UP:
1631 steps = view->height > view->lineno
1632 ? -view->lineno : -view->height;
1635 case REQ_MOVE_PAGE_DOWN:
1636 steps = view->lineno + view->height >= view->lines
1637 ? view->lines - view->lineno - 1 : view->height;
1649 die("request %d not handled in switch", request);
1652 if (steps <= 0 && view->lineno == 0) {
1653 report("Cannot move beyond the first line");
1656 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1657 report("Cannot move beyond the last line");
1661 /* Move the current line */
1662 view->lineno += steps;
1663 assert(0 <= view->lineno && view->lineno < view->lines);
1665 /* Check whether the view needs to be scrolled */
1666 if (view->lineno < view->offset ||
1667 view->lineno >= view->offset + view->height) {
1668 scroll_steps = steps;
1669 if (steps < 0 && -steps > view->offset) {
1670 scroll_steps = -view->offset;
1672 } else if (steps > 0) {
1673 if (view->lineno == view->lines - 1 &&
1674 view->lines > view->height) {
1675 scroll_steps = view->lines - view->offset - 1;
1676 if (scroll_steps >= view->height)
1677 scroll_steps -= view->height - 1;
1682 if (!view_is_displayed(view)) {
1683 view->offset += scroll_steps;
1684 assert(0 <= view->offset && view->offset < view->lines);
1685 view->ops->select(view, &view->line[view->lineno]);
1689 /* Repaint the old "current" line if we be scrolling */
1690 if (ABS(steps) < view->height)
1691 draw_view_line(view, view->lineno - steps - view->offset);
1694 do_scroll_view(view, scroll_steps);
1698 /* Draw the current line */
1699 draw_view_line(view, view->lineno - view->offset);
1701 redrawwin(view->win);
1702 wrefresh(view->win);
1711 static void search_view(struct view *view, enum request request);
1714 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1716 assert(view_is_displayed(view));
1718 if (!view->ops->grep(view, line))
1721 if (lineno - view->offset >= view->height) {
1722 view->offset = lineno;
1723 view->lineno = lineno;
1727 unsigned long old_lineno = view->lineno - view->offset;
1729 view->lineno = lineno;
1730 draw_view_line(view, old_lineno);
1732 draw_view_line(view, view->lineno - view->offset);
1733 redrawwin(view->win);
1734 wrefresh(view->win);
1737 report("Line %ld matches '%s'", lineno + 1, view->grep);
1742 find_next(struct view *view, enum request request)
1744 unsigned long lineno = view->lineno;
1749 report("No previous search");
1751 search_view(view, request);
1761 case REQ_SEARCH_BACK:
1770 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1771 lineno += direction;
1773 /* Note, lineno is unsigned long so will wrap around in which case it
1774 * will become bigger than view->lines. */
1775 for (; lineno < view->lines; lineno += direction) {
1776 struct line *line = &view->line[lineno];
1778 if (find_next_line(view, lineno, line))
1782 report("No match found for '%s'", view->grep);
1786 search_view(struct view *view, enum request request)
1791 regfree(view->regex);
1794 view->regex = calloc(1, sizeof(*view->regex));
1799 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1800 if (regex_err != 0) {
1801 char buf[SIZEOF_STR] = "unknown error";
1803 regerror(regex_err, view->regex, buf, sizeof(buf));
1804 report("Search failed: %s", buf);
1808 string_copy(view->grep, opt_search);
1810 find_next(view, request);
1814 * Incremental updating
1818 end_update(struct view *view)
1822 set_nonblocking_input(FALSE);
1823 if (view->pipe == stdin)
1831 begin_update(struct view *view)
1837 string_copy(view->cmd, opt_cmd);
1839 /* When running random commands, initially show the
1840 * command in the title. However, it maybe later be
1841 * overwritten if a commit line is selected. */
1842 if (view == VIEW(REQ_VIEW_PAGER))
1843 string_copy(view->ref, view->cmd);
1847 } else if (view == VIEW(REQ_VIEW_TREE)) {
1848 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1849 char path[SIZEOF_STR];
1851 if (strcmp(view->vid, view->id))
1852 opt_path[0] = path[0] = 0;
1853 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1856 if (!string_format(view->cmd, format, view->id, path))
1860 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1861 const char *id = view->id;
1863 if (!string_format(view->cmd, format, id, id, id, id, id))
1866 /* Put the current ref_* value to the view title ref
1867 * member. This is needed by the blob view. Most other
1868 * views sets it automatically after loading because the
1869 * first line is a commit line. */
1870 string_copy_rev(view->ref, view->id);
1873 /* Special case for the pager view. */
1875 view->pipe = opt_pipe;
1878 view->pipe = popen(view->cmd, "r");
1884 set_nonblocking_input(TRUE);
1889 string_copy_rev(view->vid, view->id);
1894 for (i = 0; i < view->lines; i++)
1895 if (view->line[i].data)
1896 free(view->line[i].data);
1902 view->start_time = time(NULL);
1907 static struct line *
1908 realloc_lines(struct view *view, size_t line_size)
1910 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1916 view->line_size = line_size;
1921 update_view(struct view *view)
1923 char in_buffer[BUFSIZ];
1924 char out_buffer[BUFSIZ * 2];
1926 /* The number of lines to read. If too low it will cause too much
1927 * redrawing (and possible flickering), if too high responsiveness
1929 unsigned long lines = view->height;
1930 int redraw_from = -1;
1935 /* Only redraw if lines are visible. */
1936 if (view->offset + view->height >= view->lines)
1937 redraw_from = view->lines - view->offset;
1939 /* FIXME: This is probably not perfect for backgrounded views. */
1940 if (!realloc_lines(view, view->lines + lines))
1943 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1944 size_t linelen = strlen(line);
1947 line[linelen - 1] = 0;
1949 if (opt_iconv != ICONV_NONE) {
1950 ICONV_CONST char *inbuf = line;
1951 size_t inlen = linelen;
1953 char *outbuf = out_buffer;
1954 size_t outlen = sizeof(out_buffer);
1958 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1959 if (ret != (size_t) -1) {
1961 linelen = strlen(out_buffer);
1965 if (!view->ops->read(view, line))
1975 lines = view->lines;
1976 for (digits = 0; lines; digits++)
1979 /* Keep the displayed view in sync with line number scaling. */
1980 if (digits != view->digits) {
1981 view->digits = digits;
1986 if (!view_is_displayed(view))
1989 if (view == VIEW(REQ_VIEW_TREE)) {
1990 /* Clear the view and redraw everything since the tree sorting
1991 * might have rearranged things. */
1994 } else if (redraw_from >= 0) {
1995 /* If this is an incremental update, redraw the previous line
1996 * since for commits some members could have changed when
1997 * loading the main view. */
1998 if (redraw_from > 0)
2001 /* Since revision graph visualization requires knowledge
2002 * about the parent commit, it causes a further one-off
2003 * needed to be redrawn for incremental updates. */
2004 if (redraw_from > 0 && opt_rev_graph)
2007 /* Incrementally draw avoids flickering. */
2008 redraw_view_from(view, redraw_from);
2011 /* Update the title _after_ the redraw so that if the redraw picks up a
2012 * commit reference in view->ref it'll be available here. */
2013 update_view_title(view);
2016 if (ferror(view->pipe)) {
2017 report("Failed to read: %s", strerror(errno));
2020 } else if (feof(view->pipe)) {
2028 report("Allocation failure");
2031 view->ops->read(view, NULL);
2036 static struct line *
2037 add_line_data(struct view *view, void *data, enum line_type type)
2039 struct line *line = &view->line[view->lines++];
2041 memset(line, 0, sizeof(*line));
2048 static struct line *
2049 add_line_text(struct view *view, char *data, enum line_type type)
2052 data = strdup(data);
2054 return data ? add_line_data(view, data, type) : NULL;
2063 OPEN_DEFAULT = 0, /* Use default view switching. */
2064 OPEN_SPLIT = 1, /* Split current view. */
2065 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2066 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2070 open_view(struct view *prev, enum request request, enum open_flags flags)
2072 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2073 bool split = !!(flags & OPEN_SPLIT);
2074 bool reload = !!(flags & OPEN_RELOAD);
2075 struct view *view = VIEW(request);
2076 int nviews = displayed_views();
2077 struct view *base_view = display[0];
2079 if (view == prev && nviews == 1 && !reload) {
2080 report("Already in %s view", view->name);
2084 if (view->ops->open) {
2085 if (!view->ops->open(view)) {
2086 report("Failed to load %s view", view->name);
2090 } else if ((reload || strcmp(view->vid, view->id)) &&
2091 !begin_update(view)) {
2092 report("Failed to load %s view", view->name);
2101 /* Maximize the current view. */
2102 memset(display, 0, sizeof(display));
2104 display[current_view] = view;
2107 /* Resize the view when switching between split- and full-screen,
2108 * or when switching between two different full-screen views. */
2109 if (nviews != displayed_views() ||
2110 (nviews == 1 && base_view != display[0]))
2113 if (split && prev->lineno - prev->offset >= prev->height) {
2114 /* Take the title line into account. */
2115 int lines = prev->lineno - prev->offset - prev->height + 1;
2117 /* Scroll the view that was split if the current line is
2118 * outside the new limited view. */
2119 do_scroll_view(prev, lines);
2122 if (prev && view != prev) {
2123 if (split && !backgrounded) {
2124 /* "Blur" the previous view. */
2125 update_view_title(prev);
2128 view->parent = prev;
2131 if (view->pipe && view->lines == 0) {
2132 /* Clear the old view and let the incremental updating refill
2141 /* If the view is backgrounded the above calls to report()
2142 * won't redraw the view title. */
2144 update_view_title(view);
2148 open_editor(bool from_root, char *file)
2150 char cmd[SIZEOF_STR];
2151 char file_sq[SIZEOF_STR];
2153 char *prefix = from_root ? opt_cdup : "";
2155 editor = getenv("GIT_EDITOR");
2156 if (!editor && *opt_editor)
2157 editor = opt_editor;
2159 editor = getenv("VISUAL");
2161 editor = getenv("EDITOR");
2165 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2166 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2167 def_prog_mode(); /* save current tty modes */
2168 endwin(); /* restore original tty modes */
2176 * User request switch noodle
2180 view_driver(struct view *view, enum request request)
2184 if (request == REQ_NONE) {
2189 if (view && view->lines) {
2190 request = view->ops->request(view, request, &view->line[view->lineno]);
2191 if (request == REQ_NONE)
2198 case REQ_MOVE_PAGE_UP:
2199 case REQ_MOVE_PAGE_DOWN:
2200 case REQ_MOVE_FIRST_LINE:
2201 case REQ_MOVE_LAST_LINE:
2202 move_view(view, request);
2205 case REQ_SCROLL_LINE_DOWN:
2206 case REQ_SCROLL_LINE_UP:
2207 case REQ_SCROLL_PAGE_DOWN:
2208 case REQ_SCROLL_PAGE_UP:
2209 scroll_view(view, request);
2214 report("No file chosen, press %s to open tree view",
2215 get_key(REQ_VIEW_TREE));
2218 open_view(view, request, OPEN_DEFAULT);
2221 case REQ_VIEW_PAGER:
2222 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2223 report("No pager content, press %s to run command from prompt",
2224 get_key(REQ_PROMPT));
2227 open_view(view, request, OPEN_DEFAULT);
2230 case REQ_VIEW_STAGE:
2231 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2232 report("No stage content, press %s to open the status view and choose file",
2233 get_key(REQ_VIEW_STATUS));
2236 open_view(view, request, OPEN_DEFAULT);
2244 case REQ_VIEW_STATUS:
2245 open_view(view, request, OPEN_DEFAULT);
2250 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2252 if ((view == VIEW(REQ_VIEW_DIFF) &&
2253 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2254 (view == VIEW(REQ_VIEW_STAGE) &&
2255 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2256 (view == VIEW(REQ_VIEW_BLOB) &&
2257 view->parent == VIEW(REQ_VIEW_TREE))) {
2260 view = view->parent;
2261 line = view->lineno;
2262 move_view(view, request);
2263 if (view_is_displayed(view))
2264 update_view_title(view);
2265 if (line != view->lineno)
2266 view->ops->request(view, REQ_ENTER,
2267 &view->line[view->lineno]);
2270 move_view(view, request);
2276 int nviews = displayed_views();
2277 int next_view = (current_view + 1) % nviews;
2279 if (next_view == current_view) {
2280 report("Only one view is displayed");
2284 current_view = next_view;
2285 /* Blur out the title of the previous view. */
2286 update_view_title(view);
2291 report("Refreshing is not yet supported for the %s view", view->name);
2294 case REQ_TOGGLE_LINENO:
2295 opt_line_number = !opt_line_number;
2299 case REQ_TOGGLE_REV_GRAPH:
2300 opt_rev_graph = !opt_rev_graph;
2305 /* Always reload^Wrerun commands from the prompt. */
2306 open_view(view, opt_request, OPEN_RELOAD);
2310 case REQ_SEARCH_BACK:
2311 search_view(view, request);
2316 find_next(view, request);
2319 case REQ_STOP_LOADING:
2320 for (i = 0; i < ARRAY_SIZE(views); i++) {
2323 report("Stopped loading the %s view", view->name),
2328 case REQ_SHOW_VERSION:
2329 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2332 case REQ_SCREEN_RESIZE:
2335 case REQ_SCREEN_REDRAW:
2340 report("Nothing to edit");
2343 case REQ_CHERRY_PICK:
2344 report("Nothing to cherry-pick");
2348 report("Nothing to enter");
2352 case REQ_VIEW_CLOSE:
2353 /* XXX: Mark closed views by letting view->parent point to the
2354 * view itself. Parents to closed view should never be
2357 view->parent->parent != view->parent) {
2358 memset(display, 0, sizeof(display));
2360 display[current_view] = view->parent;
2361 view->parent = view;
2371 /* An unknown key will show most commonly used commands. */
2372 report("Unknown key, press 'h' for help");
2385 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2387 char *text = line->data;
2388 enum line_type type = line->type;
2389 int textlen = strlen(text);
2392 wmove(view->win, lineno, 0);
2396 wchgat(view->win, -1, 0, type, NULL);
2399 attr = get_line_attr(type);
2400 wattrset(view->win, attr);
2402 if (opt_line_number || opt_tab_size < TABSIZE) {
2403 static char spaces[] = " ";
2404 int col_offset = 0, col = 0;
2406 if (opt_line_number) {
2407 unsigned long real_lineno = view->offset + lineno + 1;
2409 if (real_lineno == 1 ||
2410 (real_lineno % opt_num_interval) == 0) {
2411 wprintw(view->win, "%.*d", view->digits, real_lineno);
2414 waddnstr(view->win, spaces,
2415 MIN(view->digits, STRING_SIZE(spaces)));
2417 waddstr(view->win, ": ");
2418 col_offset = view->digits + 2;
2421 while (text && col_offset + col < view->width) {
2422 int cols_max = view->width - col_offset - col;
2426 if (*text == '\t') {
2428 assert(sizeof(spaces) > TABSIZE);
2430 cols = opt_tab_size - (col % opt_tab_size);
2433 text = strchr(text, '\t');
2434 cols = line ? text - pos : strlen(pos);
2437 waddnstr(view->win, pos, MIN(cols, cols_max));
2442 int col = 0, pos = 0;
2444 for (; pos < textlen && col < view->width; pos++, col++)
2445 if (text[pos] == '\t')
2446 col += TABSIZE - (col % TABSIZE) - 1;
2448 waddnstr(view->win, text, pos);
2455 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2457 char refbuf[SIZEOF_STR];
2461 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2464 pipe = popen(refbuf, "r");
2468 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2469 ref = chomp_string(ref);
2475 /* This is the only fatal call, since it can "corrupt" the buffer. */
2476 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2483 add_pager_refs(struct view *view, struct line *line)
2485 char buf[SIZEOF_STR];
2486 char *commit_id = line->data + STRING_SIZE("commit ");
2488 size_t bufpos = 0, refpos = 0;
2489 const char *sep = "Refs: ";
2490 bool is_tag = FALSE;
2492 assert(line->type == LINE_COMMIT);
2494 refs = get_refs(commit_id);
2496 if (view == VIEW(REQ_VIEW_DIFF))
2497 goto try_add_describe_ref;
2502 struct ref *ref = refs[refpos];
2503 char *fmt = ref->tag ? "%s[%s]" :
2504 ref->remote ? "%s<%s>" : "%s%s";
2506 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2511 } while (refs[refpos++]->next);
2513 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2514 try_add_describe_ref:
2515 /* Add <tag>-g<commit_id> "fake" reference. */
2516 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2523 if (!realloc_lines(view, view->line_size + 1))
2526 add_line_text(view, buf, LINE_PP_REFS);
2530 pager_read(struct view *view, char *data)
2537 line = add_line_text(view, data, get_line_type(data));
2541 if (line->type == LINE_COMMIT &&
2542 (view == VIEW(REQ_VIEW_DIFF) ||
2543 view == VIEW(REQ_VIEW_LOG)))
2544 add_pager_refs(view, line);
2550 pager_request(struct view *view, enum request request, struct line *line)
2554 if (request != REQ_ENTER)
2557 if (line->type == LINE_COMMIT &&
2558 (view == VIEW(REQ_VIEW_LOG) ||
2559 view == VIEW(REQ_VIEW_PAGER))) {
2560 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2564 /* Always scroll the view even if it was split. That way
2565 * you can use Enter to scroll through the log view and
2566 * split open each commit diff. */
2567 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2569 /* FIXME: A minor workaround. Scrolling the view will call report("")
2570 * but if we are scrolling a non-current view this won't properly
2571 * update the view title. */
2573 update_view_title(view);
2579 pager_grep(struct view *view, struct line *line)
2582 char *text = line->data;
2587 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2594 pager_select(struct view *view, struct line *line)
2596 if (line->type == LINE_COMMIT) {
2597 char *text = line->data + STRING_SIZE("commit ");
2599 if (view != VIEW(REQ_VIEW_PAGER))
2600 string_copy_rev(view->ref, text);
2601 string_copy_rev(ref_commit, text);
2605 static struct view_ops pager_ops = {
2621 help_open(struct view *view)
2624 int lines = ARRAY_SIZE(req_info) + 2;
2627 if (view->lines > 0)
2630 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2631 if (!req_info[i].request)
2634 view->line = calloc(lines, sizeof(*view->line));
2638 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2640 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2643 if (!req_info[i].request) {
2644 add_line_text(view, "", LINE_DEFAULT);
2645 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2649 key = get_key(req_info[i].request);
2650 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2653 add_line_text(view, buf, LINE_DEFAULT);
2659 static struct view_ops help_ops = {
2674 struct tree_stack_entry {
2675 struct tree_stack_entry *prev; /* Entry below this in the stack */
2676 unsigned long lineno; /* Line number to restore */
2677 char *name; /* Position of name in opt_path */
2680 /* The top of the path stack. */
2681 static struct tree_stack_entry *tree_stack = NULL;
2682 unsigned long tree_lineno = 0;
2685 pop_tree_stack_entry(void)
2687 struct tree_stack_entry *entry = tree_stack;
2689 tree_lineno = entry->lineno;
2691 tree_stack = entry->prev;
2696 push_tree_stack_entry(char *name, unsigned long lineno)
2698 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2699 size_t pathlen = strlen(opt_path);
2704 entry->prev = tree_stack;
2705 entry->name = opt_path + pathlen;
2708 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2709 pop_tree_stack_entry();
2713 /* Move the current line to the first tree entry. */
2715 entry->lineno = lineno;
2718 /* Parse output from git-ls-tree(1):
2720 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2721 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2722 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2723 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2726 #define SIZEOF_TREE_ATTR \
2727 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2729 #define TREE_UP_FORMAT "040000 tree %s\t.."
2732 tree_compare_entry(enum line_type type1, char *name1,
2733 enum line_type type2, char *name2)
2735 if (type1 != type2) {
2736 if (type1 == LINE_TREE_DIR)
2741 return strcmp(name1, name2);
2745 tree_read(struct view *view, char *text)
2747 size_t textlen = text ? strlen(text) : 0;
2748 char buf[SIZEOF_STR];
2750 enum line_type type;
2751 bool first_read = view->lines == 0;
2753 if (textlen <= SIZEOF_TREE_ATTR)
2756 type = text[STRING_SIZE("100644 ")] == 't'
2757 ? LINE_TREE_DIR : LINE_TREE_FILE;
2760 /* Add path info line */
2761 if (!string_format(buf, "Directory path /%s", opt_path) ||
2762 !realloc_lines(view, view->line_size + 1) ||
2763 !add_line_text(view, buf, LINE_DEFAULT))
2766 /* Insert "link" to parent directory. */
2768 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2769 !realloc_lines(view, view->line_size + 1) ||
2770 !add_line_text(view, buf, LINE_TREE_DIR))
2775 /* Strip the path part ... */
2777 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2778 size_t striplen = strlen(opt_path);
2779 char *path = text + SIZEOF_TREE_ATTR;
2781 if (pathlen > striplen)
2782 memmove(path, path + striplen,
2783 pathlen - striplen + 1);
2786 /* Skip "Directory ..." and ".." line. */
2787 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2788 struct line *line = &view->line[pos];
2789 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2790 char *path2 = text + SIZEOF_TREE_ATTR;
2791 int cmp = tree_compare_entry(line->type, path1, type, path2);
2796 text = strdup(text);
2800 if (view->lines > pos)
2801 memmove(&view->line[pos + 1], &view->line[pos],
2802 (view->lines - pos) * sizeof(*line));
2804 line = &view->line[pos];
2811 if (!add_line_text(view, text, type))
2814 if (tree_lineno > view->lineno) {
2815 view->lineno = tree_lineno;
2823 tree_request(struct view *view, enum request request, struct line *line)
2825 enum open_flags flags;
2827 if (request != REQ_ENTER)
2830 /* Cleanup the stack if the tree view is at a different tree. */
2831 while (!*opt_path && tree_stack)
2832 pop_tree_stack_entry();
2834 switch (line->type) {
2836 /* Depending on whether it is a subdir or parent (updir?) link
2837 * mangle the path buffer. */
2838 if (line == &view->line[1] && *opt_path) {
2839 pop_tree_stack_entry();
2842 char *data = line->data;
2843 char *basename = data + SIZEOF_TREE_ATTR;
2845 push_tree_stack_entry(basename, view->lineno);
2848 /* Trees and subtrees share the same ID, so they are not not
2849 * unique like blobs. */
2850 flags = OPEN_RELOAD;
2851 request = REQ_VIEW_TREE;
2854 case LINE_TREE_FILE:
2855 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2856 request = REQ_VIEW_BLOB;
2863 open_view(view, request, flags);
2864 if (request == REQ_VIEW_TREE) {
2865 view->lineno = tree_lineno;
2872 tree_select(struct view *view, struct line *line)
2874 char *text = line->data + STRING_SIZE("100644 blob ");
2876 if (line->type == LINE_TREE_FILE) {
2877 string_copy_rev(ref_blob, text);
2879 } else if (line->type != LINE_TREE_DIR) {
2883 string_copy_rev(view->ref, text);
2886 static struct view_ops tree_ops = {
2897 blob_read(struct view *view, char *line)
2899 return add_line_text(view, line, LINE_DEFAULT) != NULL;
2902 static struct view_ops blob_ops = {
2921 char rev[SIZEOF_REV];
2925 char rev[SIZEOF_REV];
2927 char name[SIZEOF_STR];
2930 static struct status stage_status;
2931 static enum line_type stage_line_type;
2933 /* Get fields from the diff line:
2934 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2937 status_get_diff(struct status *file, char *buf, size_t bufsize)
2939 char *old_mode = buf + 1;
2940 char *new_mode = buf + 8;
2941 char *old_rev = buf + 15;
2942 char *new_rev = buf + 56;
2943 char *status = buf + 97;
2945 if (bufsize != 99 ||
2946 old_mode[-1] != ':' ||
2947 new_mode[-1] != ' ' ||
2948 old_rev[-1] != ' ' ||
2949 new_rev[-1] != ' ' ||
2953 file->status = *status;
2955 string_copy_rev(file->old.rev, old_rev);
2956 string_copy_rev(file->new.rev, new_rev);
2958 file->old.mode = strtoul(old_mode, NULL, 8);
2959 file->new.mode = strtoul(new_mode, NULL, 8);
2967 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2969 struct status *file = NULL;
2970 char buf[SIZEOF_STR * 4];
2974 pipe = popen(cmd, "r");
2978 add_line_data(view, NULL, type);
2980 while (!feof(pipe) && !ferror(pipe)) {
2984 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2987 bufsize += readsize;
2989 /* Process while we have NUL chars. */
2990 while ((sep = memchr(buf, 0, bufsize))) {
2991 size_t sepsize = sep - buf + 1;
2994 if (!realloc_lines(view, view->line_size + 1))
2997 file = calloc(1, sizeof(*file));
3001 add_line_data(view, file, type);
3004 /* Parse diff info part. */
3008 } else if (!file->status) {
3009 if (!status_get_diff(file, buf, sepsize))
3013 memmove(buf, sep + 1, bufsize);
3015 sep = memchr(buf, 0, bufsize);
3018 sepsize = sep - buf + 1;
3021 /* git-ls-files just delivers a NUL separated
3022 * list of file names similar to the second half
3023 * of the git-diff-* output. */
3024 string_ncopy(file->name, buf, sepsize);
3026 memmove(buf, sep + 1, bufsize);
3037 if (!view->line[view->lines - 1].data)
3038 add_line_data(view, NULL, LINE_STAT_NONE);
3044 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
3045 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3046 #define STATUS_LIST_OTHER_CMD \
3047 "git ls-files -z --others --exclude-per-directory=.gitignore"
3049 #define STATUS_DIFF_SHOW_CMD \
3050 "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3052 /* First parse staged info using git-diff-index(1), then parse unstaged
3053 * info using git-diff-files(1), and finally untracked files using
3054 * git-ls-files(1). */
3056 status_open(struct view *view)
3058 struct stat statbuf;
3059 char exclude[SIZEOF_STR];
3060 char cmd[SIZEOF_STR];
3061 unsigned long prev_lineno = view->lineno;
3065 for (i = 0; i < view->lines; i++)
3066 free(view->line[i].data);
3068 view->lines = view->line_size = view->lineno = 0;
3071 if (!realloc_lines(view, view->line_size + 6))
3074 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3077 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3079 if (stat(exclude, &statbuf) >= 0) {
3080 size_t cmdsize = strlen(cmd);
3082 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3083 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3087 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3088 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3089 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3092 /* If all went well restore the previous line number to stay in
3094 if (prev_lineno < view->lines)
3095 view->lineno = prev_lineno;
3097 view->lineno = view->lines - 1;
3103 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3105 struct status *status = line->data;
3107 wmove(view->win, lineno, 0);
3110 wattrset(view->win, get_line_attr(LINE_CURSOR));
3111 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3113 } else if (!status && line->type != LINE_STAT_NONE) {
3114 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3115 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3118 wattrset(view->win, get_line_attr(line->type));
3124 switch (line->type) {
3125 case LINE_STAT_STAGED:
3126 text = "Changes to be committed:";
3129 case LINE_STAT_UNSTAGED:
3130 text = "Changed but not updated:";
3133 case LINE_STAT_UNTRACKED:
3134 text = "Untracked files:";
3137 case LINE_STAT_NONE:
3138 text = " (no files)";
3145 waddstr(view->win, text);
3149 waddch(view->win, status->status);
3151 wattrset(view->win, A_NORMAL);
3152 wmove(view->win, lineno, 4);
3153 waddstr(view->win, status->name);
3159 status_enter(struct view *view, struct line *line)
3161 struct status *status = line->data;
3162 char path[SIZEOF_STR] = "";
3166 if (line->type == LINE_STAT_NONE ||
3167 (!status && line[1].type == LINE_STAT_NONE)) {
3168 report("No file to diff");
3172 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3176 line->type != LINE_STAT_UNTRACKED &&
3177 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3180 switch (line->type) {
3181 case LINE_STAT_STAGED:
3182 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3186 info = "Staged changes to %s";
3188 info = "Staged changes";
3191 case LINE_STAT_UNSTAGED:
3192 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3196 info = "Unstaged changes to %s";
3198 info = "Unstaged changes";
3201 case LINE_STAT_UNTRACKED:
3207 report("No file to show");
3211 opt_pipe = fopen(status->name, "r");
3212 info = "Untracked file %s";
3219 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3220 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3222 stage_status = *status;
3224 memset(&stage_status, 0, sizeof(stage_status));
3227 stage_line_type = line->type;
3228 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3236 status_update_file(struct view *view, struct status *status, enum line_type type)
3238 char cmd[SIZEOF_STR];
3239 char buf[SIZEOF_STR];
3246 type != LINE_STAT_UNTRACKED &&
3247 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3251 case LINE_STAT_STAGED:
3252 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3258 string_add(cmd, cmdsize, "git update-index -z --index-info");
3261 case LINE_STAT_UNSTAGED:
3262 case LINE_STAT_UNTRACKED:
3263 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3266 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3273 pipe = popen(cmd, "w");
3277 while (!ferror(pipe) && written < bufsize) {
3278 written += fwrite(buf + written, 1, bufsize - written, pipe);
3283 if (written != bufsize)
3290 status_update(struct view *view)
3292 struct line *line = &view->line[view->lineno];
3294 assert(view->lines);
3297 while (++line < view->line + view->lines && line->data) {
3298 if (!status_update_file(view, line->data, line->type))
3299 report("Failed to update file status");
3302 if (!line[-1].data) {
3303 report("Nothing to update");
3307 } else if (!status_update_file(view, line->data, line->type)) {
3308 report("Failed to update file status");
3311 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3315 status_request(struct view *view, enum request request, struct line *line)
3317 struct status *status = line->data;
3320 case REQ_STATUS_UPDATE:
3321 status_update(view);
3328 open_editor(status->status != '?', status->name);
3329 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3333 status_enter(view, line);
3337 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3348 status_select(struct view *view, struct line *line)
3350 struct status *status = line->data;
3351 char file[SIZEOF_STR] = "all files";
3354 if (status && !string_format(file, "'%s'", status->name))
3357 if (!status && line[1].type == LINE_STAT_NONE)
3360 switch (line->type) {
3361 case LINE_STAT_STAGED:
3362 text = "Press %s to unstage %s for commit";
3365 case LINE_STAT_UNSTAGED:
3366 text = "Press %s to stage %s for commit";
3369 case LINE_STAT_UNTRACKED:
3370 text = "Press %s to stage %s for addition";
3373 case LINE_STAT_NONE:
3374 text = "Nothing to update";
3381 string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
3385 status_grep(struct view *view, struct line *line)
3387 struct status *status = line->data;
3388 enum { S_STATUS, S_NAME, S_END } state;
3395 for (state = S_STATUS; state < S_END; state++) {
3399 case S_NAME: text = status->name; break;
3401 buf[0] = status->status;
3409 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3416 static struct view_ops status_ops = {
3428 stage_diff_line(FILE *pipe, struct line *line)
3430 char *buf = line->data;
3431 size_t bufsize = strlen(buf);
3434 while (!ferror(pipe) && written < bufsize) {
3435 written += fwrite(buf + written, 1, bufsize - written, pipe);
3440 return written == bufsize;
3443 static struct line *
3444 stage_diff_hdr(struct view *view, struct line *line)
3446 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3447 struct line *diff_hdr;
3449 if (line->type == LINE_DIFF_CHUNK)
3450 diff_hdr = line - 1;
3452 diff_hdr = view->line + 1;
3454 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3455 if (diff_hdr->type == LINE_DIFF_HEADER)
3458 diff_hdr += diff_hdr_dir;
3465 stage_update_chunk(struct view *view, struct line *line)
3467 char cmd[SIZEOF_STR];
3469 struct line *diff_hdr, *diff_chunk, *diff_end;
3472 diff_hdr = stage_diff_hdr(view, line);
3477 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3480 if (!string_format_from(cmd, &cmdsize,
3481 "git apply --cached %s - && "
3482 "git update-index -q --unmerged --refresh 2>/dev/null",
3483 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3486 pipe = popen(cmd, "w");
3490 diff_end = view->line + view->lines;
3491 if (line->type != LINE_DIFF_CHUNK) {
3492 diff_chunk = diff_hdr;
3495 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3496 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3497 diff_chunk->type == LINE_DIFF_HEADER)
3498 diff_end = diff_chunk;
3502 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3503 switch (diff_hdr->type) {
3504 case LINE_DIFF_HEADER:
3505 case LINE_DIFF_INDEX:
3515 if (!stage_diff_line(pipe, diff_hdr++)) {
3522 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3527 if (diff_chunk != diff_end)
3534 stage_update(struct view *view, struct line *line)
3536 if (stage_line_type != LINE_STAT_UNTRACKED &&
3537 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3538 if (!stage_update_chunk(view, line)) {
3539 report("Failed to apply chunk");
3543 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3544 report("Failed to update file");
3548 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3550 view = VIEW(REQ_VIEW_STATUS);
3551 if (view_is_displayed(view))
3552 status_enter(view, &view->line[view->lineno]);
3556 stage_request(struct view *view, enum request request, struct line *line)
3559 case REQ_STATUS_UPDATE:
3560 stage_update(view, line);
3564 if (!stage_status.name[0])
3567 open_editor(stage_status.status != '?', stage_status.name);
3571 pager_request(view, request, line);
3581 static struct view_ops stage_ops = {
3597 char id[SIZEOF_REV]; /* SHA1 ID. */
3598 char title[128]; /* First line of the commit message. */
3599 char author[75]; /* Author of the commit. */
3600 struct tm time; /* Date from the author ident. */
3601 struct ref **refs; /* Repository references. */
3602 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3603 size_t graph_size; /* The width of the graph array. */
3606 /* Size of rev graph with no "padding" columns */
3607 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3610 struct rev_graph *prev, *next, *parents;
3611 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3613 struct commit *commit;
3617 /* Parents of the commit being visualized. */
3618 static struct rev_graph graph_parents[4];
3620 /* The current stack of revisions on the graph. */
3621 static struct rev_graph graph_stacks[4] = {
3622 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3623 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3624 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3625 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3629 graph_parent_is_merge(struct rev_graph *graph)
3631 return graph->parents->size > 1;
3635 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3637 struct commit *commit = graph->commit;
3639 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3640 commit->graph[commit->graph_size++] = symbol;
3644 done_rev_graph(struct rev_graph *graph)
3646 if (graph_parent_is_merge(graph) &&
3647 graph->pos < graph->size - 1 &&
3648 graph->next->size == graph->size + graph->parents->size - 1) {
3649 size_t i = graph->pos + graph->parents->size - 1;
3651 graph->commit->graph_size = i * 2;
3652 while (i < graph->next->size - 1) {
3653 append_to_rev_graph(graph, ' ');
3654 append_to_rev_graph(graph, '\\');
3659 graph->size = graph->pos = 0;
3660 graph->commit = NULL;
3661 memset(graph->parents, 0, sizeof(*graph->parents));
3665 push_rev_graph(struct rev_graph *graph, char *parent)
3669 /* "Collapse" duplicate parents lines.
3671 * FIXME: This needs to also update update the drawn graph but
3672 * for now it just serves as a method for pruning graph lines. */
3673 for (i = 0; i < graph->size; i++)
3674 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3677 if (graph->size < SIZEOF_REVITEMS) {
3678 string_copy_rev(graph->rev[graph->size++], parent);
3683 get_rev_graph_symbol(struct rev_graph *graph)
3687 if (graph->parents->size == 0)
3688 symbol = REVGRAPH_INIT;
3689 else if (graph_parent_is_merge(graph))
3690 symbol = REVGRAPH_MERGE;
3691 else if (graph->pos >= graph->size)
3692 symbol = REVGRAPH_BRANCH;
3694 symbol = REVGRAPH_COMMIT;
3700 draw_rev_graph(struct rev_graph *graph)
3703 chtype separator, line;
3705 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3706 static struct rev_filler fillers[] = {
3707 { ' ', REVGRAPH_LINE },
3712 chtype symbol = get_rev_graph_symbol(graph);
3713 struct rev_filler *filler;
3716 filler = &fillers[DEFAULT];
3718 for (i = 0; i < graph->pos; i++) {
3719 append_to_rev_graph(graph, filler->line);
3720 if (graph_parent_is_merge(graph->prev) &&
3721 graph->prev->pos == i)
3722 filler = &fillers[RSHARP];
3724 append_to_rev_graph(graph, filler->separator);
3727 /* Place the symbol for this revision. */
3728 append_to_rev_graph(graph, symbol);
3730 if (graph->prev->size > graph->size)
3731 filler = &fillers[RDIAG];
3733 filler = &fillers[DEFAULT];
3737 for (; i < graph->size; i++) {
3738 append_to_rev_graph(graph, filler->separator);
3739 append_to_rev_graph(graph, filler->line);
3740 if (graph_parent_is_merge(graph->prev) &&
3741 i < graph->prev->pos + graph->parents->size)
3742 filler = &fillers[RSHARP];
3743 if (graph->prev->size > graph->size)
3744 filler = &fillers[LDIAG];
3747 if (graph->prev->size > graph->size) {
3748 append_to_rev_graph(graph, filler->separator);
3749 if (filler->line != ' ')
3750 append_to_rev_graph(graph, filler->line);
3754 /* Prepare the next rev graph */
3756 prepare_rev_graph(struct rev_graph *graph)
3760 /* First, traverse all lines of revisions up to the active one. */
3761 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3762 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3765 push_rev_graph(graph->next, graph->rev[graph->pos]);
3768 /* Interleave the new revision parent(s). */
3769 for (i = 0; i < graph->parents->size; i++)
3770 push_rev_graph(graph->next, graph->parents->rev[i]);
3772 /* Lastly, put any remaining revisions. */
3773 for (i = graph->pos + 1; i < graph->size; i++)
3774 push_rev_graph(graph->next, graph->rev[i]);
3778 update_rev_graph(struct rev_graph *graph)
3780 /* If this is the finalizing update ... */
3782 prepare_rev_graph(graph);
3784 /* Graph visualization needs a one rev look-ahead,
3785 * so the first update doesn't visualize anything. */
3786 if (!graph->prev->commit)
3789 draw_rev_graph(graph->prev);
3790 done_rev_graph(graph->prev->prev);
3799 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3801 char buf[DATE_COLS + 1];
3802 struct commit *commit = line->data;
3803 enum line_type type;
3809 if (!*commit->author)
3812 wmove(view->win, lineno, col);
3816 wattrset(view->win, get_line_attr(type));
3817 wchgat(view->win, -1, 0, type, NULL);
3820 type = LINE_MAIN_COMMIT;
3821 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3824 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3825 waddnstr(view->win, buf, timelen);
3826 waddstr(view->win, " ");
3829 wmove(view->win, lineno, col);
3830 if (type != LINE_CURSOR)
3831 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3834 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3836 authorlen = strlen(commit->author);
3837 if (authorlen > AUTHOR_COLS - 2) {
3838 authorlen = AUTHOR_COLS - 2;
3844 waddnstr(view->win, commit->author, authorlen);
3845 if (type != LINE_CURSOR)
3846 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3847 waddch(view->win, '~');
3849 waddstr(view->win, commit->author);
3853 if (type != LINE_CURSOR)
3854 wattrset(view->win, A_NORMAL);
3856 if (opt_rev_graph && commit->graph_size) {
3859 wmove(view->win, lineno, col);
3860 /* Using waddch() instead of waddnstr() ensures that
3861 * they'll be rendered correctly for the cursor line. */
3862 for (i = 0; i < commit->graph_size; i++)
3863 waddch(view->win, commit->graph[i]);
3865 waddch(view->win, ' ');
3866 col += commit->graph_size + 1;
3869 wmove(view->win, lineno, col);
3875 if (type == LINE_CURSOR)
3877 else if (commit->refs[i]->tag)
3878 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3879 else if (commit->refs[i]->remote)
3880 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3882 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3883 waddstr(view->win, "[");
3884 waddstr(view->win, commit->refs[i]->name);
3885 waddstr(view->win, "]");
3886 if (type != LINE_CURSOR)
3887 wattrset(view->win, A_NORMAL);
3888 waddstr(view->win, " ");
3889 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3890 } while (commit->refs[i++]->next);
3893 if (type != LINE_CURSOR)
3894 wattrset(view->win, get_line_attr(type));
3897 int titlelen = strlen(commit->title);
3899 if (col + titlelen > view->width)
3900 titlelen = view->width - col;
3902 waddnstr(view->win, commit->title, titlelen);
3908 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3910 main_read(struct view *view, char *line)
3912 static struct rev_graph *graph = graph_stacks;
3913 enum line_type type;
3914 struct commit *commit;
3917 update_rev_graph(graph);
3921 type = get_line_type(line);
3922 if (type == LINE_COMMIT) {
3923 commit = calloc(1, sizeof(struct commit));
3927 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3928 commit->refs = get_refs(commit->id);
3929 graph->commit = commit;
3930 add_line_data(view, commit, LINE_MAIN_COMMIT);
3936 commit = view->line[view->lines - 1].data;
3940 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3945 /* Parse author lines where the name may be empty:
3946 * author <email@address.tld> 1138474660 +0100
3948 char *ident = line + STRING_SIZE("author ");
3949 char *nameend = strchr(ident, '<');
3950 char *emailend = strchr(ident, '>');
3952 if (!nameend || !emailend)
3955 update_rev_graph(graph);
3956 graph = graph->next;
3958 *nameend = *emailend = 0;
3959 ident = chomp_string(ident);
3961 ident = chomp_string(nameend + 1);
3966 string_ncopy(commit->author, ident, strlen(ident));
3968 /* Parse epoch and timezone */
3969 if (emailend[1] == ' ') {
3970 char *secs = emailend + 2;
3971 char *zone = strchr(secs, ' ');
3972 time_t time = (time_t) atol(secs);
3974 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3978 tz = ('0' - zone[1]) * 60 * 60 * 10;
3979 tz += ('0' - zone[2]) * 60 * 60;
3980 tz += ('0' - zone[3]) * 60;
3981 tz += ('0' - zone[4]) * 60;
3989 gmtime_r(&time, &commit->time);
3994 /* Fill in the commit title if it has not already been set. */
3995 if (commit->title[0])
3998 /* Require titles to start with a non-space character at the
3999 * offset used by git log. */
4000 if (strncmp(line, " ", 4))
4003 /* Well, if the title starts with a whitespace character,
4004 * try to be forgiving. Otherwise we end up with no title. */
4005 while (isspace(*line))
4009 /* FIXME: More graceful handling of titles; append "..." to
4010 * shortened titles, etc. */
4012 string_ncopy(commit->title, line, strlen(line));
4019 cherry_pick_commit(struct commit *commit)
4021 char cmd[SIZEOF_STR];
4022 char *cherry_pick = getenv("TIG_CHERRY_PICK");
4025 cherry_pick = "git cherry-pick";
4027 if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4028 def_prog_mode(); /* save current tty modes */
4029 endwin(); /* restore original tty modes */
4031 fprintf(stderr, "Press Enter to continue");
4039 main_request(struct view *view, enum request request, struct line *line)
4041 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4043 if (request == REQ_ENTER)
4044 open_view(view, REQ_VIEW_DIFF, flags);
4045 else if (request == REQ_CHERRY_PICK)
4046 cherry_pick_commit(line->data);
4054 main_grep(struct view *view, struct line *line)
4056 struct commit *commit = line->data;
4057 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4058 char buf[DATE_COLS + 1];
4061 for (state = S_TITLE; state < S_END; state++) {
4065 case S_TITLE: text = commit->title; break;
4066 case S_AUTHOR: text = commit->author; break;
4068 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4077 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4085 main_select(struct view *view, struct line *line)
4087 struct commit *commit = line->data;
4089 string_copy_rev(view->ref, commit->id);
4090 string_copy_rev(ref_commit, view->ref);
4093 static struct view_ops main_ops = {
4105 * Unicode / UTF-8 handling
4107 * NOTE: Much of the following code for dealing with unicode is derived from
4108 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4109 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4112 /* I've (over)annotated a lot of code snippets because I am not entirely
4113 * confident that the approach taken by this small UTF-8 interface is correct.
4117 unicode_width(unsigned long c)
4120 (c <= 0x115f /* Hangul Jamo */
4123 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4125 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4126 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4127 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4128 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4129 || (c >= 0xffe0 && c <= 0xffe6)
4130 || (c >= 0x20000 && c <= 0x2fffd)
4131 || (c >= 0x30000 && c <= 0x3fffd)))
4137 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4138 * Illegal bytes are set one. */
4139 static const unsigned char utf8_bytes[256] = {
4140 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,
4141 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,
4142 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,
4143 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,
4144 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,
4145 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,
4146 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,
4147 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,
4150 /* Decode UTF-8 multi-byte representation into a unicode character. */
4151 static inline unsigned long
4152 utf8_to_unicode(const char *string, size_t length)
4154 unsigned long unicode;
4158 unicode = string[0];
4161 unicode = (string[0] & 0x1f) << 6;
4162 unicode += (string[1] & 0x3f);
4165 unicode = (string[0] & 0x0f) << 12;
4166 unicode += ((string[1] & 0x3f) << 6);
4167 unicode += (string[2] & 0x3f);
4170 unicode = (string[0] & 0x0f) << 18;
4171 unicode += ((string[1] & 0x3f) << 12);
4172 unicode += ((string[2] & 0x3f) << 6);
4173 unicode += (string[3] & 0x3f);
4176 unicode = (string[0] & 0x0f) << 24;
4177 unicode += ((string[1] & 0x3f) << 18);
4178 unicode += ((string[2] & 0x3f) << 12);
4179 unicode += ((string[3] & 0x3f) << 6);
4180 unicode += (string[4] & 0x3f);
4183 unicode = (string[0] & 0x01) << 30;
4184 unicode += ((string[1] & 0x3f) << 24);
4185 unicode += ((string[2] & 0x3f) << 18);
4186 unicode += ((string[3] & 0x3f) << 12);
4187 unicode += ((string[4] & 0x3f) << 6);
4188 unicode += (string[5] & 0x3f);
4191 die("Invalid unicode length");
4194 /* Invalid characters could return the special 0xfffd value but NUL
4195 * should be just as good. */
4196 return unicode > 0xffff ? 0 : unicode;
4199 /* Calculates how much of string can be shown within the given maximum width
4200 * and sets trimmed parameter to non-zero value if all of string could not be
4203 * Additionally, adds to coloffset how many many columns to move to align with
4204 * the expected position. Takes into account how multi-byte and double-width
4205 * characters will effect the cursor position.
4207 * Returns the number of bytes to output from string to satisfy max_width. */
4209 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4211 const char *start = string;
4212 const char *end = strchr(string, '\0');
4218 while (string < end) {
4219 int c = *(unsigned char *) string;
4220 unsigned char bytes = utf8_bytes[c];
4222 unsigned long unicode;
4224 if (string + bytes > end)
4227 /* Change representation to figure out whether
4228 * it is a single- or double-width character. */
4230 unicode = utf8_to_unicode(string, bytes);
4231 /* FIXME: Graceful handling of invalid unicode character. */
4235 ucwidth = unicode_width(unicode);
4237 if (width > max_width) {
4242 /* The column offset collects the differences between the
4243 * number of bytes encoding a character and the number of
4244 * columns will be used for rendering said character.
4246 * So if some character A is encoded in 2 bytes, but will be
4247 * represented on the screen using only 1 byte this will and up
4248 * adding 1 to the multi-byte column offset.
4250 * Assumes that no double-width character can be encoding in
4251 * less than two bytes. */
4252 if (bytes > ucwidth)
4253 mbwidth += bytes - ucwidth;
4258 *coloffset += mbwidth;
4260 return string - start;
4268 /* Whether or not the curses interface has been initialized. */
4269 static bool cursed = FALSE;
4271 /* The status window is used for polling keystrokes. */
4272 static WINDOW *status_win;
4274 static bool status_empty = TRUE;
4276 /* Update status and title window. */
4278 report(const char *msg, ...)
4280 struct view *view = display[current_view];
4285 if (!status_empty || *msg) {
4288 va_start(args, msg);
4290 wmove(status_win, 0, 0);
4292 vwprintw(status_win, msg, args);
4293 status_empty = FALSE;
4295 status_empty = TRUE;
4297 wclrtoeol(status_win);
4298 wrefresh(status_win);
4303 update_view_title(view);
4304 update_display_cursor(view);
4307 /* Controls when nodelay should be in effect when polling user input. */
4309 set_nonblocking_input(bool loading)
4311 static unsigned int loading_views;
4313 if ((loading == FALSE && loading_views-- == 1) ||
4314 (loading == TRUE && loading_views++ == 0))
4315 nodelay(status_win, loading);
4323 /* Initialize the curses library */
4324 if (isatty(STDIN_FILENO)) {
4325 cursed = !!initscr();
4327 /* Leave stdin and stdout alone when acting as a pager. */
4328 FILE *io = fopen("/dev/tty", "r+");
4331 die("Failed to open /dev/tty");
4332 cursed = !!newterm(NULL, io, io);
4336 die("Failed to initialize curses");
4338 nonl(); /* Tell curses not to do NL->CR/NL on output */
4339 cbreak(); /* Take input chars one at a time, no wait for \n */
4340 noecho(); /* Don't echo input */
4341 leaveok(stdscr, TRUE);
4346 getmaxyx(stdscr, y, x);
4347 status_win = newwin(1, 0, y - 1, 0);
4349 die("Failed to create status window");
4351 /* Enable keyboard mapping */
4352 keypad(status_win, TRUE);
4353 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4357 read_prompt(const char *prompt)
4359 enum { READING, STOP, CANCEL } status = READING;
4360 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4363 while (status == READING) {
4369 foreach_view (view, i)
4374 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4375 wclrtoeol(status_win);
4377 /* Refresh, accept single keystroke of input */
4378 key = wgetch(status_win);
4383 status = pos ? STOP : CANCEL;
4401 if (pos >= sizeof(buf)) {
4402 report("Input string too long");
4407 buf[pos++] = (char) key;
4411 /* Clear the status window */
4412 status_empty = FALSE;
4415 if (status == CANCEL)
4424 * Repository references
4427 static struct ref *refs;
4428 static size_t refs_size;
4430 /* Id <-> ref store */
4431 static struct ref ***id_refs;
4432 static size_t id_refs_size;
4434 static struct ref **
4437 struct ref ***tmp_id_refs;
4438 struct ref **ref_list = NULL;
4439 size_t ref_list_size = 0;
4442 for (i = 0; i < id_refs_size; i++)
4443 if (!strcmp(id, id_refs[i][0]->id))
4446 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4450 id_refs = tmp_id_refs;
4452 for (i = 0; i < refs_size; i++) {
4455 if (strcmp(id, refs[i].id))
4458 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4466 if (ref_list_size > 0)
4467 ref_list[ref_list_size - 1]->next = 1;
4468 ref_list[ref_list_size] = &refs[i];
4470 /* XXX: The properties of the commit chains ensures that we can
4471 * safely modify the shared ref. The repo references will
4472 * always be similar for the same id. */
4473 ref_list[ref_list_size]->next = 0;
4478 id_refs[id_refs_size++] = ref_list;
4484 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4488 bool remote = FALSE;
4490 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4491 /* Commits referenced by tags has "^{}" appended. */
4492 if (name[namelen - 1] != '}')
4495 while (namelen > 0 && name[namelen] != '^')
4499 namelen -= STRING_SIZE("refs/tags/");
4500 name += STRING_SIZE("refs/tags/");
4502 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4504 namelen -= STRING_SIZE("refs/remotes/");
4505 name += STRING_SIZE("refs/remotes/");
4507 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4508 namelen -= STRING_SIZE("refs/heads/");
4509 name += STRING_SIZE("refs/heads/");
4511 } else if (!strcmp(name, "HEAD")) {
4515 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4519 ref = &refs[refs_size++];
4520 ref->name = malloc(namelen + 1);
4524 strncpy(ref->name, name, namelen);
4525 ref->name[namelen] = 0;
4527 ref->remote = remote;
4528 string_copy_rev(ref->id, id);
4536 const char *cmd_env = getenv("TIG_LS_REMOTE");
4537 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4539 return read_properties(popen(cmd, "r"), "\t", read_ref);
4543 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4545 if (!strcmp(name, "i18n.commitencoding"))
4546 string_ncopy(opt_encoding, value, valuelen);
4548 if (!strcmp(name, "core.editor"))
4549 string_ncopy(opt_editor, value, valuelen);
4555 load_repo_config(void)
4557 return read_properties(popen(GIT_CONFIG " --list", "r"),
4558 "=", read_repo_config_option);
4562 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4564 if (!opt_git_dir[0])
4565 string_ncopy(opt_git_dir, name, namelen);
4567 string_ncopy(opt_cdup, name, namelen);
4571 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4572 * must be the last one! */
4574 load_repo_info(void)
4576 return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
4577 "=", read_repo_info);
4581 read_properties(FILE *pipe, const char *separators,
4582 int (*read_property)(char *, size_t, char *, size_t))
4584 char buffer[BUFSIZ];
4591 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4596 name = chomp_string(name);
4597 namelen = strcspn(name, separators);
4599 if (name[namelen]) {
4601 value = chomp_string(name + namelen + 1);
4602 valuelen = strlen(value);
4609 state = read_property(name, namelen, value, valuelen);
4612 if (state != ERR && ferror(pipe))
4625 static void __NORETURN
4628 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4634 static void __NORETURN
4635 die(const char *err, ...)
4641 va_start(args, err);
4642 fputs("tig: ", stderr);
4643 vfprintf(stderr, err, args);
4644 fputs("\n", stderr);
4651 main(int argc, char *argv[])
4654 enum request request;
4657 signal(SIGINT, quit);
4659 if (setlocale(LC_ALL, "")) {
4660 char *codeset = nl_langinfo(CODESET);
4662 string_ncopy(opt_codeset, codeset, strlen(codeset));
4665 if (load_repo_info() == ERR)
4666 die("Failed to load repo info.");
4668 if (load_options() == ERR)
4669 die("Failed to load user config.");
4671 /* Load the repo config file so options can be overwritten from
4672 * the command line. */
4673 if (load_repo_config() == ERR)
4674 die("Failed to load repo config.");
4676 if (!parse_options(argc, argv))
4679 /* Require a git repository unless when running in pager mode. */
4680 if (!opt_git_dir[0])
4681 die("Not a git repository");
4683 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4684 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4685 if (opt_iconv == ICONV_NONE)
4686 die("Failed to initialize character set conversion");
4689 if (load_refs() == ERR)
4690 die("Failed to load refs.");
4692 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4693 view->cmd_env = getenv(view->cmd_env);
4695 request = opt_request;
4699 while (view_driver(display[current_view], request)) {
4703 foreach_view (view, i)
4706 /* Refresh, accept single keystroke of input */
4707 key = wgetch(status_win);
4709 /* wgetch() with nodelay() enabled returns ERR when there's no
4716 request = get_keybinding(display[current_view]->keymap, key);
4718 /* Some low-level request handling. This keeps access to
4719 * status_win restricted. */
4723 char *cmd = read_prompt(":");
4725 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4726 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4727 opt_request = REQ_VIEW_DIFF;
4729 opt_request = REQ_VIEW_PAGER;
4738 case REQ_SEARCH_BACK:
4740 const char *prompt = request == REQ_SEARCH
4742 char *search = read_prompt(prompt);
4745 string_ncopy(opt_search, search, strlen(search));
4750 case REQ_SCREEN_RESIZE:
4754 getmaxyx(stdscr, height, width);
4756 /* Resize the status view and let the view driver take
4757 * care of resizing the displayed views. */
4758 wresize(status_win, 1, width);
4759 mvwin(status_win, height - 1, 0);
4760 wrefresh(status_win);