1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
59 #define __NORETURN __attribute__((__noreturn__))
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
71 #define ABS(x) ((x) >= 0 ? (x) : -(x))
72 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
75 #define STRING_SIZE(x) (sizeof(x) - 1)
77 #define SIZEOF_STR 1024 /* Default string size. */
78 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
79 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
83 #define REVGRAPH_INIT 'I'
84 #define REVGRAPH_MERGE 'M'
85 #define REVGRAPH_BRANCH '+'
86 #define REVGRAPH_COMMIT '*'
87 #define REVGRAPH_BOUND '^'
89 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
91 /* This color name can be used to refer to the default term colors. */
92 #define COLOR_DEFAULT (-1)
94 #define ICONV_NONE ((iconv_t) -1)
96 #define ICONV_CONST /* nothing */
99 /* The format and size of the date column in the main view. */
100 #define DATE_FORMAT "%Y-%m-%d %H:%M"
101 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
103 #define AUTHOR_COLS 20
106 /* The default interval between line numbers. */
107 #define NUMBER_INTERVAL 5
111 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
113 #define NULL_ID "0000000000000000000000000000000000000000"
116 #define GIT_CONFIG "git config"
119 #define TIG_LS_REMOTE \
120 "git ls-remote . 2>/dev/null"
122 #define TIG_DIFF_CMD \
123 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
125 #define TIG_LOG_CMD \
126 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
128 #define TIG_MAIN_CMD \
129 "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
131 #define TIG_TREE_CMD \
134 #define TIG_BLOB_CMD \
135 "git cat-file blob %s"
137 /* XXX: Needs to be defined to the empty string. */
138 #define TIG_HELP_CMD ""
139 #define TIG_PAGER_CMD ""
140 #define TIG_STATUS_CMD ""
141 #define TIG_STAGE_CMD ""
142 #define TIG_BLAME_CMD ""
144 /* Some ascii-shorthands fitted into the ncurses namespace. */
146 #define KEY_RETURN '\r'
151 char *name; /* Ref name; tag or head names are shortened. */
152 char id[SIZEOF_REV]; /* Commit SHA1 ID */
153 unsigned int head:1; /* Is it the current HEAD? */
154 unsigned int tag:1; /* Is it a tag? */
155 unsigned int ltag:1; /* If so, is the tag local? */
156 unsigned int remote:1; /* Is it a remote ref? */
157 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
158 unsigned int next:1; /* For ref lists: are there more refs? */
161 static struct ref **get_refs(char *id);
170 set_from_int_map(struct int_map *map, size_t map_size,
171 int *value, const char *name, int namelen)
176 for (i = 0; i < map_size; i++)
177 if (namelen == map[i].namelen &&
178 !strncasecmp(name, map[i].name, namelen)) {
179 *value = map[i].value;
192 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
194 if (srclen > dstlen - 1)
197 strncpy(dst, src, srclen);
201 /* Shorthands for safely copying into a fixed buffer. */
203 #define string_copy(dst, src) \
204 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
206 #define string_ncopy(dst, src, srclen) \
207 string_ncopy_do(dst, sizeof(dst), src, srclen)
209 #define string_copy_rev(dst, src) \
210 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
212 #define string_add(dst, from, src) \
213 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
216 chomp_string(char *name)
220 while (isspace(*name))
223 namelen = strlen(name) - 1;
224 while (namelen > 0 && isspace(name[namelen]))
231 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
234 size_t pos = bufpos ? *bufpos : 0;
237 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
243 return pos >= bufsize ? FALSE : TRUE;
246 #define string_format(buf, fmt, args...) \
247 string_nformat(buf, sizeof(buf), NULL, fmt, args)
249 #define string_format_from(buf, from, fmt, args...) \
250 string_nformat(buf, sizeof(buf), from, fmt, args)
253 string_enum_compare(const char *str1, const char *str2, int len)
257 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
259 /* Diff-Header == DIFF_HEADER */
260 for (i = 0; i < len; i++) {
261 if (toupper(str1[i]) == toupper(str2[i]))
264 if (string_enum_sep(str1[i]) &&
265 string_enum_sep(str2[i]))
268 return str1[i] - str2[i];
276 * NOTE: The following is a slightly modified copy of the git project's shell
277 * quoting routines found in the quote.c file.
279 * Help to copy the thing properly quoted for the shell safety. any single
280 * quote is replaced with '\'', any exclamation point is replaced with '\!',
281 * and the whole thing is enclosed in a
284 * original sq_quote result
285 * name ==> name ==> 'name'
286 * a b ==> a b ==> 'a b'
287 * a'b ==> a'\''b ==> 'a'\''b'
288 * a!b ==> a'\!'b ==> 'a'\!'b'
292 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
296 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
299 while ((c = *src++)) {
300 if (c == '\'' || c == '!') {
311 if (bufsize < SIZEOF_STR)
323 /* XXX: Keep the view request first and in sync with views[]. */ \
324 REQ_GROUP("View switching") \
325 REQ_(VIEW_MAIN, "Show main view"), \
326 REQ_(VIEW_DIFF, "Show diff view"), \
327 REQ_(VIEW_LOG, "Show log view"), \
328 REQ_(VIEW_TREE, "Show tree view"), \
329 REQ_(VIEW_BLOB, "Show blob view"), \
330 REQ_(VIEW_BLAME, "Show blame view"), \
331 REQ_(VIEW_HELP, "Show help page"), \
332 REQ_(VIEW_PAGER, "Show pager view"), \
333 REQ_(VIEW_STATUS, "Show status view"), \
334 REQ_(VIEW_STAGE, "Show stage view"), \
336 REQ_GROUP("View manipulation") \
337 REQ_(ENTER, "Enter current line and scroll"), \
338 REQ_(NEXT, "Move to next"), \
339 REQ_(PREVIOUS, "Move to previous"), \
340 REQ_(VIEW_NEXT, "Move focus to next view"), \
341 REQ_(REFRESH, "Reload and refresh"), \
342 REQ_(MAXIMIZE, "Maximize the current view"), \
343 REQ_(VIEW_CLOSE, "Close the current view"), \
344 REQ_(QUIT, "Close all views and quit"), \
346 REQ_GROUP("Cursor navigation") \
347 REQ_(MOVE_UP, "Move cursor one line up"), \
348 REQ_(MOVE_DOWN, "Move cursor one line down"), \
349 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
350 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
351 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
352 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
354 REQ_GROUP("Scrolling") \
355 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
356 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
357 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
358 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
360 REQ_GROUP("Searching") \
361 REQ_(SEARCH, "Search the view"), \
362 REQ_(SEARCH_BACK, "Search backwards in the view"), \
363 REQ_(FIND_NEXT, "Find next search match"), \
364 REQ_(FIND_PREV, "Find previous search match"), \
367 REQ_(PROMPT, "Bring up the prompt"), \
368 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
369 REQ_(SCREEN_RESIZE, "Resize the screen"), \
370 REQ_(SHOW_VERSION, "Show version information"), \
371 REQ_(STOP_LOADING, "Stop all loading views"), \
372 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
373 REQ_(TOGGLE_DATE, "Toggle date display"), \
374 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
375 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
376 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
377 REQ_(STATUS_UPDATE, "Update file status"), \
378 REQ_(STATUS_MERGE, "Merge file using external tool"), \
379 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
380 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
381 REQ_(EDIT, "Open in editor"), \
382 REQ_(NONE, "Do nothing")
385 /* User action requests. */
387 #define REQ_GROUP(help)
388 #define REQ_(req, help) REQ_##req
390 /* Offset all requests to avoid conflicts with ncurses getch values. */
391 REQ_OFFSET = KEY_MAX + 1,
398 struct request_info {
399 enum request request;
405 static struct request_info req_info[] = {
406 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
407 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
414 get_request(const char *name)
416 int namelen = strlen(name);
419 for (i = 0; i < ARRAY_SIZE(req_info); i++)
420 if (req_info[i].namelen == namelen &&
421 !string_enum_compare(req_info[i].name, name, namelen))
422 return req_info[i].request;
432 static const char usage[] =
433 "tig " TIG_VERSION " (" __DATE__ ")\n"
435 "Usage: tig [options] [revs] [--] [paths]\n"
436 " or: tig show [options] [revs] [--] [paths]\n"
437 " or: tig blame [rev] path\n"
439 " or: tig < [git command output]\n"
442 " -v, --version Show version and exit\n"
443 " -h, --help Show help message and exit";
445 /* Option and state variables. */
446 static bool opt_date = TRUE;
447 static bool opt_author = TRUE;
448 static bool opt_line_number = FALSE;
449 static bool opt_line_graphics = TRUE;
450 static bool opt_rev_graph = FALSE;
451 static bool opt_show_refs = TRUE;
452 static int opt_num_interval = NUMBER_INTERVAL;
453 static int opt_tab_size = TAB_SIZE;
454 static enum request opt_request = REQ_VIEW_MAIN;
455 static char opt_cmd[SIZEOF_STR] = "";
456 static char opt_path[SIZEOF_STR] = "";
457 static char opt_file[SIZEOF_STR] = "";
458 static char opt_ref[SIZEOF_REF] = "";
459 static char opt_head[SIZEOF_REF] = "";
460 static char opt_remote[SIZEOF_REF] = "";
461 static bool opt_no_head = TRUE;
462 static FILE *opt_pipe = NULL;
463 static char opt_encoding[20] = "UTF-8";
464 static bool opt_utf8 = TRUE;
465 static char opt_codeset[20] = "UTF-8";
466 static iconv_t opt_iconv = ICONV_NONE;
467 static char opt_search[SIZEOF_STR] = "";
468 static char opt_cdup[SIZEOF_STR] = "";
469 static char opt_git_dir[SIZEOF_STR] = "";
470 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
471 static char opt_editor[SIZEOF_STR] = "";
474 parse_options(int argc, char *argv[])
478 bool seen_dashdash = FALSE;
481 if (!isatty(STDIN_FILENO)) {
482 opt_request = REQ_VIEW_PAGER;
490 subcommand = argv[1];
491 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
492 opt_request = REQ_VIEW_STATUS;
493 if (!strcmp(subcommand, "-S"))
494 warn("`-S' has been deprecated; use `tig status' instead");
496 warn("ignoring arguments after `%s'", subcommand);
499 } else if (!strcmp(subcommand, "blame")) {
500 opt_request = REQ_VIEW_BLAME;
501 if (argc <= 2 || argc > 4)
502 die("invalid number of options to blame\n\n%s", usage);
506 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
510 string_ncopy(opt_file, argv[i], strlen(argv[i]));
513 } else if (!strcmp(subcommand, "show")) {
514 opt_request = REQ_VIEW_DIFF;
516 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
517 opt_request = subcommand[0] == 'l'
518 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
519 warn("`tig %s' has been deprecated", subcommand);
526 /* XXX: This is vulnerable to the user overriding
527 * options required for the main view parser. */
528 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
530 string_format(opt_cmd, "git %s", subcommand);
532 buf_size = strlen(opt_cmd);
534 for (i = 1 + !!subcommand; i < argc; i++) {
537 if (seen_dashdash || !strcmp(opt, "--")) {
538 seen_dashdash = TRUE;
540 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
541 printf("tig version %s\n", TIG_VERSION);
544 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
545 printf("%s\n", usage);
549 opt_cmd[buf_size++] = ' ';
550 buf_size = sq_quote(opt_cmd, buf_size, opt);
551 if (buf_size >= sizeof(opt_cmd))
552 die("command too long");
555 opt_cmd[buf_size] = 0;
562 * Line-oriented content detection.
566 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
567 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
568 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
569 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
570 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
571 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
572 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
573 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
574 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
575 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
576 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
577 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
578 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
579 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
580 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
581 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
582 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
583 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
586 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
587 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
588 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
589 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
590 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
591 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
592 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
593 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
594 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
595 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
596 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
597 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
598 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
599 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
600 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
601 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
602 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
603 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
604 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
605 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
606 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
607 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
608 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
609 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
610 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
611 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
612 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
613 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
614 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
615 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
616 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
617 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
618 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
619 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
622 #define LINE(type, line, fg, bg, attr) \
630 const char *name; /* Option name. */
631 int namelen; /* Size of option name. */
632 const char *line; /* The start of line to match. */
633 int linelen; /* Size of string to match. */
634 int fg, bg, attr; /* Color and text attributes for the lines. */
637 static struct line_info line_info[] = {
638 #define LINE(type, line, fg, bg, attr) \
639 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
644 static enum line_type
645 get_line_type(char *line)
647 int linelen = strlen(line);
650 for (type = 0; type < ARRAY_SIZE(line_info); type++)
651 /* Case insensitive search matches Signed-off-by lines better. */
652 if (linelen >= line_info[type].linelen &&
653 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
660 get_line_attr(enum line_type type)
662 assert(type < ARRAY_SIZE(line_info));
663 return COLOR_PAIR(type) | line_info[type].attr;
666 static struct line_info *
667 get_line_info(char *name)
669 size_t namelen = strlen(name);
672 for (type = 0; type < ARRAY_SIZE(line_info); type++)
673 if (namelen == line_info[type].namelen &&
674 !string_enum_compare(line_info[type].name, name, namelen))
675 return &line_info[type];
683 int default_bg = line_info[LINE_DEFAULT].bg;
684 int default_fg = line_info[LINE_DEFAULT].fg;
689 if (assume_default_colors(default_fg, default_bg) == ERR) {
690 default_bg = COLOR_BLACK;
691 default_fg = COLOR_WHITE;
694 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
695 struct line_info *info = &line_info[type];
696 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
697 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
699 init_pair(type, fg, bg);
707 unsigned int selected:1;
708 unsigned int dirty:1;
710 void *data; /* User data */
720 enum request request;
721 struct keybinding *next;
724 static struct keybinding default_keybindings[] = {
726 { 'm', REQ_VIEW_MAIN },
727 { 'd', REQ_VIEW_DIFF },
728 { 'l', REQ_VIEW_LOG },
729 { 't', REQ_VIEW_TREE },
730 { 'f', REQ_VIEW_BLOB },
731 { 'B', REQ_VIEW_BLAME },
732 { 'p', REQ_VIEW_PAGER },
733 { 'h', REQ_VIEW_HELP },
734 { 'S', REQ_VIEW_STATUS },
735 { 'c', REQ_VIEW_STAGE },
737 /* View manipulation */
738 { 'q', REQ_VIEW_CLOSE },
739 { KEY_TAB, REQ_VIEW_NEXT },
740 { KEY_RETURN, REQ_ENTER },
741 { KEY_UP, REQ_PREVIOUS },
742 { KEY_DOWN, REQ_NEXT },
743 { 'R', REQ_REFRESH },
744 { KEY_F(5), REQ_REFRESH },
745 { 'O', REQ_MAXIMIZE },
747 /* Cursor navigation */
748 { 'k', REQ_MOVE_UP },
749 { 'j', REQ_MOVE_DOWN },
750 { KEY_HOME, REQ_MOVE_FIRST_LINE },
751 { KEY_END, REQ_MOVE_LAST_LINE },
752 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
753 { ' ', REQ_MOVE_PAGE_DOWN },
754 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
755 { 'b', REQ_MOVE_PAGE_UP },
756 { '-', REQ_MOVE_PAGE_UP },
759 { KEY_IC, REQ_SCROLL_LINE_UP },
760 { KEY_DC, REQ_SCROLL_LINE_DOWN },
761 { 'w', REQ_SCROLL_PAGE_UP },
762 { 's', REQ_SCROLL_PAGE_DOWN },
766 { '?', REQ_SEARCH_BACK },
767 { 'n', REQ_FIND_NEXT },
768 { 'N', REQ_FIND_PREV },
772 { 'z', REQ_STOP_LOADING },
773 { 'v', REQ_SHOW_VERSION },
774 { 'r', REQ_SCREEN_REDRAW },
775 { '.', REQ_TOGGLE_LINENO },
776 { 'D', REQ_TOGGLE_DATE },
777 { 'A', REQ_TOGGLE_AUTHOR },
778 { 'g', REQ_TOGGLE_REV_GRAPH },
779 { 'F', REQ_TOGGLE_REFS },
781 { 'u', REQ_STATUS_UPDATE },
782 { 'M', REQ_STATUS_MERGE },
783 { '@', REQ_STAGE_NEXT },
784 { ',', REQ_TREE_PARENT },
787 /* Using the ncurses SIGWINCH handler. */
788 { KEY_RESIZE, REQ_SCREEN_RESIZE },
791 #define KEYMAP_INFO \
805 #define KEYMAP_(name) KEYMAP_##name
810 static struct int_map keymap_table[] = {
811 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
816 #define set_keymap(map, name) \
817 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
819 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
822 add_keybinding(enum keymap keymap, enum request request, int key)
824 struct keybinding *keybinding;
826 keybinding = calloc(1, sizeof(*keybinding));
828 die("Failed to allocate keybinding");
830 keybinding->alias = key;
831 keybinding->request = request;
832 keybinding->next = keybindings[keymap];
833 keybindings[keymap] = keybinding;
836 /* Looks for a key binding first in the given map, then in the generic map, and
837 * lastly in the default keybindings. */
839 get_keybinding(enum keymap keymap, int key)
841 struct keybinding *kbd;
844 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
845 if (kbd->alias == key)
848 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
849 if (kbd->alias == key)
852 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
853 if (default_keybindings[i].alias == key)
854 return default_keybindings[i].request;
856 return (enum request) key;
865 static struct key key_table[] = {
866 { "Enter", KEY_RETURN },
868 { "Backspace", KEY_BACKSPACE },
870 { "Escape", KEY_ESC },
871 { "Left", KEY_LEFT },
872 { "Right", KEY_RIGHT },
874 { "Down", KEY_DOWN },
875 { "Insert", KEY_IC },
876 { "Delete", KEY_DC },
878 { "Home", KEY_HOME },
880 { "PageUp", KEY_PPAGE },
881 { "PageDown", KEY_NPAGE },
891 { "F10", KEY_F(10) },
892 { "F11", KEY_F(11) },
893 { "F12", KEY_F(12) },
897 get_key_value(const char *name)
901 for (i = 0; i < ARRAY_SIZE(key_table); i++)
902 if (!strcasecmp(key_table[i].name, name))
903 return key_table[i].value;
905 if (strlen(name) == 1 && isprint(*name))
912 get_key_name(int key_value)
914 static char key_char[] = "'X'";
918 for (key = 0; key < ARRAY_SIZE(key_table); key++)
919 if (key_table[key].value == key_value)
920 seq = key_table[key].name;
924 isprint(key_value)) {
925 key_char[1] = (char) key_value;
929 return seq ? seq : "'?'";
933 get_key(enum request request)
935 static char buf[BUFSIZ];
942 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
943 struct keybinding *keybinding = &default_keybindings[i];
945 if (keybinding->request != request)
948 if (!string_format_from(buf, &pos, "%s%s", sep,
949 get_key_name(keybinding->alias)))
950 return "Too many keybindings!";
960 char cmd[SIZEOF_STR];
963 static struct run_request *run_request;
964 static size_t run_requests;
967 add_run_request(enum keymap keymap, int key, int argc, char **argv)
969 struct run_request *req;
970 char cmd[SIZEOF_STR];
973 for (bufpos = 0; argc > 0; argc--, argv++)
974 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
977 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
982 req = &run_request[run_requests++];
983 string_copy(req->cmd, cmd);
984 req->keymap = keymap;
987 return REQ_NONE + run_requests;
990 static struct run_request *
991 get_run_request(enum request request)
993 if (request <= REQ_NONE)
995 return &run_request[request - REQ_NONE - 1];
999 add_builtin_run_requests(void)
1006 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1007 { KEYMAP_GENERIC, 'G', { "git gc" } },
1011 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1014 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1015 if (req != REQ_NONE)
1016 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1021 * User config file handling.
1024 static struct int_map color_map[] = {
1025 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1037 #define set_color(color, name) \
1038 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1040 static struct int_map attr_map[] = {
1041 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1048 ATTR_MAP(UNDERLINE),
1051 #define set_attribute(attr, name) \
1052 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1054 static int config_lineno;
1055 static bool config_errors;
1056 static char *config_msg;
1058 /* Wants: object fgcolor bgcolor [attr] */
1060 option_color_command(int argc, char *argv[])
1062 struct line_info *info;
1064 if (argc != 3 && argc != 4) {
1065 config_msg = "Wrong number of arguments given to color command";
1069 info = get_line_info(argv[0]);
1071 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1072 info = get_line_info("delimiter");
1074 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1075 info = get_line_info("date");
1078 config_msg = "Unknown color name";
1083 if (set_color(&info->fg, argv[1]) == ERR ||
1084 set_color(&info->bg, argv[2]) == ERR) {
1085 config_msg = "Unknown color";
1089 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1090 config_msg = "Unknown attribute";
1097 static bool parse_bool(const char *s)
1099 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1100 !strcmp(s, "yes")) ? TRUE : FALSE;
1103 /* Wants: name = value */
1105 option_set_command(int argc, char *argv[])
1108 config_msg = "Wrong number of arguments given to set command";
1112 if (strcmp(argv[1], "=")) {
1113 config_msg = "No value assigned";
1117 if (!strcmp(argv[0], "show-author")) {
1118 opt_author = parse_bool(argv[2]);
1122 if (!strcmp(argv[0], "show-date")) {
1123 opt_date = parse_bool(argv[2]);
1127 if (!strcmp(argv[0], "show-rev-graph")) {
1128 opt_rev_graph = parse_bool(argv[2]);
1132 if (!strcmp(argv[0], "show-refs")) {
1133 opt_show_refs = parse_bool(argv[2]);
1137 if (!strcmp(argv[0], "show-line-numbers")) {
1138 opt_line_number = parse_bool(argv[2]);
1142 if (!strcmp(argv[0], "line-graphics")) {
1143 opt_line_graphics = parse_bool(argv[2]);
1147 if (!strcmp(argv[0], "line-number-interval")) {
1148 opt_num_interval = atoi(argv[2]);
1152 if (!strcmp(argv[0], "tab-size")) {
1153 opt_tab_size = atoi(argv[2]);
1157 if (!strcmp(argv[0], "commit-encoding")) {
1158 char *arg = argv[2];
1159 int delimiter = *arg;
1162 switch (delimiter) {
1165 for (arg++, i = 0; arg[i]; i++)
1166 if (arg[i] == delimiter) {
1171 string_ncopy(opt_encoding, arg, strlen(arg));
1176 config_msg = "Unknown variable name";
1180 /* Wants: mode request key */
1182 option_bind_command(int argc, char *argv[])
1184 enum request request;
1189 config_msg = "Wrong number of arguments given to bind command";
1193 if (set_keymap(&keymap, argv[0]) == ERR) {
1194 config_msg = "Unknown key map";
1198 key = get_key_value(argv[1]);
1200 config_msg = "Unknown key";
1204 request = get_request(argv[2]);
1205 if (request == REQ_NONE) {
1206 const char *obsolete[] = { "cherry-pick" };
1207 size_t namelen = strlen(argv[2]);
1210 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1211 if (namelen == strlen(obsolete[i]) &&
1212 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1213 config_msg = "Obsolete request name";
1218 if (request == REQ_NONE && *argv[2]++ == '!')
1219 request = add_run_request(keymap, key, argc - 2, argv + 2);
1220 if (request == REQ_NONE) {
1221 config_msg = "Unknown request name";
1225 add_keybinding(keymap, request, key);
1231 set_option(char *opt, char *value)
1238 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1239 argv[argc++] = value;
1242 /* Nothing more to tokenize or last available token. */
1243 if (!*value || argc >= ARRAY_SIZE(argv))
1247 while (isspace(*value))
1251 if (!strcmp(opt, "color"))
1252 return option_color_command(argc, argv);
1254 if (!strcmp(opt, "set"))
1255 return option_set_command(argc, argv);
1257 if (!strcmp(opt, "bind"))
1258 return option_bind_command(argc, argv);
1260 config_msg = "Unknown option command";
1265 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1270 config_msg = "Internal error";
1272 /* Check for comment markers, since read_properties() will
1273 * only ensure opt and value are split at first " \t". */
1274 optlen = strcspn(opt, "#");
1278 if (opt[optlen] != 0) {
1279 config_msg = "No option value";
1283 /* Look for comment endings in the value. */
1284 size_t len = strcspn(value, "#");
1286 if (len < valuelen) {
1288 value[valuelen] = 0;
1291 status = set_option(opt, value);
1294 if (status == ERR) {
1295 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1296 config_lineno, (int) optlen, opt, config_msg);
1297 config_errors = TRUE;
1300 /* Always keep going if errors are encountered. */
1305 load_option_file(const char *path)
1309 /* It's ok that the file doesn't exist. */
1310 file = fopen(path, "r");
1315 config_errors = FALSE;
1317 if (read_properties(file, " \t", read_option) == ERR ||
1318 config_errors == TRUE)
1319 fprintf(stderr, "Errors while loading %s.\n", path);
1325 char *home = getenv("HOME");
1326 char *tigrc_user = getenv("TIGRC_USER");
1327 char *tigrc_system = getenv("TIGRC_SYSTEM");
1328 char buf[SIZEOF_STR];
1330 add_builtin_run_requests();
1332 if (!tigrc_system) {
1333 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1337 load_option_file(tigrc_system);
1340 if (!home || !string_format(buf, "%s/.tigrc", home))
1344 load_option_file(tigrc_user);
1357 /* The display array of active views and the index of the current view. */
1358 static struct view *display[2];
1359 static unsigned int current_view;
1361 /* Reading from the prompt? */
1362 static bool input_mode = FALSE;
1364 #define foreach_displayed_view(view, i) \
1365 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1367 #define displayed_views() (display[1] != NULL ? 2 : 1)
1369 /* Current head and commit ID */
1370 static char ref_blob[SIZEOF_REF] = "";
1371 static char ref_commit[SIZEOF_REF] = "HEAD";
1372 static char ref_head[SIZEOF_REF] = "HEAD";
1375 const char *name; /* View name */
1376 const char *cmd_fmt; /* Default command line format */
1377 const char *cmd_env; /* Command line set via environment */
1378 const char *id; /* Points to either of ref_{head,commit,blob} */
1380 struct view_ops *ops; /* View operations */
1382 enum keymap keymap; /* What keymap does this view have */
1383 bool git_dir; /* Whether the view requires a git directory. */
1385 char cmd[SIZEOF_STR]; /* Command buffer */
1386 char ref[SIZEOF_REF]; /* Hovered commit reference */
1387 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1389 int height, width; /* The width and height of the main window */
1390 WINDOW *win; /* The main window */
1391 WINDOW *title; /* The title window living below the main window */
1394 unsigned long offset; /* Offset of the window top */
1395 unsigned long lineno; /* Current line number */
1398 char grep[SIZEOF_STR]; /* Search string */
1399 regex_t *regex; /* Pre-compiled regex */
1401 /* If non-NULL, points to the view that opened this view. If this view
1402 * is closed tig will switch back to the parent view. */
1403 struct view *parent;
1406 size_t lines; /* Total number of lines */
1407 struct line *line; /* Line index */
1408 size_t line_alloc; /* Total number of allocated lines */
1409 size_t line_size; /* Total number of used lines */
1410 unsigned int digits; /* Number of digits in the lines member. */
1413 struct line *curline; /* Line currently being drawn. */
1414 enum line_type curtype; /* Attribute currently used for drawing. */
1415 unsigned long col; /* Column when drawing. */
1423 /* What type of content being displayed. Used in the title bar. */
1425 /* Open and reads in all view content. */
1426 bool (*open)(struct view *view);
1427 /* Read one line; updates view->line. */
1428 bool (*read)(struct view *view, char *data);
1429 /* Draw one line; @lineno must be < view->height. */
1430 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1431 /* Depending on view handle a special requests. */
1432 enum request (*request)(struct view *view, enum request request, struct line *line);
1433 /* Search for regex in a line. */
1434 bool (*grep)(struct view *view, struct line *line);
1436 void (*select)(struct view *view, struct line *line);
1439 static struct view_ops pager_ops;
1440 static struct view_ops main_ops;
1441 static struct view_ops tree_ops;
1442 static struct view_ops blob_ops;
1443 static struct view_ops blame_ops;
1444 static struct view_ops help_ops;
1445 static struct view_ops status_ops;
1446 static struct view_ops stage_ops;
1448 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1449 { name, cmd, #env, ref, ops, map, git }
1451 #define VIEW_(id, name, ops, git, ref) \
1452 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1455 static struct view views[] = {
1456 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1457 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1458 VIEW_(LOG, "log", &pager_ops, TRUE, ref_head),
1459 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1460 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1461 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1462 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1463 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1464 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1465 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1468 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1469 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1471 #define foreach_view(view, i) \
1472 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1474 #define view_is_displayed(view) \
1475 (view == display[0] || view == display[1])
1482 static int line_graphics[] = {
1483 /* LINE_GRAPHIC_VLINE: */ '|'
1487 set_view_attr(struct view *view, enum line_type type)
1489 if (!view->curline->selected && view->curtype != type) {
1490 wattrset(view->win, get_line_attr(type));
1491 wchgat(view->win, -1, 0, type, NULL);
1492 view->curtype = type;
1497 draw_chars(struct view *view, enum line_type type, const char *string,
1498 int max_len, bool use_tilde)
1502 int trimmed = FALSE;
1508 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1510 col = len = strlen(string);
1511 if (len > max_len) {
1515 col = len = max_len;
1520 set_view_attr(view, type);
1521 waddnstr(view->win, string, len);
1522 if (trimmed && use_tilde) {
1523 set_view_attr(view, LINE_DELIMITER);
1524 waddch(view->win, '~');
1532 draw_space(struct view *view, enum line_type type, int max, int spaces)
1534 static char space[] = " ";
1537 spaces = MIN(max, spaces);
1539 while (spaces > 0) {
1540 int len = MIN(spaces, sizeof(space) - 1);
1542 col += draw_chars(view, type, space, spaces, FALSE);
1550 draw_lineno(struct view *view, unsigned int lineno)
1553 int digits3 = view->digits < 3 ? 3 : view->digits;
1554 int max_number = MIN(digits3, STRING_SIZE(number));
1555 int max = view->width - view->col;
1558 if (max < max_number)
1561 lineno += view->offset + 1;
1562 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1563 static char fmt[] = "%1ld";
1565 if (view->digits <= 9)
1566 fmt[1] = '0' + digits3;
1568 if (!string_format(number, fmt, lineno))
1570 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1572 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1576 set_view_attr(view, LINE_DEFAULT);
1577 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1582 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1585 return view->width - view->col <= 0;
1589 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1591 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1592 return view->width - view->col <= 0;
1596 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1598 int max = view->width - view->col;
1604 set_view_attr(view, type);
1605 /* Using waddch() instead of waddnstr() ensures that
1606 * they'll be rendered correctly for the cursor line. */
1607 for (i = 0; i < size; i++)
1608 waddch(view->win, graphic[i]);
1612 waddch(view->win, ' ');
1616 return view->width - view->col <= 0;
1620 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1622 int max = MIN(view->width - view->col, len);
1626 col = draw_chars(view, type, text, max - 1, trim);
1628 col = draw_space(view, type, max - 1, max - 1);
1630 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1631 return view->width - view->col <= 0;
1635 draw_date(struct view *view, struct tm *time)
1637 char buf[DATE_COLS];
1642 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1643 date = timelen ? buf : NULL;
1645 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1649 draw_view_line(struct view *view, unsigned int lineno)
1652 bool selected = (view->offset + lineno == view->lineno);
1655 assert(view_is_displayed(view));
1657 if (view->offset + lineno >= view->lines)
1660 line = &view->line[view->offset + lineno];
1662 wmove(view->win, lineno, 0);
1664 view->curline = line;
1665 view->curtype = LINE_NONE;
1666 line->selected = FALSE;
1669 set_view_attr(view, LINE_CURSOR);
1670 line->selected = TRUE;
1671 view->ops->select(view, line);
1672 } else if (line->selected) {
1673 wclrtoeol(view->win);
1676 scrollok(view->win, FALSE);
1677 draw_ok = view->ops->draw(view, line, lineno);
1678 scrollok(view->win, TRUE);
1684 redraw_view_dirty(struct view *view)
1689 for (lineno = 0; lineno < view->height; lineno++) {
1690 struct line *line = &view->line[view->offset + lineno];
1696 if (!draw_view_line(view, lineno))
1702 redrawwin(view->win);
1704 wnoutrefresh(view->win);
1706 wrefresh(view->win);
1710 redraw_view_from(struct view *view, int lineno)
1712 assert(0 <= lineno && lineno < view->height);
1714 for (; lineno < view->height; lineno++) {
1715 if (!draw_view_line(view, lineno))
1719 redrawwin(view->win);
1721 wnoutrefresh(view->win);
1723 wrefresh(view->win);
1727 redraw_view(struct view *view)
1730 redraw_view_from(view, 0);
1735 update_view_title(struct view *view)
1737 char buf[SIZEOF_STR];
1738 char state[SIZEOF_STR];
1739 size_t bufpos = 0, statelen = 0;
1741 assert(view_is_displayed(view));
1743 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1744 unsigned int view_lines = view->offset + view->height;
1745 unsigned int lines = view->lines
1746 ? MIN(view_lines, view->lines) * 100 / view->lines
1749 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1756 time_t secs = time(NULL) - view->start_time;
1758 /* Three git seconds are a long time ... */
1760 string_format_from(state, &statelen, " %lds", secs);
1764 string_format_from(buf, &bufpos, "[%s]", view->name);
1765 if (*view->ref && bufpos < view->width) {
1766 size_t refsize = strlen(view->ref);
1767 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1769 if (minsize < view->width)
1770 refsize = view->width - minsize + 7;
1771 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1774 if (statelen && bufpos < view->width) {
1775 string_format_from(buf, &bufpos, " %s", state);
1778 if (view == display[current_view])
1779 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1781 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1783 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1784 wclrtoeol(view->title);
1785 wmove(view->title, 0, view->width - 1);
1788 wnoutrefresh(view->title);
1790 wrefresh(view->title);
1794 resize_display(void)
1797 struct view *base = display[0];
1798 struct view *view = display[1] ? display[1] : display[0];
1800 /* Setup window dimensions */
1802 getmaxyx(stdscr, base->height, base->width);
1804 /* Make room for the status window. */
1808 /* Horizontal split. */
1809 view->width = base->width;
1810 view->height = SCALE_SPLIT_VIEW(base->height);
1811 base->height -= view->height;
1813 /* Make room for the title bar. */
1817 /* Make room for the title bar. */
1822 foreach_displayed_view (view, i) {
1824 view->win = newwin(view->height, 0, offset, 0);
1826 die("Failed to create %s view", view->name);
1828 scrollok(view->win, TRUE);
1830 view->title = newwin(1, 0, offset + view->height, 0);
1832 die("Failed to create title window");
1835 wresize(view->win, view->height, view->width);
1836 mvwin(view->win, offset, 0);
1837 mvwin(view->title, offset + view->height, 0);
1840 offset += view->height + 1;
1845 redraw_display(void)
1850 foreach_displayed_view (view, i) {
1852 update_view_title(view);
1857 update_display_cursor(struct view *view)
1859 /* Move the cursor to the right-most column of the cursor line.
1861 * XXX: This could turn out to be a bit expensive, but it ensures that
1862 * the cursor does not jump around. */
1864 wmove(view->win, view->lineno - view->offset, view->width - 1);
1865 wrefresh(view->win);
1873 /* Scrolling backend */
1875 do_scroll_view(struct view *view, int lines)
1877 bool redraw_current_line = FALSE;
1879 /* The rendering expects the new offset. */
1880 view->offset += lines;
1882 assert(0 <= view->offset && view->offset < view->lines);
1885 /* Move current line into the view. */
1886 if (view->lineno < view->offset) {
1887 view->lineno = view->offset;
1888 redraw_current_line = TRUE;
1889 } else if (view->lineno >= view->offset + view->height) {
1890 view->lineno = view->offset + view->height - 1;
1891 redraw_current_line = TRUE;
1894 assert(view->offset <= view->lineno && view->lineno < view->lines);
1896 /* Redraw the whole screen if scrolling is pointless. */
1897 if (view->height < ABS(lines)) {
1901 int line = lines > 0 ? view->height - lines : 0;
1902 int end = line + ABS(lines);
1904 wscrl(view->win, lines);
1906 for (; line < end; line++) {
1907 if (!draw_view_line(view, line))
1911 if (redraw_current_line)
1912 draw_view_line(view, view->lineno - view->offset);
1915 redrawwin(view->win);
1916 wrefresh(view->win);
1920 /* Scroll frontend */
1922 scroll_view(struct view *view, enum request request)
1926 assert(view_is_displayed(view));
1929 case REQ_SCROLL_PAGE_DOWN:
1930 lines = view->height;
1931 case REQ_SCROLL_LINE_DOWN:
1932 if (view->offset + lines > view->lines)
1933 lines = view->lines - view->offset;
1935 if (lines == 0 || view->offset + view->height >= view->lines) {
1936 report("Cannot scroll beyond the last line");
1941 case REQ_SCROLL_PAGE_UP:
1942 lines = view->height;
1943 case REQ_SCROLL_LINE_UP:
1944 if (lines > view->offset)
1945 lines = view->offset;
1948 report("Cannot scroll beyond the first line");
1956 die("request %d not handled in switch", request);
1959 do_scroll_view(view, lines);
1964 move_view(struct view *view, enum request request)
1966 int scroll_steps = 0;
1970 case REQ_MOVE_FIRST_LINE:
1971 steps = -view->lineno;
1974 case REQ_MOVE_LAST_LINE:
1975 steps = view->lines - view->lineno - 1;
1978 case REQ_MOVE_PAGE_UP:
1979 steps = view->height > view->lineno
1980 ? -view->lineno : -view->height;
1983 case REQ_MOVE_PAGE_DOWN:
1984 steps = view->lineno + view->height >= view->lines
1985 ? view->lines - view->lineno - 1 : view->height;
1997 die("request %d not handled in switch", request);
2000 if (steps <= 0 && view->lineno == 0) {
2001 report("Cannot move beyond the first line");
2004 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2005 report("Cannot move beyond the last line");
2009 /* Move the current line */
2010 view->lineno += steps;
2011 assert(0 <= view->lineno && view->lineno < view->lines);
2013 /* Check whether the view needs to be scrolled */
2014 if (view->lineno < view->offset ||
2015 view->lineno >= view->offset + view->height) {
2016 scroll_steps = steps;
2017 if (steps < 0 && -steps > view->offset) {
2018 scroll_steps = -view->offset;
2020 } else if (steps > 0) {
2021 if (view->lineno == view->lines - 1 &&
2022 view->lines > view->height) {
2023 scroll_steps = view->lines - view->offset - 1;
2024 if (scroll_steps >= view->height)
2025 scroll_steps -= view->height - 1;
2030 if (!view_is_displayed(view)) {
2031 view->offset += scroll_steps;
2032 assert(0 <= view->offset && view->offset < view->lines);
2033 view->ops->select(view, &view->line[view->lineno]);
2037 /* Repaint the old "current" line if we be scrolling */
2038 if (ABS(steps) < view->height)
2039 draw_view_line(view, view->lineno - steps - view->offset);
2042 do_scroll_view(view, scroll_steps);
2046 /* Draw the current line */
2047 draw_view_line(view, view->lineno - view->offset);
2049 redrawwin(view->win);
2050 wrefresh(view->win);
2059 static void search_view(struct view *view, enum request request);
2062 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2064 assert(view_is_displayed(view));
2066 if (!view->ops->grep(view, line))
2069 if (lineno - view->offset >= view->height) {
2070 view->offset = lineno;
2071 view->lineno = lineno;
2075 unsigned long old_lineno = view->lineno - view->offset;
2077 view->lineno = lineno;
2078 draw_view_line(view, old_lineno);
2080 draw_view_line(view, view->lineno - view->offset);
2081 redrawwin(view->win);
2082 wrefresh(view->win);
2085 report("Line %ld matches '%s'", lineno + 1, view->grep);
2090 find_next(struct view *view, enum request request)
2092 unsigned long lineno = view->lineno;
2097 report("No previous search");
2099 search_view(view, request);
2109 case REQ_SEARCH_BACK:
2118 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2119 lineno += direction;
2121 /* Note, lineno is unsigned long so will wrap around in which case it
2122 * will become bigger than view->lines. */
2123 for (; lineno < view->lines; lineno += direction) {
2124 struct line *line = &view->line[lineno];
2126 if (find_next_line(view, lineno, line))
2130 report("No match found for '%s'", view->grep);
2134 search_view(struct view *view, enum request request)
2139 regfree(view->regex);
2142 view->regex = calloc(1, sizeof(*view->regex));
2147 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2148 if (regex_err != 0) {
2149 char buf[SIZEOF_STR] = "unknown error";
2151 regerror(regex_err, view->regex, buf, sizeof(buf));
2152 report("Search failed: %s", buf);
2156 string_copy(view->grep, opt_search);
2158 find_next(view, request);
2162 * Incremental updating
2166 end_update(struct view *view, bool force)
2170 while (!view->ops->read(view, NULL))
2173 set_nonblocking_input(FALSE);
2174 if (view->pipe == stdin)
2182 begin_update(struct view *view)
2185 end_update(view, TRUE);
2188 string_copy(view->cmd, opt_cmd);
2190 /* When running random commands, initially show the
2191 * command in the title. However, it maybe later be
2192 * overwritten if a commit line is selected. */
2193 if (view == VIEW(REQ_VIEW_PAGER))
2194 string_copy(view->ref, view->cmd);
2198 } else if (view == VIEW(REQ_VIEW_TREE)) {
2199 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2200 char path[SIZEOF_STR];
2202 if (strcmp(view->vid, view->id))
2203 opt_path[0] = path[0] = 0;
2204 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2207 if (!string_format(view->cmd, format, view->id, path))
2211 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2212 const char *id = view->id;
2214 if (!string_format(view->cmd, format, id, id, id, id, id))
2217 /* Put the current ref_* value to the view title ref
2218 * member. This is needed by the blob view. Most other
2219 * views sets it automatically after loading because the
2220 * first line is a commit line. */
2221 string_copy_rev(view->ref, view->id);
2224 /* Special case for the pager view. */
2226 view->pipe = opt_pipe;
2229 view->pipe = popen(view->cmd, "r");
2235 set_nonblocking_input(TRUE);
2240 string_copy_rev(view->vid, view->id);
2245 for (i = 0; i < view->lines; i++)
2246 if (view->line[i].data)
2247 free(view->line[i].data);
2253 view->start_time = time(NULL);
2258 #define ITEM_CHUNK_SIZE 256
2260 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2262 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2263 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2265 if (mem == NULL || num_chunks != num_chunks_new) {
2266 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2267 mem = realloc(mem, *size * item_size);
2273 static struct line *
2274 realloc_lines(struct view *view, size_t line_size)
2276 size_t alloc = view->line_alloc;
2277 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2278 sizeof(*view->line));
2284 view->line_alloc = alloc;
2285 view->line_size = line_size;
2290 update_view(struct view *view)
2292 char in_buffer[BUFSIZ];
2293 char out_buffer[BUFSIZ * 2];
2295 /* The number of lines to read. If too low it will cause too much
2296 * redrawing (and possible flickering), if too high responsiveness
2298 unsigned long lines = view->height;
2299 int redraw_from = -1;
2304 /* Only redraw if lines are visible. */
2305 if (view->offset + view->height >= view->lines)
2306 redraw_from = view->lines - view->offset;
2308 /* FIXME: This is probably not perfect for backgrounded views. */
2309 if (!realloc_lines(view, view->lines + lines))
2312 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2313 size_t linelen = strlen(line);
2316 line[linelen - 1] = 0;
2318 if (opt_iconv != ICONV_NONE) {
2319 ICONV_CONST char *inbuf = line;
2320 size_t inlen = linelen;
2322 char *outbuf = out_buffer;
2323 size_t outlen = sizeof(out_buffer);
2327 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2328 if (ret != (size_t) -1) {
2330 linelen = strlen(out_buffer);
2334 if (!view->ops->read(view, line))
2344 lines = view->lines;
2345 for (digits = 0; lines; digits++)
2348 /* Keep the displayed view in sync with line number scaling. */
2349 if (digits != view->digits) {
2350 view->digits = digits;
2355 if (!view_is_displayed(view))
2358 if (view == VIEW(REQ_VIEW_TREE)) {
2359 /* Clear the view and redraw everything since the tree sorting
2360 * might have rearranged things. */
2363 } else if (redraw_from >= 0) {
2364 /* If this is an incremental update, redraw the previous line
2365 * since for commits some members could have changed when
2366 * loading the main view. */
2367 if (redraw_from > 0)
2370 /* Since revision graph visualization requires knowledge
2371 * about the parent commit, it causes a further one-off
2372 * needed to be redrawn for incremental updates. */
2373 if (redraw_from > 0 && opt_rev_graph)
2376 /* Incrementally draw avoids flickering. */
2377 redraw_view_from(view, redraw_from);
2380 if (view == VIEW(REQ_VIEW_BLAME))
2381 redraw_view_dirty(view);
2383 /* Update the title _after_ the redraw so that if the redraw picks up a
2384 * commit reference in view->ref it'll be available here. */
2385 update_view_title(view);
2388 if (ferror(view->pipe)) {
2389 report("Failed to read: %s", strerror(errno));
2390 end_update(view, TRUE);
2392 } else if (feof(view->pipe)) {
2394 end_update(view, FALSE);
2400 report("Allocation failure");
2401 end_update(view, TRUE);
2405 static struct line *
2406 add_line_data(struct view *view, void *data, enum line_type type)
2408 struct line *line = &view->line[view->lines++];
2410 memset(line, 0, sizeof(*line));
2417 static struct line *
2418 add_line_text(struct view *view, char *data, enum line_type type)
2421 data = strdup(data);
2423 return data ? add_line_data(view, data, type) : NULL;
2432 OPEN_DEFAULT = 0, /* Use default view switching. */
2433 OPEN_SPLIT = 1, /* Split current view. */
2434 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2435 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2436 OPEN_NOMAXIMIZE = 8 /* Do not maximize the current view. */
2440 open_view(struct view *prev, enum request request, enum open_flags flags)
2442 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2443 bool split = !!(flags & OPEN_SPLIT);
2444 bool reload = !!(flags & OPEN_RELOAD);
2445 bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2446 struct view *view = VIEW(request);
2447 int nviews = displayed_views();
2448 struct view *base_view = display[0];
2450 if (view == prev && nviews == 1 && !reload) {
2451 report("Already in %s view", view->name);
2455 if (view->git_dir && !opt_git_dir[0]) {
2456 report("The %s view is disabled in pager view", view->name);
2464 } else if (!nomaximize) {
2465 /* Maximize the current view. */
2466 memset(display, 0, sizeof(display));
2468 display[current_view] = view;
2471 /* Resize the view when switching between split- and full-screen,
2472 * or when switching between two different full-screen views. */
2473 if (nviews != displayed_views() ||
2474 (nviews == 1 && base_view != display[0]))
2477 if (view->ops->open) {
2478 if (!view->ops->open(view)) {
2479 report("Failed to load %s view", view->name);
2483 } else if ((reload || strcmp(view->vid, view->id)) &&
2484 !begin_update(view)) {
2485 report("Failed to load %s view", view->name);
2489 if (split && prev->lineno - prev->offset >= prev->height) {
2490 /* Take the title line into account. */
2491 int lines = prev->lineno - prev->offset - prev->height + 1;
2493 /* Scroll the view that was split if the current line is
2494 * outside the new limited view. */
2495 do_scroll_view(prev, lines);
2498 if (prev && view != prev) {
2499 if (split && !backgrounded) {
2500 /* "Blur" the previous view. */
2501 update_view_title(prev);
2504 view->parent = prev;
2507 if (view->pipe && view->lines == 0) {
2508 /* Clear the old view and let the incremental updating refill
2517 /* If the view is backgrounded the above calls to report()
2518 * won't redraw the view title. */
2520 update_view_title(view);
2524 open_external_viewer(const char *cmd)
2526 def_prog_mode(); /* save current tty modes */
2527 endwin(); /* restore original tty modes */
2529 fprintf(stderr, "Press Enter to continue");
2536 open_mergetool(const char *file)
2538 char cmd[SIZEOF_STR];
2539 char file_sq[SIZEOF_STR];
2541 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2542 string_format(cmd, "git mergetool %s", file_sq)) {
2543 open_external_viewer(cmd);
2548 open_editor(bool from_root, const char *file)
2550 char cmd[SIZEOF_STR];
2551 char file_sq[SIZEOF_STR];
2553 char *prefix = from_root ? opt_cdup : "";
2555 editor = getenv("GIT_EDITOR");
2556 if (!editor && *opt_editor)
2557 editor = opt_editor;
2559 editor = getenv("VISUAL");
2561 editor = getenv("EDITOR");
2565 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2566 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2567 open_external_viewer(cmd);
2572 open_run_request(enum request request)
2574 struct run_request *req = get_run_request(request);
2575 char buf[SIZEOF_STR * 2];
2580 report("Unknown run request");
2588 char *next = strstr(cmd, "%(");
2589 int len = next - cmd;
2596 } else if (!strncmp(next, "%(head)", 7)) {
2599 } else if (!strncmp(next, "%(commit)", 9)) {
2602 } else if (!strncmp(next, "%(blob)", 7)) {
2606 report("Unknown replacement in run request: `%s`", req->cmd);
2610 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2614 next = strchr(next, ')') + 1;
2618 open_external_viewer(buf);
2622 * User request switch noodle
2626 view_driver(struct view *view, enum request request)
2630 if (request == REQ_NONE) {
2635 if (request > REQ_NONE) {
2636 open_run_request(request);
2637 /* FIXME: When all views can refresh always do this. */
2638 if (view == VIEW(REQ_VIEW_STATUS) ||
2639 view == VIEW(REQ_VIEW_STAGE))
2640 request = REQ_REFRESH;
2645 if (view && view->lines) {
2646 request = view->ops->request(view, request, &view->line[view->lineno]);
2647 if (request == REQ_NONE)
2654 case REQ_MOVE_PAGE_UP:
2655 case REQ_MOVE_PAGE_DOWN:
2656 case REQ_MOVE_FIRST_LINE:
2657 case REQ_MOVE_LAST_LINE:
2658 move_view(view, request);
2661 case REQ_SCROLL_LINE_DOWN:
2662 case REQ_SCROLL_LINE_UP:
2663 case REQ_SCROLL_PAGE_DOWN:
2664 case REQ_SCROLL_PAGE_UP:
2665 scroll_view(view, request);
2668 case REQ_VIEW_BLAME:
2670 report("No file chosen, press %s to open tree view",
2671 get_key(REQ_VIEW_TREE));
2674 open_view(view, request, OPEN_DEFAULT);
2679 report("No file chosen, press %s to open tree view",
2680 get_key(REQ_VIEW_TREE));
2683 open_view(view, request, OPEN_DEFAULT);
2686 case REQ_VIEW_PAGER:
2687 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2688 report("No pager content, press %s to run command from prompt",
2689 get_key(REQ_PROMPT));
2692 open_view(view, request, OPEN_DEFAULT);
2695 case REQ_VIEW_STAGE:
2696 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2697 report("No stage content, press %s to open the status view and choose file",
2698 get_key(REQ_VIEW_STATUS));
2701 open_view(view, request, OPEN_DEFAULT);
2704 case REQ_VIEW_STATUS:
2705 if (opt_is_inside_work_tree == FALSE) {
2706 report("The status view requires a working tree");
2709 open_view(view, request, OPEN_DEFAULT);
2717 open_view(view, request, OPEN_DEFAULT);
2722 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2724 if ((view == VIEW(REQ_VIEW_DIFF) &&
2725 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2726 (view == VIEW(REQ_VIEW_DIFF) &&
2727 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2728 (view == VIEW(REQ_VIEW_STAGE) &&
2729 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2730 (view == VIEW(REQ_VIEW_BLOB) &&
2731 view->parent == VIEW(REQ_VIEW_TREE))) {
2734 view = view->parent;
2735 line = view->lineno;
2736 move_view(view, request);
2737 if (view_is_displayed(view))
2738 update_view_title(view);
2739 if (line != view->lineno)
2740 view->ops->request(view, REQ_ENTER,
2741 &view->line[view->lineno]);
2744 move_view(view, request);
2750 int nviews = displayed_views();
2751 int next_view = (current_view + 1) % nviews;
2753 if (next_view == current_view) {
2754 report("Only one view is displayed");
2758 current_view = next_view;
2759 /* Blur out the title of the previous view. */
2760 update_view_title(view);
2765 report("Refreshing is not yet supported for the %s view", view->name);
2769 if (displayed_views() == 2)
2770 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2773 case REQ_TOGGLE_LINENO:
2774 opt_line_number = !opt_line_number;
2778 case REQ_TOGGLE_DATE:
2779 opt_date = !opt_date;
2783 case REQ_TOGGLE_AUTHOR:
2784 opt_author = !opt_author;
2788 case REQ_TOGGLE_REV_GRAPH:
2789 opt_rev_graph = !opt_rev_graph;
2793 case REQ_TOGGLE_REFS:
2794 opt_show_refs = !opt_show_refs;
2799 /* Always reload^Wrerun commands from the prompt. */
2800 open_view(view, opt_request, OPEN_RELOAD);
2804 case REQ_SEARCH_BACK:
2805 search_view(view, request);
2810 find_next(view, request);
2813 case REQ_STOP_LOADING:
2814 for (i = 0; i < ARRAY_SIZE(views); i++) {
2817 report("Stopped loading the %s view", view->name),
2818 end_update(view, TRUE);
2822 case REQ_SHOW_VERSION:
2823 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2826 case REQ_SCREEN_RESIZE:
2829 case REQ_SCREEN_REDRAW:
2834 report("Nothing to edit");
2839 report("Nothing to enter");
2843 case REQ_VIEW_CLOSE:
2844 /* XXX: Mark closed views by letting view->parent point to the
2845 * view itself. Parents to closed view should never be
2848 view->parent->parent != view->parent) {
2849 memset(display, 0, sizeof(display));
2851 display[current_view] = view->parent;
2852 view->parent = view;
2862 /* An unknown key will show most commonly used commands. */
2863 report("Unknown key, press 'h' for help");
2876 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2878 char *text = line->data;
2880 if (opt_line_number && draw_lineno(view, lineno))
2883 draw_text(view, line->type, text, TRUE);
2888 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2890 char refbuf[SIZEOF_STR];
2894 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2897 pipe = popen(refbuf, "r");
2901 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2902 ref = chomp_string(ref);
2908 /* This is the only fatal call, since it can "corrupt" the buffer. */
2909 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2916 add_pager_refs(struct view *view, struct line *line)
2918 char buf[SIZEOF_STR];
2919 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2921 size_t bufpos = 0, refpos = 0;
2922 const char *sep = "Refs: ";
2923 bool is_tag = FALSE;
2925 assert(line->type == LINE_COMMIT);
2927 refs = get_refs(commit_id);
2929 if (view == VIEW(REQ_VIEW_DIFF))
2930 goto try_add_describe_ref;
2935 struct ref *ref = refs[refpos];
2936 char *fmt = ref->tag ? "%s[%s]" :
2937 ref->remote ? "%s<%s>" : "%s%s";
2939 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2944 } while (refs[refpos++]->next);
2946 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2947 try_add_describe_ref:
2948 /* Add <tag>-g<commit_id> "fake" reference. */
2949 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2956 if (!realloc_lines(view, view->line_size + 1))
2959 add_line_text(view, buf, LINE_PP_REFS);
2963 pager_read(struct view *view, char *data)
2970 line = add_line_text(view, data, get_line_type(data));
2974 if (line->type == LINE_COMMIT &&
2975 (view == VIEW(REQ_VIEW_DIFF) ||
2976 view == VIEW(REQ_VIEW_LOG)))
2977 add_pager_refs(view, line);
2983 pager_request(struct view *view, enum request request, struct line *line)
2987 if (request != REQ_ENTER)
2990 if (line->type == LINE_COMMIT &&
2991 (view == VIEW(REQ_VIEW_LOG) ||
2992 view == VIEW(REQ_VIEW_PAGER))) {
2993 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2997 /* Always scroll the view even if it was split. That way
2998 * you can use Enter to scroll through the log view and
2999 * split open each commit diff. */
3000 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3002 /* FIXME: A minor workaround. Scrolling the view will call report("")
3003 * but if we are scrolling a non-current view this won't properly
3004 * update the view title. */
3006 update_view_title(view);
3012 pager_grep(struct view *view, struct line *line)
3015 char *text = line->data;
3020 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3027 pager_select(struct view *view, struct line *line)
3029 if (line->type == LINE_COMMIT) {
3030 char *text = (char *)line->data + STRING_SIZE("commit ");
3032 if (view != VIEW(REQ_VIEW_PAGER))
3033 string_copy_rev(view->ref, text);
3034 string_copy_rev(ref_commit, text);
3038 static struct view_ops pager_ops = {
3054 help_open(struct view *view)
3057 int lines = ARRAY_SIZE(req_info) + 2;
3060 if (view->lines > 0)
3063 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3064 if (!req_info[i].request)
3067 lines += run_requests + 1;
3069 view->line = calloc(lines, sizeof(*view->line));
3073 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3075 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3078 if (req_info[i].request == REQ_NONE)
3081 if (!req_info[i].request) {
3082 add_line_text(view, "", LINE_DEFAULT);
3083 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3087 key = get_key(req_info[i].request);
3089 key = "(no key defined)";
3091 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3094 add_line_text(view, buf, LINE_DEFAULT);
3098 add_line_text(view, "", LINE_DEFAULT);
3099 add_line_text(view, "External commands:", LINE_DEFAULT);
3102 for (i = 0; i < run_requests; i++) {
3103 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3109 key = get_key_name(req->key);
3111 key = "(no key defined)";
3113 if (!string_format(buf, " %-10s %-14s `%s`",
3114 keymap_table[req->keymap].name,
3118 add_line_text(view, buf, LINE_DEFAULT);
3124 static struct view_ops help_ops = {
3139 struct tree_stack_entry {
3140 struct tree_stack_entry *prev; /* Entry below this in the stack */
3141 unsigned long lineno; /* Line number to restore */
3142 char *name; /* Position of name in opt_path */
3145 /* The top of the path stack. */
3146 static struct tree_stack_entry *tree_stack = NULL;
3147 unsigned long tree_lineno = 0;
3150 pop_tree_stack_entry(void)
3152 struct tree_stack_entry *entry = tree_stack;
3154 tree_lineno = entry->lineno;
3156 tree_stack = entry->prev;
3161 push_tree_stack_entry(char *name, unsigned long lineno)
3163 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3164 size_t pathlen = strlen(opt_path);
3169 entry->prev = tree_stack;
3170 entry->name = opt_path + pathlen;
3173 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3174 pop_tree_stack_entry();
3178 /* Move the current line to the first tree entry. */
3180 entry->lineno = lineno;
3183 /* Parse output from git-ls-tree(1):
3185 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3186 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3187 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3188 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3191 #define SIZEOF_TREE_ATTR \
3192 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3194 #define TREE_UP_FORMAT "040000 tree %s\t.."
3197 tree_compare_entry(enum line_type type1, char *name1,
3198 enum line_type type2, char *name2)
3200 if (type1 != type2) {
3201 if (type1 == LINE_TREE_DIR)
3206 return strcmp(name1, name2);
3210 tree_path(struct line *line)
3212 char *path = line->data;
3214 return path + SIZEOF_TREE_ATTR;
3218 tree_read(struct view *view, char *text)
3220 size_t textlen = text ? strlen(text) : 0;
3221 char buf[SIZEOF_STR];
3223 enum line_type type;
3224 bool first_read = view->lines == 0;
3228 if (textlen <= SIZEOF_TREE_ATTR)
3231 type = text[STRING_SIZE("100644 ")] == 't'
3232 ? LINE_TREE_DIR : LINE_TREE_FILE;
3235 /* Add path info line */
3236 if (!string_format(buf, "Directory path /%s", opt_path) ||
3237 !realloc_lines(view, view->line_size + 1) ||
3238 !add_line_text(view, buf, LINE_DEFAULT))
3241 /* Insert "link" to parent directory. */
3243 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3244 !realloc_lines(view, view->line_size + 1) ||
3245 !add_line_text(view, buf, LINE_TREE_DIR))
3250 /* Strip the path part ... */
3252 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3253 size_t striplen = strlen(opt_path);
3254 char *path = text + SIZEOF_TREE_ATTR;
3256 if (pathlen > striplen)
3257 memmove(path, path + striplen,
3258 pathlen - striplen + 1);
3261 /* Skip "Directory ..." and ".." line. */
3262 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3263 struct line *line = &view->line[pos];
3264 char *path1 = tree_path(line);
3265 char *path2 = text + SIZEOF_TREE_ATTR;
3266 int cmp = tree_compare_entry(line->type, path1, type, path2);
3271 text = strdup(text);
3275 if (view->lines > pos)
3276 memmove(&view->line[pos + 1], &view->line[pos],
3277 (view->lines - pos) * sizeof(*line));
3279 line = &view->line[pos];
3286 if (!add_line_text(view, text, type))
3289 if (tree_lineno > view->lineno) {
3290 view->lineno = tree_lineno;
3298 tree_request(struct view *view, enum request request, struct line *line)
3300 enum open_flags flags;
3302 if (request == REQ_VIEW_BLAME) {
3303 char *filename = tree_path(line);
3305 if (line->type == LINE_TREE_DIR) {
3306 report("Cannot show blame for directory %s", opt_path);
3310 string_copy(opt_ref, view->vid);
3311 string_format(opt_file, "%s%s", opt_path, filename);
3314 if (request == REQ_TREE_PARENT) {
3317 request = REQ_ENTER;
3318 line = &view->line[1];
3320 /* quit view if at top of tree */
3321 return REQ_VIEW_CLOSE;
3324 if (request != REQ_ENTER)
3327 /* Cleanup the stack if the tree view is at a different tree. */
3328 while (!*opt_path && tree_stack)
3329 pop_tree_stack_entry();
3331 switch (line->type) {
3333 /* Depending on whether it is a subdir or parent (updir?) link
3334 * mangle the path buffer. */
3335 if (line == &view->line[1] && *opt_path) {
3336 pop_tree_stack_entry();
3339 char *basename = tree_path(line);
3341 push_tree_stack_entry(basename, view->lineno);
3344 /* Trees and subtrees share the same ID, so they are not not
3345 * unique like blobs. */
3346 flags = OPEN_RELOAD;
3347 request = REQ_VIEW_TREE;
3350 case LINE_TREE_FILE:
3351 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3352 request = REQ_VIEW_BLOB;
3359 open_view(view, request, flags);
3360 if (request == REQ_VIEW_TREE) {
3361 view->lineno = tree_lineno;
3368 tree_select(struct view *view, struct line *line)
3370 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3372 if (line->type == LINE_TREE_FILE) {
3373 string_copy_rev(ref_blob, text);
3375 } else if (line->type != LINE_TREE_DIR) {
3379 string_copy_rev(view->ref, text);
3382 static struct view_ops tree_ops = {
3393 blob_read(struct view *view, char *line)
3397 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3400 static struct view_ops blob_ops = {
3413 * Loading the blame view is a two phase job:
3415 * 1. File content is read either using opt_file from the
3416 * filesystem or using git-cat-file.
3417 * 2. Then blame information is incrementally added by
3418 * reading output from git-blame.
3421 struct blame_commit {
3422 char id[SIZEOF_REV]; /* SHA1 ID. */
3423 char title[128]; /* First line of the commit message. */
3424 char author[75]; /* Author of the commit. */
3425 struct tm time; /* Date from the author ident. */
3426 char filename[128]; /* Name of file. */
3430 struct blame_commit *commit;
3431 unsigned int header:1;
3435 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3436 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3439 blame_open(struct view *view)
3441 char path[SIZEOF_STR];
3442 char ref[SIZEOF_STR] = "";
3444 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3447 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3451 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3454 view->pipe = fopen(opt_file, "r");
3456 !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3461 view->pipe = popen(view->cmd, "r");
3465 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3468 string_format(view->ref, "%s ...", opt_file);
3469 string_copy_rev(view->vid, opt_file);
3470 set_nonblocking_input(TRUE);
3475 for (i = 0; i < view->lines; i++)
3476 free(view->line[i].data);
3480 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3481 view->offset = view->lines = view->lineno = 0;
3483 view->start_time = time(NULL);
3488 static struct blame_commit *
3489 get_blame_commit(struct view *view, const char *id)
3493 for (i = 0; i < view->lines; i++) {
3494 struct blame *blame = view->line[i].data;
3499 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3500 return blame->commit;
3504 struct blame_commit *commit = calloc(1, sizeof(*commit));
3507 string_ncopy(commit->id, id, SIZEOF_REV);
3513 parse_number(char **posref, size_t *number, size_t min, size_t max)
3515 char *pos = *posref;
3518 pos = strchr(pos + 1, ' ');
3519 if (!pos || !isdigit(pos[1]))
3521 *number = atoi(pos + 1);
3522 if (*number < min || *number > max)
3529 static struct blame_commit *
3530 parse_blame_commit(struct view *view, char *text, int *blamed)
3532 struct blame_commit *commit;
3533 struct blame *blame;
3534 char *pos = text + SIZEOF_REV - 1;
3538 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3541 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3542 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3545 commit = get_blame_commit(view, text);
3551 struct line *line = &view->line[lineno + group - 1];
3554 blame->commit = commit;
3555 blame->header = !group;
3563 blame_read_file(struct view *view, char *line)
3568 if (view->lines > 0)
3569 pipe = popen(view->cmd, "r");
3570 else if (!view->parent)
3571 die("No blame exist for %s", view->vid);
3574 report("Failed to load blame data");
3583 size_t linelen = strlen(line);
3584 struct blame *blame = malloc(sizeof(*blame) + linelen);
3589 blame->commit = NULL;
3590 strncpy(blame->text, line, linelen);
3591 blame->text[linelen] = 0;
3592 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3597 match_blame_header(const char *name, char **line)
3599 size_t namelen = strlen(name);
3600 bool matched = !strncmp(name, *line, namelen);
3609 blame_read(struct view *view, char *line)
3611 static struct blame_commit *commit = NULL;
3612 static int blamed = 0;
3613 static time_t author_time;
3616 return blame_read_file(view, line);
3622 string_format(view->ref, "%s", view->vid);
3623 if (view_is_displayed(view)) {
3624 update_view_title(view);
3625 redraw_view_from(view, 0);
3631 commit = parse_blame_commit(view, line, &blamed);
3632 string_format(view->ref, "%s %2d%%", view->vid,
3633 blamed * 100 / view->lines);
3635 } else if (match_blame_header("author ", &line)) {
3636 string_ncopy(commit->author, line, strlen(line));
3638 } else if (match_blame_header("author-time ", &line)) {
3639 author_time = (time_t) atol(line);
3641 } else if (match_blame_header("author-tz ", &line)) {
3644 tz = ('0' - line[1]) * 60 * 60 * 10;
3645 tz += ('0' - line[2]) * 60 * 60;
3646 tz += ('0' - line[3]) * 60;
3647 tz += ('0' - line[4]) * 60;
3653 gmtime_r(&author_time, &commit->time);
3655 } else if (match_blame_header("summary ", &line)) {
3656 string_ncopy(commit->title, line, strlen(line));
3658 } else if (match_blame_header("filename ", &line)) {
3659 string_ncopy(commit->filename, line, strlen(line));
3667 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3669 struct blame *blame = line->data;
3670 struct tm *time = NULL;
3671 char *id = NULL, *author = NULL;
3673 if (blame->commit && *blame->commit->filename) {
3674 id = blame->commit->id;
3675 author = blame->commit->author;
3676 time = &blame->commit->time;
3679 if (opt_date && draw_date(view, time))
3683 draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
3686 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3689 if (draw_lineno(view, lineno))
3692 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3697 blame_request(struct view *view, enum request request, struct line *line)
3699 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3700 struct blame *blame = line->data;
3704 if (!blame->commit) {
3705 report("No commit loaded yet");
3709 if (!strcmp(blame->commit->id, NULL_ID)) {
3710 char path[SIZEOF_STR];
3712 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3714 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3717 open_view(view, REQ_VIEW_DIFF, flags);
3728 blame_grep(struct view *view, struct line *line)
3730 struct blame *blame = line->data;
3731 struct blame_commit *commit = blame->commit;
3734 #define MATCH(text, on) \
3735 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3738 char buf[DATE_COLS + 1];
3740 if (MATCH(commit->title, 1) ||
3741 MATCH(commit->author, opt_author) ||
3742 MATCH(commit->id, opt_date))
3745 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3750 return MATCH(blame->text, 1);
3756 blame_select(struct view *view, struct line *line)
3758 struct blame *blame = line->data;
3759 struct blame_commit *commit = blame->commit;
3764 if (!strcmp(commit->id, NULL_ID))
3765 string_ncopy(ref_commit, "HEAD", 4);
3767 string_copy_rev(ref_commit, commit->id);
3770 static struct view_ops blame_ops = {
3788 char rev[SIZEOF_REV];
3789 char name[SIZEOF_STR];
3793 char rev[SIZEOF_REV];
3794 char name[SIZEOF_STR];
3798 static char status_onbranch[SIZEOF_STR];
3799 static struct status stage_status;
3800 static enum line_type stage_line_type;
3801 static size_t stage_chunks;
3802 static int *stage_chunk;
3804 /* Get fields from the diff line:
3805 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3808 status_get_diff(struct status *file, char *buf, size_t bufsize)
3810 char *old_mode = buf + 1;
3811 char *new_mode = buf + 8;
3812 char *old_rev = buf + 15;
3813 char *new_rev = buf + 56;
3814 char *status = buf + 97;
3817 old_mode[-1] != ':' ||
3818 new_mode[-1] != ' ' ||
3819 old_rev[-1] != ' ' ||
3820 new_rev[-1] != ' ' ||
3824 file->status = *status;
3826 string_copy_rev(file->old.rev, old_rev);
3827 string_copy_rev(file->new.rev, new_rev);
3829 file->old.mode = strtoul(old_mode, NULL, 8);
3830 file->new.mode = strtoul(new_mode, NULL, 8);
3832 file->old.name[0] = file->new.name[0] = 0;
3838 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3840 struct status *file = NULL;
3841 struct status *unmerged = NULL;
3842 char buf[SIZEOF_STR * 4];
3846 pipe = popen(cmd, "r");
3850 add_line_data(view, NULL, type);
3852 while (!feof(pipe) && !ferror(pipe)) {
3856 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3859 bufsize += readsize;
3861 /* Process while we have NUL chars. */
3862 while ((sep = memchr(buf, 0, bufsize))) {
3863 size_t sepsize = sep - buf + 1;
3866 if (!realloc_lines(view, view->line_size + 1))
3869 file = calloc(1, sizeof(*file));
3873 add_line_data(view, file, type);
3876 /* Parse diff info part. */
3878 file->status = status;
3880 string_copy(file->old.rev, NULL_ID);
3882 } else if (!file->status) {
3883 if (!status_get_diff(file, buf, sepsize))
3887 memmove(buf, sep + 1, bufsize);
3889 sep = memchr(buf, 0, bufsize);
3892 sepsize = sep - buf + 1;
3894 /* Collapse all 'M'odified entries that
3895 * follow a associated 'U'nmerged entry.
3897 if (file->status == 'U') {
3900 } else if (unmerged) {
3901 int collapse = !strcmp(buf, unmerged->new.name);
3912 /* Grab the old name for rename/copy. */
3913 if (!*file->old.name &&
3914 (file->status == 'R' || file->status == 'C')) {
3915 sepsize = sep - buf + 1;
3916 string_ncopy(file->old.name, buf, sepsize);
3918 memmove(buf, sep + 1, bufsize);
3920 sep = memchr(buf, 0, bufsize);
3923 sepsize = sep - buf + 1;
3926 /* git-ls-files just delivers a NUL separated
3927 * list of file names similar to the second half
3928 * of the git-diff-* output. */
3929 string_ncopy(file->new.name, buf, sepsize);
3930 if (!*file->old.name)
3931 string_copy(file->old.name, file->new.name);
3933 memmove(buf, sep + 1, bufsize);
3944 if (!view->line[view->lines - 1].data)
3945 add_line_data(view, NULL, LINE_STAT_NONE);
3951 /* Don't show unmerged entries in the staged section. */
3952 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3953 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3954 #define STATUS_LIST_OTHER_CMD \
3955 "git ls-files -z --others --exclude-per-directory=.gitignore"
3956 #define STATUS_LIST_NO_HEAD_CMD \
3957 "git ls-files -z --cached --exclude-per-directory=.gitignore"
3959 #define STATUS_DIFF_INDEX_SHOW_CMD \
3960 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3962 #define STATUS_DIFF_FILES_SHOW_CMD \
3963 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3965 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3966 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3968 /* First parse staged info using git-diff-index(1), then parse unstaged
3969 * info using git-diff-files(1), and finally untracked files using
3970 * git-ls-files(1). */
3972 status_open(struct view *view)
3974 struct stat statbuf;
3975 char exclude[SIZEOF_STR];
3976 char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3977 char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3978 unsigned long prev_lineno = view->lineno;
3979 char indexstatus = 0;
3982 for (i = 0; i < view->lines; i++)
3983 free(view->line[i].data);
3985 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3988 if (!realloc_lines(view, view->line_size + 7))
3991 add_line_data(view, NULL, LINE_STAT_HEAD);
3993 string_copy(status_onbranch, "Initial commit");
3994 else if (!*opt_head)
3995 string_copy(status_onbranch, "Not currently on any branch");
3996 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4000 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
4004 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
4007 if (stat(exclude, &statbuf) >= 0) {
4008 size_t cmdsize = strlen(othercmd);
4010 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
4011 sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4014 cmdsize = strlen(indexcmd);
4016 (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4017 sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4021 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4023 if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4024 !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4025 !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4028 /* If all went well restore the previous line number to stay in
4029 * the context or select a line with something that can be
4031 if (prev_lineno >= view->lines)
4032 prev_lineno = view->lines - 1;
4033 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4035 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4038 /* If the above fails, always skip the "On branch" line. */
4039 if (prev_lineno < view->lines)
4040 view->lineno = prev_lineno;
4044 if (view->lineno < view->offset)
4045 view->offset = view->lineno;
4046 else if (view->offset + view->height <= view->lineno)
4047 view->offset = view->lineno - view->height + 1;
4053 status_draw(struct view *view, struct line *line, unsigned int lineno)
4055 struct status *status = line->data;
4056 enum line_type type;
4060 switch (line->type) {
4061 case LINE_STAT_STAGED:
4062 type = LINE_STAT_SECTION;
4063 text = "Changes to be committed:";
4066 case LINE_STAT_UNSTAGED:
4067 type = LINE_STAT_SECTION;
4068 text = "Changed but not updated:";
4071 case LINE_STAT_UNTRACKED:
4072 type = LINE_STAT_SECTION;
4073 text = "Untracked files:";
4076 case LINE_STAT_NONE:
4077 type = LINE_DEFAULT;
4078 text = " (no files)";
4081 case LINE_STAT_HEAD:
4082 type = LINE_STAT_HEAD;
4083 text = status_onbranch;
4090 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4092 buf[0] = status->status;
4093 if (draw_text(view, line->type, buf, TRUE))
4095 type = LINE_DEFAULT;
4096 text = status->new.name;
4099 draw_text(view, type, text, TRUE);
4104 status_enter(struct view *view, struct line *line)
4106 struct status *status = line->data;
4107 char oldpath[SIZEOF_STR] = "";
4108 char newpath[SIZEOF_STR] = "";
4111 enum open_flags split;
4113 if (line->type == LINE_STAT_NONE ||
4114 (!status && line[1].type == LINE_STAT_NONE)) {
4115 report("No file to diff");
4120 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4122 /* Diffs for unmerged entries are empty when pasing the
4123 * new path, so leave it empty. */
4124 if (status->status != 'U' &&
4125 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4130 line->type != LINE_STAT_UNTRACKED &&
4131 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4134 switch (line->type) {
4135 case LINE_STAT_STAGED:
4137 if (!string_format_from(opt_cmd, &cmdsize,
4138 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4142 if (!string_format_from(opt_cmd, &cmdsize,
4143 STATUS_DIFF_INDEX_SHOW_CMD,
4149 info = "Staged changes to %s";
4151 info = "Staged changes";
4154 case LINE_STAT_UNSTAGED:
4155 if (!string_format_from(opt_cmd, &cmdsize,
4156 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4159 info = "Unstaged changes to %s";
4161 info = "Unstaged changes";
4164 case LINE_STAT_UNTRACKED:
4169 report("No file to show");
4173 opt_pipe = fopen(status->new.name, "r");
4174 info = "Untracked file %s";
4177 case LINE_STAT_HEAD:
4181 die("line type %d not handled in switch", line->type);
4184 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4185 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4186 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4188 stage_status = *status;
4190 memset(&stage_status, 0, sizeof(stage_status));
4193 stage_line_type = line->type;
4195 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4202 status_exists(struct status *status, enum line_type type)
4204 struct view *view = VIEW(REQ_VIEW_STATUS);
4207 for (line = view->line; line < view->line + view->lines; line++) {
4208 struct status *pos = line->data;
4210 if (line->type == type && pos &&
4211 !strcmp(status->new.name, pos->new.name))
4220 status_update_prepare(enum line_type type)
4222 char cmd[SIZEOF_STR];
4226 type != LINE_STAT_UNTRACKED &&
4227 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4231 case LINE_STAT_STAGED:
4232 string_add(cmd, cmdsize, "git update-index -z --index-info");
4235 case LINE_STAT_UNSTAGED:
4236 case LINE_STAT_UNTRACKED:
4237 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4241 die("line type %d not handled in switch", type);
4244 return popen(cmd, "w");
4248 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4250 char buf[SIZEOF_STR];
4255 case LINE_STAT_STAGED:
4256 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4259 status->old.name, 0))
4263 case LINE_STAT_UNSTAGED:
4264 case LINE_STAT_UNTRACKED:
4265 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4270 die("line type %d not handled in switch", type);
4273 while (!ferror(pipe) && written < bufsize) {
4274 written += fwrite(buf + written, 1, bufsize - written, pipe);
4277 return written == bufsize;
4281 status_update_file(struct status *status, enum line_type type)
4283 FILE *pipe = status_update_prepare(type);
4289 result = status_update_write(pipe, status, type);
4295 status_update_files(struct view *view, struct line *line)
4297 FILE *pipe = status_update_prepare(line->type);
4299 struct line *pos = view->line + view->lines;
4306 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4309 for (file = 0, done = 0; result && file < files; line++, file++) {
4310 int almost_done = file * 100 / files;
4312 if (almost_done > done) {
4314 string_format(view->ref, "updating file %u of %u (%d%% done)",
4316 update_view_title(view);
4318 result = status_update_write(pipe, line->data, line->type);
4326 status_update(struct view *view)
4328 struct line *line = &view->line[view->lineno];
4330 assert(view->lines);
4333 /* This should work even for the "On branch" line. */
4334 if (line < view->line + view->lines && !line[1].data) {
4335 report("Nothing to update");
4339 if (!status_update_files(view, line + 1)) {
4340 report("Failed to update file status");
4344 } else if (!status_update_file(line->data, line->type)) {
4345 report("Failed to update file status");
4353 status_request(struct view *view, enum request request, struct line *line)
4355 struct status *status = line->data;
4358 case REQ_STATUS_UPDATE:
4359 if (!status_update(view))
4363 case REQ_STATUS_MERGE:
4364 if (!status || status->status != 'U') {
4365 report("Merging only possible for files with unmerged status ('U').");
4368 open_mergetool(status->new.name);
4375 open_editor(status->status != '?', status->new.name);
4378 case REQ_VIEW_BLAME:
4380 string_copy(opt_file, status->new.name);
4386 /* After returning the status view has been split to
4387 * show the stage view. No further reloading is
4389 status_enter(view, line);
4393 /* Simply reload the view. */
4400 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4406 status_select(struct view *view, struct line *line)
4408 struct status *status = line->data;
4409 char file[SIZEOF_STR] = "all files";
4413 if (status && !string_format(file, "'%s'", status->new.name))
4416 if (!status && line[1].type == LINE_STAT_NONE)
4419 switch (line->type) {
4420 case LINE_STAT_STAGED:
4421 text = "Press %s to unstage %s for commit";
4424 case LINE_STAT_UNSTAGED:
4425 text = "Press %s to stage %s for commit";
4428 case LINE_STAT_UNTRACKED:
4429 text = "Press %s to stage %s for addition";
4432 case LINE_STAT_HEAD:
4433 case LINE_STAT_NONE:
4434 text = "Nothing to update";
4438 die("line type %d not handled in switch", line->type);
4441 if (status && status->status == 'U') {
4442 text = "Press %s to resolve conflict in %s";
4443 key = get_key(REQ_STATUS_MERGE);
4446 key = get_key(REQ_STATUS_UPDATE);
4449 string_format(view->ref, text, key, file);
4453 status_grep(struct view *view, struct line *line)
4455 struct status *status = line->data;
4456 enum { S_STATUS, S_NAME, S_END } state;
4463 for (state = S_STATUS; state < S_END; state++) {
4467 case S_NAME: text = status->new.name; break;
4469 buf[0] = status->status;
4477 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4484 static struct view_ops status_ops = {
4496 stage_diff_line(FILE *pipe, struct line *line)
4498 char *buf = line->data;
4499 size_t bufsize = strlen(buf);
4502 while (!ferror(pipe) && written < bufsize) {
4503 written += fwrite(buf + written, 1, bufsize - written, pipe);
4508 return written == bufsize;
4512 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4514 while (line < end) {
4515 if (!stage_diff_line(pipe, line++))
4517 if (line->type == LINE_DIFF_CHUNK ||
4518 line->type == LINE_DIFF_HEADER)
4525 static struct line *
4526 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4528 for (; view->line < line; line--)
4529 if (line->type == type)
4536 stage_update_chunk(struct view *view, struct line *chunk)
4538 char cmd[SIZEOF_STR];
4540 struct line *diff_hdr;
4543 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4548 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4551 if (!string_format_from(cmd, &cmdsize,
4552 "git apply --whitespace=nowarn --cached %s - && "
4553 "git update-index -q --unmerged --refresh 2>/dev/null",
4554 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4557 pipe = popen(cmd, "w");
4561 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4562 !stage_diff_write(pipe, chunk, view->line + view->lines))
4567 return chunk ? TRUE : FALSE;
4571 stage_update(struct view *view, struct line *line)
4573 struct line *chunk = NULL;
4575 if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4576 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4579 if (!stage_update_chunk(view, chunk)) {
4580 report("Failed to apply chunk");
4584 } else if (!stage_status.status) {
4585 view = VIEW(REQ_VIEW_STATUS);
4587 for (line = view->line; line < view->line + view->lines; line++)
4588 if (line->type == stage_line_type)
4591 if (!status_update_files(view, line + 1)) {
4592 report("Failed to update files");
4596 } else if (!status_update_file(&stage_status, stage_line_type)) {
4597 report("Failed to update file");
4605 stage_next(struct view *view, struct line *line)
4609 if (!stage_chunks) {
4610 static size_t alloc = 0;
4613 for (line = view->line; line < view->line + view->lines; line++) {
4614 if (line->type != LINE_DIFF_CHUNK)
4617 tmp = realloc_items(stage_chunk, &alloc,
4618 stage_chunks, sizeof(*tmp));
4620 report("Allocation failure");
4625 stage_chunk[stage_chunks++] = line - view->line;
4629 for (i = 0; i < stage_chunks; i++) {
4630 if (stage_chunk[i] > view->lineno) {
4631 do_scroll_view(view, stage_chunk[i] - view->lineno);
4632 report("Chunk %d of %d", i + 1, stage_chunks);
4637 report("No next chunk found");
4641 stage_request(struct view *view, enum request request, struct line *line)
4644 case REQ_STATUS_UPDATE:
4645 if (!stage_update(view, line))
4649 case REQ_STAGE_NEXT:
4650 if (stage_line_type == LINE_STAT_UNTRACKED) {
4651 report("File is untracked; press %s to add",
4652 get_key(REQ_STATUS_UPDATE));
4655 stage_next(view, line);
4659 if (!stage_status.new.name[0])
4662 open_editor(stage_status.status != '?', stage_status.new.name);
4666 /* Reload everything ... */
4669 case REQ_VIEW_BLAME:
4670 if (stage_status.new.name[0]) {
4671 string_copy(opt_file, stage_status.new.name);
4677 return pager_request(view, request, line);
4683 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4685 /* Check whether the staged entry still exists, and close the
4686 * stage view if it doesn't. */
4687 if (!status_exists(&stage_status, stage_line_type))
4688 return REQ_VIEW_CLOSE;
4690 if (stage_line_type == LINE_STAT_UNTRACKED)
4691 opt_pipe = fopen(stage_status.new.name, "r");
4693 string_copy(opt_cmd, view->cmd);
4694 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4699 static struct view_ops stage_ops = {
4715 char id[SIZEOF_REV]; /* SHA1 ID. */
4716 char title[128]; /* First line of the commit message. */
4717 char author[75]; /* Author of the commit. */
4718 struct tm time; /* Date from the author ident. */
4719 struct ref **refs; /* Repository references. */
4720 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4721 size_t graph_size; /* The width of the graph array. */
4722 bool has_parents; /* Rewritten --parents seen. */
4725 /* Size of rev graph with no "padding" columns */
4726 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4729 struct rev_graph *prev, *next, *parents;
4730 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4732 struct commit *commit;
4734 unsigned int boundary:1;
4737 /* Parents of the commit being visualized. */
4738 static struct rev_graph graph_parents[4];
4740 /* The current stack of revisions on the graph. */
4741 static struct rev_graph graph_stacks[4] = {
4742 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4743 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4744 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4745 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4749 graph_parent_is_merge(struct rev_graph *graph)
4751 return graph->parents->size > 1;
4755 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4757 struct commit *commit = graph->commit;
4759 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4760 commit->graph[commit->graph_size++] = symbol;
4764 done_rev_graph(struct rev_graph *graph)
4766 if (graph_parent_is_merge(graph) &&
4767 graph->pos < graph->size - 1 &&
4768 graph->next->size == graph->size + graph->parents->size - 1) {
4769 size_t i = graph->pos + graph->parents->size - 1;
4771 graph->commit->graph_size = i * 2;
4772 while (i < graph->next->size - 1) {
4773 append_to_rev_graph(graph, ' ');
4774 append_to_rev_graph(graph, '\\');
4779 graph->size = graph->pos = 0;
4780 graph->commit = NULL;
4781 memset(graph->parents, 0, sizeof(*graph->parents));
4785 push_rev_graph(struct rev_graph *graph, char *parent)
4789 /* "Collapse" duplicate parents lines.
4791 * FIXME: This needs to also update update the drawn graph but
4792 * for now it just serves as a method for pruning graph lines. */
4793 for (i = 0; i < graph->size; i++)
4794 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4797 if (graph->size < SIZEOF_REVITEMS) {
4798 string_copy_rev(graph->rev[graph->size++], parent);
4803 get_rev_graph_symbol(struct rev_graph *graph)
4807 if (graph->boundary)
4808 symbol = REVGRAPH_BOUND;
4809 else if (graph->parents->size == 0)
4810 symbol = REVGRAPH_INIT;
4811 else if (graph_parent_is_merge(graph))
4812 symbol = REVGRAPH_MERGE;
4813 else if (graph->pos >= graph->size)
4814 symbol = REVGRAPH_BRANCH;
4816 symbol = REVGRAPH_COMMIT;
4822 draw_rev_graph(struct rev_graph *graph)
4825 chtype separator, line;
4827 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4828 static struct rev_filler fillers[] = {
4834 chtype symbol = get_rev_graph_symbol(graph);
4835 struct rev_filler *filler;
4838 if (opt_line_graphics)
4839 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4841 filler = &fillers[DEFAULT];
4843 for (i = 0; i < graph->pos; i++) {
4844 append_to_rev_graph(graph, filler->line);
4845 if (graph_parent_is_merge(graph->prev) &&
4846 graph->prev->pos == i)
4847 filler = &fillers[RSHARP];
4849 append_to_rev_graph(graph, filler->separator);
4852 /* Place the symbol for this revision. */
4853 append_to_rev_graph(graph, symbol);
4855 if (graph->prev->size > graph->size)
4856 filler = &fillers[RDIAG];
4858 filler = &fillers[DEFAULT];
4862 for (; i < graph->size; i++) {
4863 append_to_rev_graph(graph, filler->separator);
4864 append_to_rev_graph(graph, filler->line);
4865 if (graph_parent_is_merge(graph->prev) &&
4866 i < graph->prev->pos + graph->parents->size)
4867 filler = &fillers[RSHARP];
4868 if (graph->prev->size > graph->size)
4869 filler = &fillers[LDIAG];
4872 if (graph->prev->size > graph->size) {
4873 append_to_rev_graph(graph, filler->separator);
4874 if (filler->line != ' ')
4875 append_to_rev_graph(graph, filler->line);
4879 /* Prepare the next rev graph */
4881 prepare_rev_graph(struct rev_graph *graph)
4885 /* First, traverse all lines of revisions up to the active one. */
4886 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4887 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4890 push_rev_graph(graph->next, graph->rev[graph->pos]);
4893 /* Interleave the new revision parent(s). */
4894 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4895 push_rev_graph(graph->next, graph->parents->rev[i]);
4897 /* Lastly, put any remaining revisions. */
4898 for (i = graph->pos + 1; i < graph->size; i++)
4899 push_rev_graph(graph->next, graph->rev[i]);
4903 update_rev_graph(struct rev_graph *graph)
4905 /* If this is the finalizing update ... */
4907 prepare_rev_graph(graph);
4909 /* Graph visualization needs a one rev look-ahead,
4910 * so the first update doesn't visualize anything. */
4911 if (!graph->prev->commit)
4914 draw_rev_graph(graph->prev);
4915 done_rev_graph(graph->prev->prev);
4924 main_draw(struct view *view, struct line *line, unsigned int lineno)
4926 struct commit *commit = line->data;
4928 if (!*commit->author)
4931 if (opt_date && draw_date(view, &commit->time))
4935 draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
4938 if (opt_rev_graph && commit->graph_size &&
4939 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4942 if (opt_show_refs && commit->refs) {
4946 enum line_type type;
4948 if (commit->refs[i]->head)
4949 type = LINE_MAIN_HEAD;
4950 else if (commit->refs[i]->ltag)
4951 type = LINE_MAIN_LOCAL_TAG;
4952 else if (commit->refs[i]->tag)
4953 type = LINE_MAIN_TAG;
4954 else if (commit->refs[i]->tracked)
4955 type = LINE_MAIN_TRACKED;
4956 else if (commit->refs[i]->remote)
4957 type = LINE_MAIN_REMOTE;
4959 type = LINE_MAIN_REF;
4961 if (draw_text(view, type, "[", TRUE) ||
4962 draw_text(view, type, commit->refs[i]->name, TRUE) ||
4963 draw_text(view, type, "]", TRUE))
4966 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4968 } while (commit->refs[i++]->next);
4971 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4975 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4977 main_read(struct view *view, char *line)
4979 static struct rev_graph *graph = graph_stacks;
4980 enum line_type type;
4981 struct commit *commit;
4984 if (!view->lines && !view->parent)
4985 die("No revisions match the given arguments.");
4986 update_rev_graph(graph);
4990 type = get_line_type(line);
4991 if (type == LINE_COMMIT) {
4992 commit = calloc(1, sizeof(struct commit));
4996 line += STRING_SIZE("commit ");
4998 graph->boundary = 1;
5002 string_copy_rev(commit->id, line);
5003 commit->refs = get_refs(commit->id);
5004 graph->commit = commit;
5005 add_line_data(view, commit, LINE_MAIN_COMMIT);
5007 while ((line = strchr(line, ' '))) {
5009 push_rev_graph(graph->parents, line);
5010 commit->has_parents = TRUE;
5017 commit = view->line[view->lines - 1].data;
5021 if (commit->has_parents)
5023 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5028 /* Parse author lines where the name may be empty:
5029 * author <email@address.tld> 1138474660 +0100
5031 char *ident = line + STRING_SIZE("author ");
5032 char *nameend = strchr(ident, '<');
5033 char *emailend = strchr(ident, '>');
5035 if (!nameend || !emailend)
5038 update_rev_graph(graph);
5039 graph = graph->next;
5041 *nameend = *emailend = 0;
5042 ident = chomp_string(ident);
5044 ident = chomp_string(nameend + 1);
5049 string_ncopy(commit->author, ident, strlen(ident));
5051 /* Parse epoch and timezone */
5052 if (emailend[1] == ' ') {
5053 char *secs = emailend + 2;
5054 char *zone = strchr(secs, ' ');
5055 time_t time = (time_t) atol(secs);
5057 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5061 tz = ('0' - zone[1]) * 60 * 60 * 10;
5062 tz += ('0' - zone[2]) * 60 * 60;
5063 tz += ('0' - zone[3]) * 60;
5064 tz += ('0' - zone[4]) * 60;
5072 gmtime_r(&time, &commit->time);
5077 /* Fill in the commit title if it has not already been set. */
5078 if (commit->title[0])
5081 /* Require titles to start with a non-space character at the
5082 * offset used by git log. */
5083 if (strncmp(line, " ", 4))
5086 /* Well, if the title starts with a whitespace character,
5087 * try to be forgiving. Otherwise we end up with no title. */
5088 while (isspace(*line))
5092 /* FIXME: More graceful handling of titles; append "..." to
5093 * shortened titles, etc. */
5095 string_ncopy(commit->title, line, strlen(line));
5102 main_request(struct view *view, enum request request, struct line *line)
5104 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5106 if (request == REQ_ENTER)
5107 open_view(view, REQ_VIEW_DIFF, flags);
5115 grep_refs(struct ref **refs, regex_t *regex)
5123 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5125 } while (refs[i++]->next);
5131 main_grep(struct view *view, struct line *line)
5133 struct commit *commit = line->data;
5134 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5135 char buf[DATE_COLS + 1];
5138 for (state = S_TITLE; state < S_END; state++) {
5142 case S_TITLE: text = commit->title; break;
5146 text = commit->author;
5151 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5158 if (grep_refs(commit->refs, view->regex) == TRUE)
5165 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5173 main_select(struct view *view, struct line *line)
5175 struct commit *commit = line->data;
5177 string_copy_rev(view->ref, commit->id);
5178 string_copy_rev(ref_commit, view->ref);
5181 static struct view_ops main_ops = {
5193 * Unicode / UTF-8 handling
5195 * NOTE: Much of the following code for dealing with unicode is derived from
5196 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5197 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5200 /* I've (over)annotated a lot of code snippets because I am not entirely
5201 * confident that the approach taken by this small UTF-8 interface is correct.
5205 unicode_width(unsigned long c)
5208 (c <= 0x115f /* Hangul Jamo */
5211 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5213 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5214 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5215 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5216 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5217 || (c >= 0xffe0 && c <= 0xffe6)
5218 || (c >= 0x20000 && c <= 0x2fffd)
5219 || (c >= 0x30000 && c <= 0x3fffd)))
5223 return opt_tab_size;
5228 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5229 * Illegal bytes are set one. */
5230 static const unsigned char utf8_bytes[256] = {
5231 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,
5232 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,
5233 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,
5234 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,
5235 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,
5236 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,
5237 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,
5238 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,
5241 /* Decode UTF-8 multi-byte representation into a unicode character. */
5242 static inline unsigned long
5243 utf8_to_unicode(const char *string, size_t length)
5245 unsigned long unicode;
5249 unicode = string[0];
5252 unicode = (string[0] & 0x1f) << 6;
5253 unicode += (string[1] & 0x3f);
5256 unicode = (string[0] & 0x0f) << 12;
5257 unicode += ((string[1] & 0x3f) << 6);
5258 unicode += (string[2] & 0x3f);
5261 unicode = (string[0] & 0x0f) << 18;
5262 unicode += ((string[1] & 0x3f) << 12);
5263 unicode += ((string[2] & 0x3f) << 6);
5264 unicode += (string[3] & 0x3f);
5267 unicode = (string[0] & 0x0f) << 24;
5268 unicode += ((string[1] & 0x3f) << 18);
5269 unicode += ((string[2] & 0x3f) << 12);
5270 unicode += ((string[3] & 0x3f) << 6);
5271 unicode += (string[4] & 0x3f);
5274 unicode = (string[0] & 0x01) << 30;
5275 unicode += ((string[1] & 0x3f) << 24);
5276 unicode += ((string[2] & 0x3f) << 18);
5277 unicode += ((string[3] & 0x3f) << 12);
5278 unicode += ((string[4] & 0x3f) << 6);
5279 unicode += (string[5] & 0x3f);
5282 die("Invalid unicode length");
5285 /* Invalid characters could return the special 0xfffd value but NUL
5286 * should be just as good. */
5287 return unicode > 0xffff ? 0 : unicode;
5290 /* Calculates how much of string can be shown within the given maximum width
5291 * and sets trimmed parameter to non-zero value if all of string could not be
5292 * shown. If the reserve flag is TRUE, it will reserve at least one
5293 * trailing character, which can be useful when drawing a delimiter.
5295 * Returns the number of bytes to output from string to satisfy max_width. */
5297 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5299 const char *start = string;
5300 const char *end = strchr(string, '\0');
5301 unsigned char last_bytes = 0;
5302 size_t last_ucwidth = 0;
5307 while (string < end) {
5308 int c = *(unsigned char *) string;
5309 unsigned char bytes = utf8_bytes[c];
5311 unsigned long unicode;
5313 if (string + bytes > end)
5316 /* Change representation to figure out whether
5317 * it is a single- or double-width character. */
5319 unicode = utf8_to_unicode(string, bytes);
5320 /* FIXME: Graceful handling of invalid unicode character. */
5324 ucwidth = unicode_width(unicode);
5326 if (*width > max_width) {
5329 if (reserve && *width == max_width) {
5330 string -= last_bytes;
5331 *width -= last_ucwidth;
5338 last_ucwidth = ucwidth;
5341 return string - start;
5349 /* Whether or not the curses interface has been initialized. */
5350 static bool cursed = FALSE;
5352 /* The status window is used for polling keystrokes. */
5353 static WINDOW *status_win;
5355 static bool status_empty = TRUE;
5357 /* Update status and title window. */
5359 report(const char *msg, ...)
5361 struct view *view = display[current_view];
5367 char buf[SIZEOF_STR];
5370 va_start(args, msg);
5371 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5372 buf[sizeof(buf) - 1] = 0;
5373 buf[sizeof(buf) - 2] = '.';
5374 buf[sizeof(buf) - 3] = '.';
5375 buf[sizeof(buf) - 4] = '.';
5381 if (!status_empty || *msg) {
5384 va_start(args, msg);
5386 wmove(status_win, 0, 0);
5388 vwprintw(status_win, msg, args);
5389 status_empty = FALSE;
5391 status_empty = TRUE;
5393 wclrtoeol(status_win);
5394 wrefresh(status_win);
5399 update_view_title(view);
5400 update_display_cursor(view);
5403 /* Controls when nodelay should be in effect when polling user input. */
5405 set_nonblocking_input(bool loading)
5407 static unsigned int loading_views;
5409 if ((loading == FALSE && loading_views-- == 1) ||
5410 (loading == TRUE && loading_views++ == 0))
5411 nodelay(status_win, loading);
5419 /* Initialize the curses library */
5420 if (isatty(STDIN_FILENO)) {
5421 cursed = !!initscr();
5423 /* Leave stdin and stdout alone when acting as a pager. */
5424 FILE *io = fopen("/dev/tty", "r+");
5427 die("Failed to open /dev/tty");
5428 cursed = !!newterm(NULL, io, io);
5432 die("Failed to initialize curses");
5434 nonl(); /* Tell curses not to do NL->CR/NL on output */
5435 cbreak(); /* Take input chars one at a time, no wait for \n */
5436 noecho(); /* Don't echo input */
5437 leaveok(stdscr, TRUE);
5442 getmaxyx(stdscr, y, x);
5443 status_win = newwin(1, 0, y - 1, 0);
5445 die("Failed to create status window");
5447 /* Enable keyboard mapping */
5448 keypad(status_win, TRUE);
5449 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5451 TABSIZE = opt_tab_size;
5452 if (opt_line_graphics) {
5453 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5458 read_prompt(const char *prompt)
5460 enum { READING, STOP, CANCEL } status = READING;
5461 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5464 while (status == READING) {
5470 foreach_view (view, i)
5475 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5476 wclrtoeol(status_win);
5478 /* Refresh, accept single keystroke of input */
5479 key = wgetch(status_win);
5484 status = pos ? STOP : CANCEL;
5502 if (pos >= sizeof(buf)) {
5503 report("Input string too long");
5508 buf[pos++] = (char) key;
5512 /* Clear the status window */
5513 status_empty = FALSE;
5516 if (status == CANCEL)
5525 * Repository references
5528 static struct ref *refs = NULL;
5529 static size_t refs_alloc = 0;
5530 static size_t refs_size = 0;
5532 /* Id <-> ref store */
5533 static struct ref ***id_refs = NULL;
5534 static size_t id_refs_alloc = 0;
5535 static size_t id_refs_size = 0;
5537 static struct ref **
5540 struct ref ***tmp_id_refs;
5541 struct ref **ref_list = NULL;
5542 size_t ref_list_alloc = 0;
5543 size_t ref_list_size = 0;
5546 for (i = 0; i < id_refs_size; i++)
5547 if (!strcmp(id, id_refs[i][0]->id))
5550 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5555 id_refs = tmp_id_refs;
5557 for (i = 0; i < refs_size; i++) {
5560 if (strcmp(id, refs[i].id))
5563 tmp = realloc_items(ref_list, &ref_list_alloc,
5564 ref_list_size + 1, sizeof(*ref_list));
5572 if (ref_list_size > 0)
5573 ref_list[ref_list_size - 1]->next = 1;
5574 ref_list[ref_list_size] = &refs[i];
5576 /* XXX: The properties of the commit chains ensures that we can
5577 * safely modify the shared ref. The repo references will
5578 * always be similar for the same id. */
5579 ref_list[ref_list_size]->next = 0;
5584 id_refs[id_refs_size++] = ref_list;
5590 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5595 bool remote = FALSE;
5596 bool tracked = FALSE;
5597 bool check_replace = FALSE;
5600 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5601 if (!strcmp(name + namelen - 3, "^{}")) {
5604 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5605 check_replace = TRUE;
5611 namelen -= STRING_SIZE("refs/tags/");
5612 name += STRING_SIZE("refs/tags/");
5614 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5616 namelen -= STRING_SIZE("refs/remotes/");
5617 name += STRING_SIZE("refs/remotes/");
5618 tracked = !strcmp(opt_remote, name);
5620 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5621 namelen -= STRING_SIZE("refs/heads/");
5622 name += STRING_SIZE("refs/heads/");
5623 head = !strncmp(opt_head, name, namelen);
5625 } else if (!strcmp(name, "HEAD")) {
5626 opt_no_head = FALSE;
5630 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5631 /* it's an annotated tag, replace the previous sha1 with the
5632 * resolved commit id; relies on the fact git-ls-remote lists
5633 * the commit id of an annotated tag right beofre the commit id
5635 refs[refs_size - 1].ltag = ltag;
5636 string_copy_rev(refs[refs_size - 1].id, id);
5640 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5644 ref = &refs[refs_size++];
5645 ref->name = malloc(namelen + 1);
5649 strncpy(ref->name, name, namelen);
5650 ref->name[namelen] = 0;
5654 ref->remote = remote;
5655 ref->tracked = tracked;
5656 string_copy_rev(ref->id, id);
5664 const char *cmd_env = getenv("TIG_LS_REMOTE");
5665 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5667 return read_properties(popen(cmd, "r"), "\t", read_ref);
5671 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5673 if (!strcmp(name, "i18n.commitencoding"))
5674 string_ncopy(opt_encoding, value, valuelen);
5676 if (!strcmp(name, "core.editor"))
5677 string_ncopy(opt_editor, value, valuelen);
5679 /* branch.<head>.remote */
5681 !strncmp(name, "branch.", 7) &&
5682 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5683 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5684 string_ncopy(opt_remote, value, valuelen);
5686 if (*opt_head && *opt_remote &&
5687 !strncmp(name, "branch.", 7) &&
5688 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5689 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5690 size_t from = strlen(opt_remote);
5692 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5693 value += STRING_SIZE("refs/heads/");
5694 valuelen -= STRING_SIZE("refs/heads/");
5697 if (!string_format_from(opt_remote, &from, "/%s", value))
5705 load_git_config(void)
5707 return read_properties(popen(GIT_CONFIG " --list", "r"),
5708 "=", read_repo_config_option);
5712 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5714 if (!opt_git_dir[0]) {
5715 string_ncopy(opt_git_dir, name, namelen);
5717 } else if (opt_is_inside_work_tree == -1) {
5718 /* This can be 3 different values depending on the
5719 * version of git being used. If git-rev-parse does not
5720 * understand --is-inside-work-tree it will simply echo
5721 * the option else either "true" or "false" is printed.
5722 * Default to true for the unknown case. */
5723 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5725 } else if (opt_cdup[0] == ' ') {
5726 string_ncopy(opt_cdup, name, namelen);
5728 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5729 namelen -= STRING_SIZE("refs/heads/");
5730 name += STRING_SIZE("refs/heads/");
5731 string_ncopy(opt_head, name, namelen);
5739 load_repo_info(void)
5742 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5743 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5745 /* XXX: The line outputted by "--show-cdup" can be empty so
5746 * initialize it to something invalid to make it possible to
5747 * detect whether it has been set or not. */
5750 result = read_properties(pipe, "=", read_repo_info);
5751 if (opt_cdup[0] == ' ')
5758 read_properties(FILE *pipe, const char *separators,
5759 int (*read_property)(char *, size_t, char *, size_t))
5761 char buffer[BUFSIZ];
5768 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5773 name = chomp_string(name);
5774 namelen = strcspn(name, separators);
5776 if (name[namelen]) {
5778 value = chomp_string(name + namelen + 1);
5779 valuelen = strlen(value);
5786 state = read_property(name, namelen, value, valuelen);
5789 if (state != ERR && ferror(pipe))
5802 static void __NORETURN
5805 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5811 static void __NORETURN
5812 die(const char *err, ...)
5818 va_start(args, err);
5819 fputs("tig: ", stderr);
5820 vfprintf(stderr, err, args);
5821 fputs("\n", stderr);
5828 warn(const char *msg, ...)
5832 va_start(args, msg);
5833 fputs("tig warning: ", stderr);
5834 vfprintf(stderr, msg, args);
5835 fputs("\n", stderr);
5840 main(int argc, char *argv[])
5843 enum request request;
5846 signal(SIGINT, quit);
5848 if (setlocale(LC_ALL, "")) {
5849 char *codeset = nl_langinfo(CODESET);
5851 string_ncopy(opt_codeset, codeset, strlen(codeset));
5854 if (load_repo_info() == ERR)
5855 die("Failed to load repo info.");
5857 if (load_options() == ERR)
5858 die("Failed to load user config.");
5860 if (load_git_config() == ERR)
5861 die("Failed to load repo config.");
5863 if (!parse_options(argc, argv))
5866 /* Require a git repository unless when running in pager mode. */
5867 if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5868 die("Not a git repository");
5870 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5873 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5874 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5875 if (opt_iconv == ICONV_NONE)
5876 die("Failed to initialize character set conversion");
5879 if (*opt_git_dir && load_refs() == ERR)
5880 die("Failed to load refs.");
5882 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5883 view->cmd_env = getenv(view->cmd_env);
5885 request = opt_request;
5889 while (view_driver(display[current_view], request)) {
5893 foreach_view (view, i)
5896 /* Refresh, accept single keystroke of input */
5897 key = wgetch(status_win);
5899 /* wgetch() with nodelay() enabled returns ERR when there's no
5906 request = get_keybinding(display[current_view]->keymap, key);
5908 /* Some low-level request handling. This keeps access to
5909 * status_win restricted. */
5913 char *cmd = read_prompt(":");
5915 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5916 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5917 opt_request = REQ_VIEW_DIFF;
5919 opt_request = REQ_VIEW_PAGER;
5928 case REQ_SEARCH_BACK:
5930 const char *prompt = request == REQ_SEARCH
5932 char *search = read_prompt(prompt);
5935 string_ncopy(opt_search, search, strlen(search));
5940 case REQ_SCREEN_RESIZE:
5944 getmaxyx(stdscr, height, width);
5946 /* Resize the status view and let the view driver take
5947 * care of resizing the displayed views. */
5948 wresize(status_win, 1, width);
5949 mvwin(status_win, height - 1, 0);
5950 wrefresh(status_win);