1 /* Copyright (c) 2006 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.
15 #define VERSION "unknown-version"
33 #include <sys/types.h>
43 #define __NORETURN __attribute__((__noreturn__))
48 static void __NORETURN die(const char *err, ...);
49 static void report(const char *msg, ...);
50 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading);
52 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
54 #define ABS(x) ((x) >= 0 ? (x) : -(x))
55 #define MIN(x, y) ((x) < (y) ? (x) : (y))
57 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x) (sizeof(x) - 1)
60 #define SIZEOF_STR 1024 /* Default string size. */
61 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
66 #define REVGRAPH_INIT 'I'
67 #define REVGRAPH_MERGE 'M'
68 #define REVGRAPH_BRANCH '+'
69 #define REVGRAPH_COMMIT '*'
70 #define REVGRAPH_LINE '|'
72 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
74 /* This color name can be used to refer to the default term colors. */
75 #define COLOR_DEFAULT (-1)
77 #define ICONV_NONE ((iconv_t) -1)
79 /* The format and size of the date column in the main view. */
80 #define DATE_FORMAT "%Y-%m-%d %H:%M"
81 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
83 #define AUTHOR_COLS 20
85 /* The default interval between line numbers. */
86 #define NUMBER_INTERVAL 1
90 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
92 #define TIG_LS_REMOTE \
93 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
95 #define TIG_DIFF_CMD \
96 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
99 "git log --cc --stat -n100 %s 2>/dev/null"
101 #define TIG_MAIN_CMD \
102 "git log --topo-order --pretty=raw %s 2>/dev/null"
104 #define TIG_TREE_CMD \
107 #define TIG_BLOB_CMD \
108 "git cat-file blob %s"
110 /* XXX: Needs to be defined to the empty string. */
111 #define TIG_HELP_CMD ""
112 #define TIG_PAGER_CMD ""
113 #define TIG_STATUS_CMD ""
115 /* Some ascii-shorthands fitted into the ncurses namespace. */
117 #define KEY_RETURN '\r'
122 char *name; /* Ref name; tag or head names are shortened. */
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int tag:1; /* Is it a tag? */
125 unsigned int remote:1; /* Is it a remote ref? */
126 unsigned int next:1; /* For ref lists: are there more refs? */
129 static struct ref **get_refs(char *id);
138 set_from_int_map(struct int_map *map, size_t map_size,
139 int *value, const char *name, int namelen)
144 for (i = 0; i < map_size; i++)
145 if (namelen == map[i].namelen &&
146 !strncasecmp(name, map[i].name, namelen)) {
147 *value = map[i].value;
160 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
162 if (srclen > dstlen - 1)
165 strncpy(dst, src, srclen);
169 /* Shorthands for safely copying into a fixed buffer. */
171 #define string_copy(dst, src) \
172 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
174 #define string_ncopy(dst, src, srclen) \
175 string_ncopy_do(dst, sizeof(dst), src, srclen)
177 #define string_copy_rev(dst, src) \
178 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
180 #define string_add(dst, from, src) \
181 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
184 chomp_string(char *name)
188 while (isspace(*name))
191 namelen = strlen(name) - 1;
192 while (namelen > 0 && isspace(name[namelen]))
199 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
202 size_t pos = bufpos ? *bufpos : 0;
205 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
211 return pos >= bufsize ? FALSE : TRUE;
214 #define string_format(buf, fmt, args...) \
215 string_nformat(buf, sizeof(buf), NULL, fmt, args)
217 #define string_format_from(buf, from, fmt, args...) \
218 string_nformat(buf, sizeof(buf), from, fmt, args)
221 string_enum_compare(const char *str1, const char *str2, int len)
225 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
227 /* Diff-Header == DIFF_HEADER */
228 for (i = 0; i < len; i++) {
229 if (toupper(str1[i]) == toupper(str2[i]))
232 if (string_enum_sep(str1[i]) &&
233 string_enum_sep(str2[i]))
236 return str1[i] - str2[i];
244 * NOTE: The following is a slightly modified copy of the git project's shell
245 * quoting routines found in the quote.c file.
247 * Help to copy the thing properly quoted for the shell safety. any single
248 * quote is replaced with '\'', any exclamation point is replaced with '\!',
249 * and the whole thing is enclosed in a
252 * original sq_quote result
253 * name ==> name ==> 'name'
254 * a b ==> a b ==> 'a b'
255 * a'b ==> a'\''b ==> 'a'\''b'
256 * a!b ==> a'\!'b ==> 'a'\!'b'
260 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
264 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
267 while ((c = *src++)) {
268 if (c == '\'' || c == '!') {
279 if (bufsize < SIZEOF_STR)
291 /* XXX: Keep the view request first and in sync with views[]. */ \
292 REQ_GROUP("View switching") \
293 REQ_(VIEW_MAIN, "Show main view"), \
294 REQ_(VIEW_DIFF, "Show diff view"), \
295 REQ_(VIEW_LOG, "Show log view"), \
296 REQ_(VIEW_TREE, "Show tree view"), \
297 REQ_(VIEW_BLOB, "Show blob view"), \
298 REQ_(VIEW_HELP, "Show help page"), \
299 REQ_(VIEW_PAGER, "Show pager view"), \
300 REQ_(VIEW_STATUS, "Show status view"), \
302 REQ_GROUP("View manipulation") \
303 REQ_(ENTER, "Enter current line and scroll"), \
304 REQ_(NEXT, "Move to next"), \
305 REQ_(PREVIOUS, "Move to previous"), \
306 REQ_(VIEW_NEXT, "Move focus to next view"), \
307 REQ_(VIEW_CLOSE, "Close the current view"), \
308 REQ_(QUIT, "Close all views and quit"), \
310 REQ_GROUP("Cursor navigation") \
311 REQ_(MOVE_UP, "Move cursor one line up"), \
312 REQ_(MOVE_DOWN, "Move cursor one line down"), \
313 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
314 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
315 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
316 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
318 REQ_GROUP("Scrolling") \
319 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
320 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
321 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
322 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
324 REQ_GROUP("Searching") \
325 REQ_(SEARCH, "Search the view"), \
326 REQ_(SEARCH_BACK, "Search backwards in the view"), \
327 REQ_(FIND_NEXT, "Find next search match"), \
328 REQ_(FIND_PREV, "Find previous search match"), \
331 REQ_(NONE, "Do nothing"), \
332 REQ_(PROMPT, "Bring up the prompt"), \
333 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
334 REQ_(SCREEN_RESIZE, "Resize the screen"), \
335 REQ_(SHOW_VERSION, "Show version information"), \
336 REQ_(STOP_LOADING, "Stop all loading views"), \
337 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
338 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization")
341 /* User action requests. */
343 #define REQ_GROUP(help)
344 #define REQ_(req, help) REQ_##req
346 /* Offset all requests to avoid conflicts with ncurses getch values. */
347 REQ_OFFSET = KEY_MAX + 1,
355 struct request_info {
356 enum request request;
362 static struct request_info req_info[] = {
363 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
364 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
371 get_request(const char *name)
373 int namelen = strlen(name);
376 for (i = 0; i < ARRAY_SIZE(req_info); i++)
377 if (req_info[i].namelen == namelen &&
378 !string_enum_compare(req_info[i].name, name, namelen))
379 return req_info[i].request;
389 static const char usage[] =
390 "tig " VERSION " (" __DATE__ ")\n"
392 "Usage: tig [options]\n"
393 " or: tig [options] [--] [git log options]\n"
394 " or: tig [options] log [git log options]\n"
395 " or: tig [options] diff [git diff options]\n"
396 " or: tig [options] show [git show options]\n"
397 " or: tig [options] < [git command output]\n"
400 " -l Start up in log view\n"
401 " -d Start up in diff view\n"
402 " -S Start up in status view\n"
403 " -n[I], --line-number[=I] Show line numbers with given interval\n"
404 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
405 " -- Mark end of tig options\n"
406 " -v, --version Show version and exit\n"
407 " -h, --help Show help message and exit\n";
409 /* Option and state variables. */
410 static bool opt_line_number = FALSE;
411 static bool opt_rev_graph = FALSE;
412 static int opt_num_interval = NUMBER_INTERVAL;
413 static int opt_tab_size = TABSIZE;
414 static enum request opt_request = REQ_VIEW_MAIN;
415 static char opt_cmd[SIZEOF_STR] = "";
416 static char opt_path[SIZEOF_STR] = "";
417 static FILE *opt_pipe = NULL;
418 static char opt_encoding[20] = "UTF-8";
419 static bool opt_utf8 = TRUE;
420 static char opt_codeset[20] = "UTF-8";
421 static iconv_t opt_iconv = ICONV_NONE;
422 static char opt_search[SIZEOF_STR] = "";
423 static char opt_cdup[SIZEOF_STR] = "";
431 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
441 int namelen = strlen(name);
445 if (strncmp(opt, name, namelen))
448 if (opt[namelen] == '=')
449 value = opt + namelen + 1;
452 if (!short_name || opt[1] != short_name)
457 va_start(args, type);
458 if (type == OPT_INT) {
459 number = va_arg(args, int *);
461 *number = atoi(value);
468 /* Returns the index of log or diff command or -1 to exit. */
470 parse_options(int argc, char *argv[])
474 for (i = 1; i < argc; i++) {
477 if (!strcmp(opt, "log") ||
478 !strcmp(opt, "diff") ||
479 !strcmp(opt, "show")) {
480 opt_request = opt[0] == 'l'
481 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
485 if (opt[0] && opt[0] != '-')
488 if (!strcmp(opt, "-l")) {
489 opt_request = REQ_VIEW_LOG;
493 if (!strcmp(opt, "-d")) {
494 opt_request = REQ_VIEW_DIFF;
498 if (!strcmp(opt, "-S")) {
499 opt_request = REQ_VIEW_STATUS;
503 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
504 opt_line_number = TRUE;
508 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
509 opt_tab_size = MIN(opt_tab_size, TABSIZE);
513 if (check_option(opt, 'v', "version", OPT_NONE)) {
514 printf("tig version %s\n", VERSION);
518 if (check_option(opt, 'h', "help", OPT_NONE)) {
523 if (!strcmp(opt, "--")) {
528 die("unknown option '%s'\n\n%s", opt, usage);
531 if (!isatty(STDIN_FILENO)) {
532 opt_request = REQ_VIEW_PAGER;
535 } else if (i < argc) {
538 if (opt_request == REQ_VIEW_MAIN)
539 /* XXX: This is vulnerable to the user overriding
540 * options required for the main view parser. */
541 string_copy(opt_cmd, "git log --pretty=raw");
543 string_copy(opt_cmd, "git");
544 buf_size = strlen(opt_cmd);
546 while (buf_size < sizeof(opt_cmd) && i < argc) {
547 opt_cmd[buf_size++] = ' ';
548 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
551 if (buf_size >= sizeof(opt_cmd))
552 die("command too long");
554 opt_cmd[buf_size] = 0;
557 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
565 * Line-oriented content detection.
569 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
570 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
571 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
572 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
573 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
574 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
575 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
576 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
577 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
578 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
579 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
580 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
581 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
582 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
583 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
584 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
585 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
586 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
587 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
588 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
589 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
590 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
591 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
592 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
593 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
594 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
595 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
598 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
599 LINE(STATUS, "", COLOR_GREEN, 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_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
603 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
604 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
605 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
606 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
607 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
608 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
609 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
610 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
611 LINE(STAT_SECTION, "", COLOR_DEFAULT, COLOR_BLUE, A_BOLD), \
612 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
613 LINE(STAT_STAGED, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
614 LINE(STAT_UNSTAGED,"", COLOR_YELLOW, COLOR_DEFAULT, 0), \
615 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
618 #define LINE(type, line, fg, bg, attr) \
625 const char *name; /* Option name. */
626 int namelen; /* Size of option name. */
627 const char *line; /* The start of line to match. */
628 int linelen; /* Size of string to match. */
629 int fg, bg, attr; /* Color and text attributes for the lines. */
632 static struct line_info line_info[] = {
633 #define LINE(type, line, fg, bg, attr) \
634 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
639 static enum line_type
640 get_line_type(char *line)
642 int linelen = strlen(line);
645 for (type = 0; type < ARRAY_SIZE(line_info); type++)
646 /* Case insensitive search matches Signed-off-by lines better. */
647 if (linelen >= line_info[type].linelen &&
648 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
655 get_line_attr(enum line_type type)
657 assert(type < ARRAY_SIZE(line_info));
658 return COLOR_PAIR(type) | line_info[type].attr;
661 static struct line_info *
662 get_line_info(char *name, int namelen)
666 for (type = 0; type < ARRAY_SIZE(line_info); type++)
667 if (namelen == line_info[type].namelen &&
668 !string_enum_compare(line_info[type].name, name, namelen))
669 return &line_info[type];
677 int default_bg = COLOR_BLACK;
678 int default_fg = COLOR_WHITE;
683 if (use_default_colors() != ERR) {
688 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
689 struct line_info *info = &line_info[type];
690 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
691 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
693 init_pair(type, fg, bg);
701 unsigned int selected:1;
703 void *data; /* User data */
713 enum request request;
714 struct keybinding *next;
717 static struct keybinding default_keybindings[] = {
719 { 'm', REQ_VIEW_MAIN },
720 { 'd', REQ_VIEW_DIFF },
721 { 'l', REQ_VIEW_LOG },
722 { 't', REQ_VIEW_TREE },
723 { 'f', REQ_VIEW_BLOB },
724 { 'p', REQ_VIEW_PAGER },
725 { 'h', REQ_VIEW_HELP },
726 { 'S', REQ_VIEW_STATUS },
728 /* View manipulation */
729 { 'q', REQ_VIEW_CLOSE },
730 { KEY_TAB, REQ_VIEW_NEXT },
731 { KEY_RETURN, REQ_ENTER },
732 { KEY_UP, REQ_PREVIOUS },
733 { KEY_DOWN, REQ_NEXT },
735 /* Cursor navigation */
736 { 'k', REQ_MOVE_UP },
737 { 'j', REQ_MOVE_DOWN },
738 { KEY_HOME, REQ_MOVE_FIRST_LINE },
739 { KEY_END, REQ_MOVE_LAST_LINE },
740 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
741 { ' ', REQ_MOVE_PAGE_DOWN },
742 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
743 { 'b', REQ_MOVE_PAGE_UP },
744 { '-', REQ_MOVE_PAGE_UP },
747 { KEY_IC, REQ_SCROLL_LINE_UP },
748 { KEY_DC, REQ_SCROLL_LINE_DOWN },
749 { 'w', REQ_SCROLL_PAGE_UP },
750 { 's', REQ_SCROLL_PAGE_DOWN },
754 { '?', REQ_SEARCH_BACK },
755 { 'n', REQ_FIND_NEXT },
756 { 'N', REQ_FIND_PREV },
760 { 'z', REQ_STOP_LOADING },
761 { 'v', REQ_SHOW_VERSION },
762 { 'r', REQ_SCREEN_REDRAW },
763 { '.', REQ_TOGGLE_LINENO },
764 { 'g', REQ_TOGGLE_REV_GRAPH },
767 /* Using the ncurses SIGWINCH handler. */
768 { KEY_RESIZE, REQ_SCREEN_RESIZE },
771 #define KEYMAP_INFO \
783 #define KEYMAP_(name) KEYMAP_##name
788 static struct int_map keymap_table[] = {
789 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
794 #define set_keymap(map, name) \
795 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
797 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
800 add_keybinding(enum keymap keymap, enum request request, int key)
802 struct keybinding *keybinding;
804 keybinding = calloc(1, sizeof(*keybinding));
806 die("Failed to allocate keybinding");
808 keybinding->alias = key;
809 keybinding->request = request;
810 keybinding->next = keybindings[keymap];
811 keybindings[keymap] = keybinding;
814 /* Looks for a key binding first in the given map, then in the generic map, and
815 * lastly in the default keybindings. */
817 get_keybinding(enum keymap keymap, int key)
819 struct keybinding *kbd;
822 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
823 if (kbd->alias == key)
826 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
827 if (kbd->alias == key)
830 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
831 if (default_keybindings[i].alias == key)
832 return default_keybindings[i].request;
834 return (enum request) key;
843 static struct key key_table[] = {
844 { "Enter", KEY_RETURN },
846 { "Backspace", KEY_BACKSPACE },
848 { "Escape", KEY_ESC },
849 { "Left", KEY_LEFT },
850 { "Right", KEY_RIGHT },
852 { "Down", KEY_DOWN },
853 { "Insert", KEY_IC },
854 { "Delete", KEY_DC },
856 { "Home", KEY_HOME },
858 { "PageUp", KEY_PPAGE },
859 { "PageDown", KEY_NPAGE },
869 { "F10", KEY_F(10) },
870 { "F11", KEY_F(11) },
871 { "F12", KEY_F(12) },
875 get_key_value(const char *name)
879 for (i = 0; i < ARRAY_SIZE(key_table); i++)
880 if (!strcasecmp(key_table[i].name, name))
881 return key_table[i].value;
883 if (strlen(name) == 1 && isprint(*name))
890 get_key(enum request request)
892 static char buf[BUFSIZ];
893 static char key_char[] = "'X'";
900 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
901 struct keybinding *keybinding = &default_keybindings[i];
905 if (keybinding->request != request)
908 for (key = 0; key < ARRAY_SIZE(key_table); key++)
909 if (key_table[key].value == keybinding->alias)
910 seq = key_table[key].name;
913 keybinding->alias < 127 &&
914 isprint(keybinding->alias)) {
915 key_char[1] = (char) keybinding->alias;
922 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
923 return "Too many keybindings!";
932 * User config file handling.
935 static struct int_map color_map[] = {
936 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
948 #define set_color(color, name) \
949 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
951 static struct int_map attr_map[] = {
952 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
962 #define set_attribute(attr, name) \
963 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
965 static int config_lineno;
966 static bool config_errors;
967 static char *config_msg;
969 /* Wants: object fgcolor bgcolor [attr] */
971 option_color_command(int argc, char *argv[])
973 struct line_info *info;
975 if (argc != 3 && argc != 4) {
976 config_msg = "Wrong number of arguments given to color command";
980 info = get_line_info(argv[0], strlen(argv[0]));
982 config_msg = "Unknown color name";
986 if (set_color(&info->fg, argv[1]) == ERR ||
987 set_color(&info->bg, argv[2]) == ERR) {
988 config_msg = "Unknown color";
992 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
993 config_msg = "Unknown attribute";
1000 /* Wants: name = value */
1002 option_set_command(int argc, char *argv[])
1005 config_msg = "Wrong number of arguments given to set command";
1009 if (strcmp(argv[1], "=")) {
1010 config_msg = "No value assigned";
1014 if (!strcmp(argv[0], "show-rev-graph")) {
1015 opt_rev_graph = (!strcmp(argv[2], "1") ||
1016 !strcmp(argv[2], "true") ||
1017 !strcmp(argv[2], "yes"));
1021 if (!strcmp(argv[0], "line-number-interval")) {
1022 opt_num_interval = atoi(argv[2]);
1026 if (!strcmp(argv[0], "tab-size")) {
1027 opt_tab_size = atoi(argv[2]);
1031 if (!strcmp(argv[0], "commit-encoding")) {
1032 char *arg = argv[2];
1033 int delimiter = *arg;
1036 switch (delimiter) {
1039 for (arg++, i = 0; arg[i]; i++)
1040 if (arg[i] == delimiter) {
1045 string_ncopy(opt_encoding, arg, strlen(arg));
1050 config_msg = "Unknown variable name";
1054 /* Wants: mode request key */
1056 option_bind_command(int argc, char *argv[])
1058 enum request request;
1063 config_msg = "Wrong number of arguments given to bind command";
1067 if (set_keymap(&keymap, argv[0]) == ERR) {
1068 config_msg = "Unknown key map";
1072 key = get_key_value(argv[1]);
1074 config_msg = "Unknown key";
1078 request = get_request(argv[2]);
1079 if (request == REQ_UNKNOWN) {
1080 config_msg = "Unknown request name";
1084 add_keybinding(keymap, request, key);
1090 set_option(char *opt, char *value)
1097 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1098 argv[argc++] = value;
1105 while (isspace(*value))
1109 if (!strcmp(opt, "color"))
1110 return option_color_command(argc, argv);
1112 if (!strcmp(opt, "set"))
1113 return option_set_command(argc, argv);
1115 if (!strcmp(opt, "bind"))
1116 return option_bind_command(argc, argv);
1118 config_msg = "Unknown option command";
1123 read_option(char *opt, int optlen, char *value, int valuelen)
1128 config_msg = "Internal error";
1130 /* Check for comment markers, since read_properties() will
1131 * only ensure opt and value are split at first " \t". */
1132 optlen = strcspn(opt, "#");
1136 if (opt[optlen] != 0) {
1137 config_msg = "No option value";
1141 /* Look for comment endings in the value. */
1142 int len = strcspn(value, "#");
1144 if (len < valuelen) {
1146 value[valuelen] = 0;
1149 status = set_option(opt, value);
1152 if (status == ERR) {
1153 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1154 config_lineno, optlen, opt, config_msg);
1155 config_errors = TRUE;
1158 /* Always keep going if errors are encountered. */
1165 char *home = getenv("HOME");
1166 char buf[SIZEOF_STR];
1170 config_errors = FALSE;
1172 if (!home || !string_format(buf, "%s/.tigrc", home))
1175 /* It's ok that the file doesn't exist. */
1176 file = fopen(buf, "r");
1180 if (read_properties(file, " \t", read_option) == ERR ||
1181 config_errors == TRUE)
1182 fprintf(stderr, "Errors while loading %s.\n", buf);
1195 /* The display array of active views and the index of the current view. */
1196 static struct view *display[2];
1197 static unsigned int current_view;
1199 /* Reading from the prompt? */
1200 static bool input_mode = FALSE;
1202 #define foreach_displayed_view(view, i) \
1203 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1205 #define displayed_views() (display[1] != NULL ? 2 : 1)
1207 /* Current head and commit ID */
1208 static char ref_blob[SIZEOF_REF] = "";
1209 static char ref_commit[SIZEOF_REF] = "HEAD";
1210 static char ref_head[SIZEOF_REF] = "HEAD";
1213 const char *name; /* View name */
1214 const char *cmd_fmt; /* Default command line format */
1215 const char *cmd_env; /* Command line set via environment */
1216 const char *id; /* Points to either of ref_{head,commit,blob} */
1218 struct view_ops *ops; /* View operations */
1220 enum keymap keymap; /* What keymap does this view have */
1222 char cmd[SIZEOF_STR]; /* Command buffer */
1223 char ref[SIZEOF_REF]; /* Hovered commit reference */
1224 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1226 int height, width; /* The width and height of the main window */
1227 WINDOW *win; /* The main window */
1228 WINDOW *title; /* The title window living below the main window */
1231 unsigned long offset; /* Offset of the window top */
1232 unsigned long lineno; /* Current line number */
1235 char grep[SIZEOF_STR]; /* Search string */
1236 regex_t *regex; /* Pre-compiled regex */
1238 /* If non-NULL, points to the view that opened this view. If this view
1239 * is closed tig will switch back to the parent view. */
1240 struct view *parent;
1243 unsigned long lines; /* Total number of lines */
1244 struct line *line; /* Line index */
1245 unsigned long line_size;/* Total number of allocated lines */
1246 unsigned int digits; /* Number of digits in the lines member. */
1254 /* What type of content being displayed. Used in the title bar. */
1256 /* Open and reads in all view content. */
1257 bool (*open)(struct view *view);
1258 /* Read one line; updates view->line. */
1259 bool (*read)(struct view *view, char *data);
1260 /* Draw one line; @lineno must be < view->height. */
1261 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1262 /* Depending on view, change display based on current line. */
1263 bool (*enter)(struct view *view, struct line *line);
1264 /* Search for regex in a line. */
1265 bool (*grep)(struct view *view, struct line *line);
1267 void (*select)(struct view *view, struct line *line);
1270 static struct view_ops pager_ops;
1271 static struct view_ops main_ops;
1272 static struct view_ops tree_ops;
1273 static struct view_ops blob_ops;
1274 static struct view_ops help_ops;
1275 static struct view_ops status_ops;
1277 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1278 { name, cmd, #env, ref, ops, map}
1280 #define VIEW_(id, name, ops, ref) \
1281 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1284 static struct view views[] = {
1285 VIEW_(MAIN, "main", &main_ops, ref_head),
1286 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1287 VIEW_(LOG, "log", &pager_ops, ref_head),
1288 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1289 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1290 VIEW_(HELP, "help", &help_ops, ""),
1291 VIEW_(PAGER, "pager", &pager_ops, ""),
1292 VIEW_(STATUS, "status", &status_ops, ""),
1295 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1297 #define foreach_view(view, i) \
1298 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1300 #define view_is_displayed(view) \
1301 (view == display[0] || view == display[1])
1304 draw_view_line(struct view *view, unsigned int lineno)
1307 bool selected = (view->offset + lineno == view->lineno);
1310 assert(view_is_displayed(view));
1312 if (view->offset + lineno >= view->lines)
1315 line = &view->line[view->offset + lineno];
1318 line->selected = TRUE;
1319 view->ops->select(view, line);
1320 } else if (line->selected) {
1321 line->selected = FALSE;
1322 wmove(view->win, lineno, 0);
1323 wclrtoeol(view->win);
1326 scrollok(view->win, FALSE);
1327 draw_ok = view->ops->draw(view, line, lineno, selected);
1328 scrollok(view->win, TRUE);
1334 redraw_view_from(struct view *view, int lineno)
1336 assert(0 <= lineno && lineno < view->height);
1338 for (; lineno < view->height; lineno++) {
1339 if (!draw_view_line(view, lineno))
1343 redrawwin(view->win);
1345 wnoutrefresh(view->win);
1347 wrefresh(view->win);
1351 redraw_view(struct view *view)
1354 redraw_view_from(view, 0);
1359 update_view_title(struct view *view)
1361 char buf[SIZEOF_STR];
1362 char state[SIZEOF_STR];
1363 size_t bufpos = 0, statelen = 0;
1365 assert(view_is_displayed(view));
1367 if (view->lines || view->pipe) {
1368 unsigned int view_lines = view->offset + view->height;
1369 unsigned int lines = view->lines
1370 ? MIN(view_lines, view->lines) * 100 / view->lines
1373 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1380 time_t secs = time(NULL) - view->start_time;
1382 /* Three git seconds are a long time ... */
1384 string_format_from(state, &statelen, " %lds", secs);
1388 string_format_from(buf, &bufpos, "[%s]", view->name);
1389 if (*view->ref && bufpos < view->width) {
1390 size_t refsize = strlen(view->ref);
1391 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1393 if (minsize < view->width)
1394 refsize = view->width - minsize + 7;
1395 string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
1398 if (statelen && bufpos < view->width) {
1399 string_format_from(buf, &bufpos, " %s", state);
1402 if (view == display[current_view])
1403 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1405 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1407 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1408 wclrtoeol(view->title);
1409 wmove(view->title, 0, view->width - 1);
1412 wnoutrefresh(view->title);
1414 wrefresh(view->title);
1418 resize_display(void)
1421 struct view *base = display[0];
1422 struct view *view = display[1] ? display[1] : display[0];
1424 /* Setup window dimensions */
1426 getmaxyx(stdscr, base->height, base->width);
1428 /* Make room for the status window. */
1432 /* Horizontal split. */
1433 view->width = base->width;
1434 view->height = SCALE_SPLIT_VIEW(base->height);
1435 base->height -= view->height;
1437 /* Make room for the title bar. */
1441 /* Make room for the title bar. */
1446 foreach_displayed_view (view, i) {
1448 view->win = newwin(view->height, 0, offset, 0);
1450 die("Failed to create %s view", view->name);
1452 scrollok(view->win, TRUE);
1454 view->title = newwin(1, 0, offset + view->height, 0);
1456 die("Failed to create title window");
1459 wresize(view->win, view->height, view->width);
1460 mvwin(view->win, offset, 0);
1461 mvwin(view->title, offset + view->height, 0);
1464 offset += view->height + 1;
1469 redraw_display(void)
1474 foreach_displayed_view (view, i) {
1476 update_view_title(view);
1481 update_display_cursor(struct view *view)
1483 /* Move the cursor to the right-most column of the cursor line.
1485 * XXX: This could turn out to be a bit expensive, but it ensures that
1486 * the cursor does not jump around. */
1488 wmove(view->win, view->lineno - view->offset, view->width - 1);
1489 wrefresh(view->win);
1497 /* Scrolling backend */
1499 do_scroll_view(struct view *view, int lines)
1501 bool redraw_current_line = FALSE;
1503 /* The rendering expects the new offset. */
1504 view->offset += lines;
1506 assert(0 <= view->offset && view->offset < view->lines);
1509 /* Move current line into the view. */
1510 if (view->lineno < view->offset) {
1511 view->lineno = view->offset;
1512 redraw_current_line = TRUE;
1513 } else if (view->lineno >= view->offset + view->height) {
1514 view->lineno = view->offset + view->height - 1;
1515 redraw_current_line = TRUE;
1518 assert(view->offset <= view->lineno && view->lineno < view->lines);
1520 /* Redraw the whole screen if scrolling is pointless. */
1521 if (view->height < ABS(lines)) {
1525 int line = lines > 0 ? view->height - lines : 0;
1526 int end = line + ABS(lines);
1528 wscrl(view->win, lines);
1530 for (; line < end; line++) {
1531 if (!draw_view_line(view, line))
1535 if (redraw_current_line)
1536 draw_view_line(view, view->lineno - view->offset);
1539 redrawwin(view->win);
1540 wrefresh(view->win);
1544 /* Scroll frontend */
1546 scroll_view(struct view *view, enum request request)
1550 assert(view_is_displayed(view));
1553 case REQ_SCROLL_PAGE_DOWN:
1554 lines = view->height;
1555 case REQ_SCROLL_LINE_DOWN:
1556 if (view->offset + lines > view->lines)
1557 lines = view->lines - view->offset;
1559 if (lines == 0 || view->offset + view->height >= view->lines) {
1560 report("Cannot scroll beyond the last line");
1565 case REQ_SCROLL_PAGE_UP:
1566 lines = view->height;
1567 case REQ_SCROLL_LINE_UP:
1568 if (lines > view->offset)
1569 lines = view->offset;
1572 report("Cannot scroll beyond the first line");
1580 die("request %d not handled in switch", request);
1583 do_scroll_view(view, lines);
1588 move_view(struct view *view, enum request request)
1590 int scroll_steps = 0;
1594 case REQ_MOVE_FIRST_LINE:
1595 steps = -view->lineno;
1598 case REQ_MOVE_LAST_LINE:
1599 steps = view->lines - view->lineno - 1;
1602 case REQ_MOVE_PAGE_UP:
1603 steps = view->height > view->lineno
1604 ? -view->lineno : -view->height;
1607 case REQ_MOVE_PAGE_DOWN:
1608 steps = view->lineno + view->height >= view->lines
1609 ? view->lines - view->lineno - 1 : view->height;
1621 die("request %d not handled in switch", request);
1624 if (steps <= 0 && view->lineno == 0) {
1625 report("Cannot move beyond the first line");
1628 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1629 report("Cannot move beyond the last line");
1633 /* Move the current line */
1634 view->lineno += steps;
1635 assert(0 <= view->lineno && view->lineno < view->lines);
1637 /* Check whether the view needs to be scrolled */
1638 if (view->lineno < view->offset ||
1639 view->lineno >= view->offset + view->height) {
1640 scroll_steps = steps;
1641 if (steps < 0 && -steps > view->offset) {
1642 scroll_steps = -view->offset;
1644 } else if (steps > 0) {
1645 if (view->lineno == view->lines - 1 &&
1646 view->lines > view->height) {
1647 scroll_steps = view->lines - view->offset - 1;
1648 if (scroll_steps >= view->height)
1649 scroll_steps -= view->height - 1;
1654 if (!view_is_displayed(view)) {
1655 view->offset += scroll_steps;
1656 assert(0 <= view->offset && view->offset < view->lines);
1657 view->ops->select(view, &view->line[view->lineno]);
1661 /* Repaint the old "current" line if we be scrolling */
1662 if (ABS(steps) < view->height)
1663 draw_view_line(view, view->lineno - steps - view->offset);
1666 do_scroll_view(view, scroll_steps);
1670 /* Draw the current line */
1671 draw_view_line(view, view->lineno - view->offset);
1673 redrawwin(view->win);
1674 wrefresh(view->win);
1683 static void search_view(struct view *view, enum request request);
1686 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1688 assert(view_is_displayed(view));
1690 if (!view->ops->grep(view, line))
1693 if (lineno - view->offset >= view->height) {
1694 view->offset = lineno;
1695 view->lineno = lineno;
1699 unsigned long old_lineno = view->lineno - view->offset;
1701 view->lineno = lineno;
1702 draw_view_line(view, old_lineno);
1704 draw_view_line(view, view->lineno - view->offset);
1705 redrawwin(view->win);
1706 wrefresh(view->win);
1709 report("Line %ld matches '%s'", lineno + 1, view->grep);
1714 find_next(struct view *view, enum request request)
1716 unsigned long lineno = view->lineno;
1721 report("No previous search");
1723 search_view(view, request);
1733 case REQ_SEARCH_BACK:
1742 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1743 lineno += direction;
1745 /* Note, lineno is unsigned long so will wrap around in which case it
1746 * will become bigger than view->lines. */
1747 for (; lineno < view->lines; lineno += direction) {
1748 struct line *line = &view->line[lineno];
1750 if (find_next_line(view, lineno, line))
1754 report("No match found for '%s'", view->grep);
1758 search_view(struct view *view, enum request request)
1763 regfree(view->regex);
1766 view->regex = calloc(1, sizeof(*view->regex));
1771 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1772 if (regex_err != 0) {
1773 char buf[SIZEOF_STR] = "unknown error";
1775 regerror(regex_err, view->regex, buf, sizeof(buf));
1776 report("Search failed: %s", buf);
1780 string_copy(view->grep, opt_search);
1782 find_next(view, request);
1786 * Incremental updating
1790 end_update(struct view *view)
1794 set_nonblocking_input(FALSE);
1795 if (view->pipe == stdin)
1803 begin_update(struct view *view)
1809 string_copy(view->cmd, opt_cmd);
1811 /* When running random commands, initially show the
1812 * command in the title. However, it maybe later be
1813 * overwritten if a commit line is selected. */
1814 string_copy(view->ref, view->cmd);
1816 } else if (view == VIEW(REQ_VIEW_TREE)) {
1817 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1818 char path[SIZEOF_STR];
1820 if (strcmp(view->vid, view->id))
1821 opt_path[0] = path[0] = 0;
1822 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1825 if (!string_format(view->cmd, format, view->id, path))
1829 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1830 const char *id = view->id;
1832 if (!string_format(view->cmd, format, id, id, id, id, id))
1835 /* Put the current ref_* value to the view title ref
1836 * member. This is needed by the blob view. Most other
1837 * views sets it automatically after loading because the
1838 * first line is a commit line. */
1839 string_copy_rev(view->ref, view->id);
1842 /* Special case for the pager view. */
1844 view->pipe = opt_pipe;
1847 view->pipe = popen(view->cmd, "r");
1853 set_nonblocking_input(TRUE);
1858 string_copy_rev(view->vid, view->id);
1863 for (i = 0; i < view->lines; i++)
1864 if (view->line[i].data)
1865 free(view->line[i].data);
1871 view->start_time = time(NULL);
1876 static struct line *
1877 realloc_lines(struct view *view, size_t line_size)
1879 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1885 view->line_size = line_size;
1890 update_view(struct view *view)
1892 char in_buffer[BUFSIZ];
1893 char out_buffer[BUFSIZ * 2];
1895 /* The number of lines to read. If too low it will cause too much
1896 * redrawing (and possible flickering), if too high responsiveness
1898 unsigned long lines = view->height;
1899 int redraw_from = -1;
1904 /* Only redraw if lines are visible. */
1905 if (view->offset + view->height >= view->lines)
1906 redraw_from = view->lines - view->offset;
1908 /* FIXME: This is probably not perfect for backgrounded views. */
1909 if (!realloc_lines(view, view->lines + lines))
1912 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1913 size_t linelen = strlen(line);
1916 line[linelen - 1] = 0;
1918 if (opt_iconv != ICONV_NONE) {
1920 size_t inlen = linelen;
1922 char *outbuf = out_buffer;
1923 size_t outlen = sizeof(out_buffer);
1927 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1928 if (ret != (size_t) -1) {
1930 linelen = strlen(out_buffer);
1934 if (!view->ops->read(view, line))
1944 lines = view->lines;
1945 for (digits = 0; lines; digits++)
1948 /* Keep the displayed view in sync with line number scaling. */
1949 if (digits != view->digits) {
1950 view->digits = digits;
1955 if (!view_is_displayed(view))
1958 if (view == VIEW(REQ_VIEW_TREE)) {
1959 /* Clear the view and redraw everything since the tree sorting
1960 * might have rearranged things. */
1963 } else if (redraw_from >= 0) {
1964 /* If this is an incremental update, redraw the previous line
1965 * since for commits some members could have changed when
1966 * loading the main view. */
1967 if (redraw_from > 0)
1970 /* Since revision graph visualization requires knowledge
1971 * about the parent commit, it causes a further one-off
1972 * needed to be redrawn for incremental updates. */
1973 if (redraw_from > 0 && opt_rev_graph)
1976 /* Incrementally draw avoids flickering. */
1977 redraw_view_from(view, redraw_from);
1980 /* Update the title _after_ the redraw so that if the redraw picks up a
1981 * commit reference in view->ref it'll be available here. */
1982 update_view_title(view);
1985 if (ferror(view->pipe)) {
1986 report("Failed to read: %s", strerror(errno));
1989 } else if (feof(view->pipe)) {
1997 report("Allocation failure");
2000 view->ops->read(view, NULL);
2005 static struct line *
2006 add_line_data(struct view *view, void *data, enum line_type type)
2008 struct line *line = &view->line[view->lines++];
2010 memset(line, 0, sizeof(*line));
2017 static struct line *
2018 add_line_text(struct view *view, char *data, enum line_type type)
2021 data = strdup(data);
2023 return data ? add_line_data(view, data, type) : NULL;
2032 OPEN_DEFAULT = 0, /* Use default view switching. */
2033 OPEN_SPLIT = 1, /* Split current view. */
2034 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2035 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2039 open_view(struct view *prev, enum request request, enum open_flags flags)
2041 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2042 bool split = !!(flags & OPEN_SPLIT);
2043 bool reload = !!(flags & OPEN_RELOAD);
2044 struct view *view = VIEW(request);
2045 int nviews = displayed_views();
2046 struct view *base_view = display[0];
2048 if (view == prev && nviews == 1 && !reload) {
2049 report("Already in %s view", view->name);
2053 if (view->ops->open) {
2054 if (!view->ops->open(view)) {
2055 report("Failed to load %s view", view->name);
2059 } else if ((reload || strcmp(view->vid, view->id)) &&
2060 !begin_update(view)) {
2061 report("Failed to load %s view", view->name);
2070 /* Maximize the current view. */
2071 memset(display, 0, sizeof(display));
2073 display[current_view] = view;
2076 /* Resize the view when switching between split- and full-screen,
2077 * or when switching between two different full-screen views. */
2078 if (nviews != displayed_views() ||
2079 (nviews == 1 && base_view != display[0]))
2082 if (split && prev->lineno - prev->offset >= prev->height) {
2083 /* Take the title line into account. */
2084 int lines = prev->lineno - prev->offset - prev->height + 1;
2086 /* Scroll the view that was split if the current line is
2087 * outside the new limited view. */
2088 do_scroll_view(prev, lines);
2091 if (prev && view != prev) {
2092 if (split && !backgrounded) {
2093 /* "Blur" the previous view. */
2094 update_view_title(prev);
2097 view->parent = prev;
2100 if (view->pipe && view->lines == 0) {
2101 /* Clear the old view and let the incremental updating refill
2110 /* If the view is backgrounded the above calls to report()
2111 * won't redraw the view title. */
2113 update_view_title(view);
2118 * User request switch noodle
2122 view_driver(struct view *view, enum request request)
2129 case REQ_MOVE_PAGE_UP:
2130 case REQ_MOVE_PAGE_DOWN:
2131 case REQ_MOVE_FIRST_LINE:
2132 case REQ_MOVE_LAST_LINE:
2133 move_view(view, request);
2136 case REQ_SCROLL_LINE_DOWN:
2137 case REQ_SCROLL_LINE_UP:
2138 case REQ_SCROLL_PAGE_DOWN:
2139 case REQ_SCROLL_PAGE_UP:
2140 scroll_view(view, request);
2145 report("No file chosen, press %s to open tree view",
2146 get_key(REQ_VIEW_TREE));
2149 open_view(view, request, OPEN_DEFAULT);
2152 case REQ_VIEW_PAGER:
2153 if (!VIEW(REQ_VIEW_PAGER)->lines) {
2154 report("No pager content, press %s to run command from prompt",
2155 get_key(REQ_PROMPT));
2158 open_view(view, request, OPEN_DEFAULT);
2166 case REQ_VIEW_STATUS:
2167 open_view(view, request, OPEN_DEFAULT);
2172 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2174 if ((view == VIEW(REQ_VIEW_DIFF) &&
2175 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2176 (view == VIEW(REQ_VIEW_BLOB) &&
2177 view->parent == VIEW(REQ_VIEW_TREE))) {
2178 view = view->parent;
2179 move_view(view, request);
2180 if (view_is_displayed(view))
2181 update_view_title(view);
2183 move_view(view, request);
2190 report("Nothing to enter");
2193 return view->ops->enter(view, &view->line[view->lineno]);
2197 int nviews = displayed_views();
2198 int next_view = (current_view + 1) % nviews;
2200 if (next_view == current_view) {
2201 report("Only one view is displayed");
2205 current_view = next_view;
2206 /* Blur out the title of the previous view. */
2207 update_view_title(view);
2211 case REQ_TOGGLE_LINENO:
2212 opt_line_number = !opt_line_number;
2216 case REQ_TOGGLE_REV_GRAPH:
2217 opt_rev_graph = !opt_rev_graph;
2222 /* Always reload^Wrerun commands from the prompt. */
2223 open_view(view, opt_request, OPEN_RELOAD);
2227 case REQ_SEARCH_BACK:
2228 search_view(view, request);
2233 find_next(view, request);
2236 case REQ_STOP_LOADING:
2237 for (i = 0; i < ARRAY_SIZE(views); i++) {
2240 report("Stopped loading the %s view", view->name),
2245 case REQ_SHOW_VERSION:
2246 report("tig-%s (built %s)", VERSION, __DATE__);
2249 case REQ_SCREEN_RESIZE:
2252 case REQ_SCREEN_REDRAW:
2260 case REQ_VIEW_CLOSE:
2261 /* XXX: Mark closed views by letting view->parent point to the
2262 * view itself. Parents to closed view should never be
2265 view->parent->parent != view->parent) {
2266 memset(display, 0, sizeof(display));
2268 display[current_view] = view->parent;
2269 view->parent = view;
2279 /* An unknown key will show most commonly used commands. */
2280 report("Unknown key, press 'h' for help");
2293 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2295 char *text = line->data;
2296 enum line_type type = line->type;
2297 int textlen = strlen(text);
2300 wmove(view->win, lineno, 0);
2304 wchgat(view->win, -1, 0, type, NULL);
2307 attr = get_line_attr(type);
2308 wattrset(view->win, attr);
2310 if (opt_line_number || opt_tab_size < TABSIZE) {
2311 static char spaces[] = " ";
2312 int col_offset = 0, col = 0;
2314 if (opt_line_number) {
2315 unsigned long real_lineno = view->offset + lineno + 1;
2317 if (real_lineno == 1 ||
2318 (real_lineno % opt_num_interval) == 0) {
2319 wprintw(view->win, "%.*d", view->digits, real_lineno);
2322 waddnstr(view->win, spaces,
2323 MIN(view->digits, STRING_SIZE(spaces)));
2325 waddstr(view->win, ": ");
2326 col_offset = view->digits + 2;
2329 while (text && col_offset + col < view->width) {
2330 int cols_max = view->width - col_offset - col;
2334 if (*text == '\t') {
2336 assert(sizeof(spaces) > TABSIZE);
2338 cols = opt_tab_size - (col % opt_tab_size);
2341 text = strchr(text, '\t');
2342 cols = line ? text - pos : strlen(pos);
2345 waddnstr(view->win, pos, MIN(cols, cols_max));
2350 int col = 0, pos = 0;
2352 for (; pos < textlen && col < view->width; pos++, col++)
2353 if (text[pos] == '\t')
2354 col += TABSIZE - (col % TABSIZE) - 1;
2356 waddnstr(view->win, text, pos);
2363 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2365 char refbuf[SIZEOF_STR];
2369 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2372 pipe = popen(refbuf, "r");
2376 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2377 ref = chomp_string(ref);
2383 /* This is the only fatal call, since it can "corrupt" the buffer. */
2384 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2391 add_pager_refs(struct view *view, struct line *line)
2393 char buf[SIZEOF_STR];
2394 char *commit_id = line->data + STRING_SIZE("commit ");
2396 size_t bufpos = 0, refpos = 0;
2397 const char *sep = "Refs: ";
2398 bool is_tag = FALSE;
2400 assert(line->type == LINE_COMMIT);
2402 refs = get_refs(commit_id);
2404 if (view == VIEW(REQ_VIEW_DIFF))
2405 goto try_add_describe_ref;
2410 struct ref *ref = refs[refpos];
2411 char *fmt = ref->tag ? "%s[%s]" :
2412 ref->remote ? "%s<%s>" : "%s%s";
2414 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2419 } while (refs[refpos++]->next);
2421 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2422 try_add_describe_ref:
2423 /* Add <tag>-g<commit_id> "fake" reference. */
2424 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2431 if (!realloc_lines(view, view->line_size + 1))
2434 add_line_text(view, buf, LINE_PP_REFS);
2438 pager_read(struct view *view, char *data)
2445 line = add_line_text(view, data, get_line_type(data));
2449 if (line->type == LINE_COMMIT &&
2450 (view == VIEW(REQ_VIEW_DIFF) ||
2451 view == VIEW(REQ_VIEW_LOG)))
2452 add_pager_refs(view, line);
2458 pager_enter(struct view *view, struct line *line)
2462 if (line->type == LINE_COMMIT &&
2463 (view == VIEW(REQ_VIEW_LOG) ||
2464 view == VIEW(REQ_VIEW_PAGER))) {
2465 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2469 /* Always scroll the view even if it was split. That way
2470 * you can use Enter to scroll through the log view and
2471 * split open each commit diff. */
2472 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2474 /* FIXME: A minor workaround. Scrolling the view will call report("")
2475 * but if we are scrolling a non-current view this won't properly
2476 * update the view title. */
2478 update_view_title(view);
2484 pager_grep(struct view *view, struct line *line)
2487 char *text = line->data;
2492 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2499 pager_select(struct view *view, struct line *line)
2501 if (line->type == LINE_COMMIT) {
2502 char *text = line->data + STRING_SIZE("commit ");
2504 if (view != VIEW(REQ_VIEW_PAGER))
2505 string_copy_rev(view->ref, text);
2506 string_copy_rev(ref_commit, text);
2510 static struct view_ops pager_ops = {
2526 help_open(struct view *view)
2529 int lines = ARRAY_SIZE(req_info) + 2;
2532 if (view->lines > 0)
2535 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2536 if (!req_info[i].request)
2539 view->line = calloc(lines, sizeof(*view->line));
2543 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2545 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2548 if (!req_info[i].request) {
2549 add_line_text(view, "", LINE_DEFAULT);
2550 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2554 key = get_key(req_info[i].request);
2555 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2558 add_line_text(view, buf, LINE_DEFAULT);
2564 static struct view_ops help_ops = {
2579 /* Parse output from git-ls-tree(1):
2581 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2582 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2583 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2584 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2587 #define SIZEOF_TREE_ATTR \
2588 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2590 #define TREE_UP_FORMAT "040000 tree %s\t.."
2593 tree_compare_entry(enum line_type type1, char *name1,
2594 enum line_type type2, char *name2)
2596 if (type1 != type2) {
2597 if (type1 == LINE_TREE_DIR)
2602 return strcmp(name1, name2);
2606 tree_read(struct view *view, char *text)
2608 size_t textlen = text ? strlen(text) : 0;
2609 char buf[SIZEOF_STR];
2611 enum line_type type;
2612 bool first_read = view->lines == 0;
2614 if (textlen <= SIZEOF_TREE_ATTR)
2617 type = text[STRING_SIZE("100644 ")] == 't'
2618 ? LINE_TREE_DIR : LINE_TREE_FILE;
2621 /* Add path info line */
2622 if (!string_format(buf, "Directory path /%s", opt_path) ||
2623 !realloc_lines(view, view->line_size + 1) ||
2624 !add_line_text(view, buf, LINE_DEFAULT))
2627 /* Insert "link" to parent directory. */
2629 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2630 !realloc_lines(view, view->line_size + 1) ||
2631 !add_line_text(view, buf, LINE_TREE_DIR))
2636 /* Strip the path part ... */
2638 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2639 size_t striplen = strlen(opt_path);
2640 char *path = text + SIZEOF_TREE_ATTR;
2642 if (pathlen > striplen)
2643 memmove(path, path + striplen,
2644 pathlen - striplen + 1);
2647 /* Skip "Directory ..." and ".." line. */
2648 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2649 struct line *line = &view->line[pos];
2650 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2651 char *path2 = text + SIZEOF_TREE_ATTR;
2652 int cmp = tree_compare_entry(line->type, path1, type, path2);
2657 text = strdup(text);
2661 if (view->lines > pos)
2662 memmove(&view->line[pos + 1], &view->line[pos],
2663 (view->lines - pos) * sizeof(*line));
2665 line = &view->line[pos];
2672 if (!add_line_text(view, text, type))
2675 /* Move the current line to the first tree entry. */
2683 tree_enter(struct view *view, struct line *line)
2685 enum open_flags flags;
2686 enum request request;
2688 switch (line->type) {
2690 /* Depending on whether it is a subdir or parent (updir?) link
2691 * mangle the path buffer. */
2692 if (line == &view->line[1] && *opt_path) {
2693 size_t path_len = strlen(opt_path);
2694 char *dirsep = opt_path + path_len - 1;
2696 while (dirsep > opt_path && dirsep[-1] != '/')
2702 size_t pathlen = strlen(opt_path);
2703 size_t origlen = pathlen;
2704 char *data = line->data;
2705 char *basename = data + SIZEOF_TREE_ATTR;
2707 if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2708 opt_path[origlen] = 0;
2713 /* Trees and subtrees share the same ID, so they are not not
2714 * unique like blobs. */
2715 flags = OPEN_RELOAD;
2716 request = REQ_VIEW_TREE;
2719 case LINE_TREE_FILE:
2720 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2721 request = REQ_VIEW_BLOB;
2728 open_view(view, request, flags);
2734 tree_select(struct view *view, struct line *line)
2736 char *text = line->data + STRING_SIZE("100644 blob ");
2738 if (line->type == LINE_TREE_FILE) {
2739 string_copy_rev(ref_blob, text);
2741 } else if (line->type != LINE_TREE_DIR) {
2745 string_copy_rev(view->ref, text);
2748 static struct view_ops tree_ops = {
2759 blob_read(struct view *view, char *line)
2761 return add_line_text(view, line, LINE_DEFAULT);
2764 static struct view_ops blob_ops = {
2783 char rev[SIZEOF_REV];
2787 char rev[SIZEOF_REV];
2789 char name[SIZEOF_STR];
2792 /* Get fields from the diff line:
2793 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2796 status_get_diff(struct status *file, char *buf, size_t bufsize)
2798 char *old_mode = buf + 1;
2799 char *new_mode = buf + 8;
2800 char *old_rev = buf + 15;
2801 char *new_rev = buf + 56;
2802 char *status = buf + 97;
2804 if (bufsize != 99 ||
2805 old_mode[-1] != ':' ||
2806 new_mode[-1] != ' ' ||
2807 old_rev[-1] != ' ' ||
2808 new_rev[-1] != ' ' ||
2812 file->status = *status;
2814 string_copy_rev(file->old.rev, old_rev);
2815 string_copy_rev(file->new.rev, new_rev);
2817 file->old.mode = strtoul(old_mode, NULL, 8);
2818 file->new.mode = strtoul(new_mode, NULL, 8);
2826 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2828 struct status *file = NULL;
2829 char buf[SIZEOF_STR * 4];
2833 pipe = popen(cmd, "r");
2837 add_line_data(view, NULL, type);
2839 while (!feof(pipe) && !ferror(pipe)) {
2843 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2846 bufsize += readsize;
2848 /* Process while we have NUL chars. */
2849 while ((sep = memchr(buf, 0, bufsize))) {
2850 size_t sepsize = sep - buf + 1;
2853 if (!realloc_lines(view, view->line_size + 1))
2856 file = calloc(1, sizeof(*file));
2860 add_line_data(view, file, type);
2863 /* Parse diff info part. */
2867 } else if (!file->status) {
2868 if (!status_get_diff(file, buf, sepsize))
2872 memmove(buf, sep + 1, bufsize);
2874 sep = memchr(buf, 0, bufsize);
2877 sepsize = sep - buf + 1;
2880 /* git-ls-files just delivers a NUL separated
2881 * list of file names similar to the second half
2882 * of the git-diff-* output. */
2883 string_ncopy(file->name, buf, sepsize);
2885 memmove(buf, sep + 1, bufsize);
2896 if (!view->line[view->lines - 1].data)
2897 add_line_data(view, NULL, LINE_STAT_NONE);
2903 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
2904 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
2905 #define STATUS_LIST_OTHER_CMD \
2906 "_git_exclude=$(git rev-parse --git-dir)/info/exclude;" \
2907 "test -f \"$_git_exclude\" && exclude=\"--exclude-from=$_git_exclude\";" \
2908 "git ls-files -z --others --exclude-per-directory=.gitignore \"$exclude\"" \
2910 /* First parse staged info using git-diff-index(1), then parse unstaged
2911 * info using git-diff-files(1), and finally untracked files using
2912 * git-ls-files(1). */
2914 status_open(struct view *view)
2918 for (i = 0; i < view->lines; i++)
2919 free(view->line[i].data);
2921 view->lines = view->line_size = 0;
2924 if (!realloc_lines(view, view->line_size + 6))
2927 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
2928 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
2929 !status_run(view, STATUS_LIST_OTHER_CMD, FALSE, LINE_STAT_UNTRACKED))
2936 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2938 struct status *status = line->data;
2940 wmove(view->win, lineno, 0);
2943 wattrset(view->win, get_line_attr(LINE_CURSOR));
2944 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
2946 } else if (!status && line->type != LINE_STAT_NONE) {
2947 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
2948 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
2951 wattrset(view->win, get_line_attr(line->type));
2957 switch (line->type) {
2958 case LINE_STAT_STAGED:
2959 text = "Changes to be committed:";
2962 case LINE_STAT_UNSTAGED:
2963 text = "Changed but not updated:";
2966 case LINE_STAT_UNTRACKED:
2967 text = "Untracked files:";
2970 case LINE_STAT_NONE:
2971 text = " (no files)";
2978 waddstr(view->win, text);
2982 waddch(view->win, status->status);
2984 wattrset(view->win, A_NORMAL);
2985 wmove(view->win, lineno, 4);
2986 waddstr(view->win, status->name);
2992 status_enter(struct view *view, struct line *line)
2994 struct status *status = line->data;
2995 char cmd[SIZEOF_STR];
2996 char buf[SIZEOF_STR];
3006 line->type != LINE_STAT_UNTRACKED &&
3007 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3010 switch (line->type) {
3011 case LINE_STAT_STAGED:
3012 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3018 string_add(cmd, cmdsize, "git update-index -z --index-info");
3021 case LINE_STAT_UNSTAGED:
3022 case LINE_STAT_UNTRACKED:
3023 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3026 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3033 pipe = popen(cmd, "w");
3037 while (!ferror(pipe) && written < bufsize) {
3038 written += fwrite(buf + written, 1, bufsize - written, pipe);
3043 if (written != bufsize)
3046 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3051 status_select(struct view *view, struct line *line)
3055 switch (line->type) {
3056 case LINE_STAT_STAGED:
3057 text = "Press Enter to unstage file for commit";
3060 case LINE_STAT_UNSTAGED:
3061 text = "Press Enter to stage file for commit ";
3064 case LINE_STAT_UNTRACKED:
3065 text = "Press Enter to stage file for addition";
3068 case LINE_STAT_NONE:
3075 string_ncopy(view->ref, text, strlen(text));
3079 status_grep(struct view *view, struct line *line)
3081 struct status *status = line->data;
3082 enum { S_STATUS, S_NAME, S_END } state;
3089 for (state = S_STATUS; state < S_END; state++) {
3093 case S_NAME: text = status->name; break;
3095 buf[0] = status->status;
3103 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3110 static struct view_ops status_ops = {
3126 char id[SIZEOF_REV]; /* SHA1 ID. */
3127 char title[128]; /* First line of the commit message. */
3128 char author[75]; /* Author of the commit. */
3129 struct tm time; /* Date from the author ident. */
3130 struct ref **refs; /* Repository references. */
3131 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3132 size_t graph_size; /* The width of the graph array. */
3135 /* Size of rev graph with no "padding" columns */
3136 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3139 struct rev_graph *prev, *next, *parents;
3140 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3142 struct commit *commit;
3146 /* Parents of the commit being visualized. */
3147 static struct rev_graph graph_parents[4];
3149 /* The current stack of revisions on the graph. */
3150 static struct rev_graph graph_stacks[4] = {
3151 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3152 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3153 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3154 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3158 graph_parent_is_merge(struct rev_graph *graph)
3160 return graph->parents->size > 1;
3164 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3166 struct commit *commit = graph->commit;
3168 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3169 commit->graph[commit->graph_size++] = symbol;
3173 done_rev_graph(struct rev_graph *graph)
3175 if (graph_parent_is_merge(graph) &&
3176 graph->pos < graph->size - 1 &&
3177 graph->next->size == graph->size + graph->parents->size - 1) {
3178 size_t i = graph->pos + graph->parents->size - 1;
3180 graph->commit->graph_size = i * 2;
3181 while (i < graph->next->size - 1) {
3182 append_to_rev_graph(graph, ' ');
3183 append_to_rev_graph(graph, '\\');
3188 graph->size = graph->pos = 0;
3189 graph->commit = NULL;
3190 memset(graph->parents, 0, sizeof(*graph->parents));
3194 push_rev_graph(struct rev_graph *graph, char *parent)
3198 /* "Collapse" duplicate parents lines.
3200 * FIXME: This needs to also update update the drawn graph but
3201 * for now it just serves as a method for pruning graph lines. */
3202 for (i = 0; i < graph->size; i++)
3203 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3206 if (graph->size < SIZEOF_REVITEMS) {
3207 string_copy_rev(graph->rev[graph->size++], parent);
3212 get_rev_graph_symbol(struct rev_graph *graph)
3216 if (graph->parents->size == 0)
3217 symbol = REVGRAPH_INIT;
3218 else if (graph_parent_is_merge(graph))
3219 symbol = REVGRAPH_MERGE;
3220 else if (graph->pos >= graph->size)
3221 symbol = REVGRAPH_BRANCH;
3223 symbol = REVGRAPH_COMMIT;
3229 draw_rev_graph(struct rev_graph *graph)
3232 chtype separator, line;
3234 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3235 static struct rev_filler fillers[] = {
3236 { ' ', REVGRAPH_LINE },
3241 chtype symbol = get_rev_graph_symbol(graph);
3242 struct rev_filler *filler;
3245 filler = &fillers[DEFAULT];
3247 for (i = 0; i < graph->pos; i++) {
3248 append_to_rev_graph(graph, filler->line);
3249 if (graph_parent_is_merge(graph->prev) &&
3250 graph->prev->pos == i)
3251 filler = &fillers[RSHARP];
3253 append_to_rev_graph(graph, filler->separator);
3256 /* Place the symbol for this revision. */
3257 append_to_rev_graph(graph, symbol);
3259 if (graph->prev->size > graph->size)
3260 filler = &fillers[RDIAG];
3262 filler = &fillers[DEFAULT];
3266 for (; i < graph->size; i++) {
3267 append_to_rev_graph(graph, filler->separator);
3268 append_to_rev_graph(graph, filler->line);
3269 if (graph_parent_is_merge(graph->prev) &&
3270 i < graph->prev->pos + graph->parents->size)
3271 filler = &fillers[RSHARP];
3272 if (graph->prev->size > graph->size)
3273 filler = &fillers[LDIAG];
3276 if (graph->prev->size > graph->size) {
3277 append_to_rev_graph(graph, filler->separator);
3278 if (filler->line != ' ')
3279 append_to_rev_graph(graph, filler->line);
3283 /* Prepare the next rev graph */
3285 prepare_rev_graph(struct rev_graph *graph)
3289 /* First, traverse all lines of revisions up to the active one. */
3290 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3291 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3294 push_rev_graph(graph->next, graph->rev[graph->pos]);
3297 /* Interleave the new revision parent(s). */
3298 for (i = 0; i < graph->parents->size; i++)
3299 push_rev_graph(graph->next, graph->parents->rev[i]);
3301 /* Lastly, put any remaining revisions. */
3302 for (i = graph->pos + 1; i < graph->size; i++)
3303 push_rev_graph(graph->next, graph->rev[i]);
3307 update_rev_graph(struct rev_graph *graph)
3309 /* If this is the finalizing update ... */
3311 prepare_rev_graph(graph);
3313 /* Graph visualization needs a one rev look-ahead,
3314 * so the first update doesn't visualize anything. */
3315 if (!graph->prev->commit)
3318 draw_rev_graph(graph->prev);
3319 done_rev_graph(graph->prev->prev);
3328 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3330 char buf[DATE_COLS + 1];
3331 struct commit *commit = line->data;
3332 enum line_type type;
3338 if (!*commit->author)
3341 wmove(view->win, lineno, col);
3345 wattrset(view->win, get_line_attr(type));
3346 wchgat(view->win, -1, 0, type, NULL);
3349 type = LINE_MAIN_COMMIT;
3350 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3353 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3354 waddnstr(view->win, buf, timelen);
3355 waddstr(view->win, " ");
3358 wmove(view->win, lineno, col);
3359 if (type != LINE_CURSOR)
3360 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3363 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3365 authorlen = strlen(commit->author);
3366 if (authorlen > AUTHOR_COLS - 2) {
3367 authorlen = AUTHOR_COLS - 2;
3373 waddnstr(view->win, commit->author, authorlen);
3374 if (type != LINE_CURSOR)
3375 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3376 waddch(view->win, '~');
3378 waddstr(view->win, commit->author);
3382 if (type != LINE_CURSOR)
3383 wattrset(view->win, A_NORMAL);
3385 if (opt_rev_graph && commit->graph_size) {
3388 wmove(view->win, lineno, col);
3389 /* Using waddch() instead of waddnstr() ensures that
3390 * they'll be rendered correctly for the cursor line. */
3391 for (i = 0; i < commit->graph_size; i++)
3392 waddch(view->win, commit->graph[i]);
3394 waddch(view->win, ' ');
3395 col += commit->graph_size + 1;
3398 wmove(view->win, lineno, col);
3404 if (type == LINE_CURSOR)
3406 else if (commit->refs[i]->tag)
3407 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3408 else if (commit->refs[i]->remote)
3409 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3411 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3412 waddstr(view->win, "[");
3413 waddstr(view->win, commit->refs[i]->name);
3414 waddstr(view->win, "]");
3415 if (type != LINE_CURSOR)
3416 wattrset(view->win, A_NORMAL);
3417 waddstr(view->win, " ");
3418 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3419 } while (commit->refs[i++]->next);
3422 if (type != LINE_CURSOR)
3423 wattrset(view->win, get_line_attr(type));
3426 int titlelen = strlen(commit->title);
3428 if (col + titlelen > view->width)
3429 titlelen = view->width - col;
3431 waddnstr(view->win, commit->title, titlelen);
3437 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3439 main_read(struct view *view, char *line)
3441 static struct rev_graph *graph = graph_stacks;
3442 enum line_type type;
3443 struct commit *commit;
3446 update_rev_graph(graph);
3450 type = get_line_type(line);
3451 if (type == LINE_COMMIT) {
3452 commit = calloc(1, sizeof(struct commit));
3456 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3457 commit->refs = get_refs(commit->id);
3458 graph->commit = commit;
3459 add_line_data(view, commit, LINE_MAIN_COMMIT);
3465 commit = view->line[view->lines - 1].data;
3469 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3474 /* Parse author lines where the name may be empty:
3475 * author <email@address.tld> 1138474660 +0100
3477 char *ident = line + STRING_SIZE("author ");
3478 char *nameend = strchr(ident, '<');
3479 char *emailend = strchr(ident, '>');
3481 if (!nameend || !emailend)
3484 update_rev_graph(graph);
3485 graph = graph->next;
3487 *nameend = *emailend = 0;
3488 ident = chomp_string(ident);
3490 ident = chomp_string(nameend + 1);
3495 string_ncopy(commit->author, ident, strlen(ident));
3497 /* Parse epoch and timezone */
3498 if (emailend[1] == ' ') {
3499 char *secs = emailend + 2;
3500 char *zone = strchr(secs, ' ');
3501 time_t time = (time_t) atol(secs);
3503 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3507 tz = ('0' - zone[1]) * 60 * 60 * 10;
3508 tz += ('0' - zone[2]) * 60 * 60;
3509 tz += ('0' - zone[3]) * 60;
3510 tz += ('0' - zone[4]) * 60;
3518 gmtime_r(&time, &commit->time);
3523 /* Fill in the commit title if it has not already been set. */
3524 if (commit->title[0])
3527 /* Require titles to start with a non-space character at the
3528 * offset used by git log. */
3529 if (strncmp(line, " ", 4))
3532 /* Well, if the title starts with a whitespace character,
3533 * try to be forgiving. Otherwise we end up with no title. */
3534 while (isspace(*line))
3538 /* FIXME: More graceful handling of titles; append "..." to
3539 * shortened titles, etc. */
3541 string_ncopy(commit->title, line, strlen(line));
3548 main_enter(struct view *view, struct line *line)
3550 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3552 open_view(view, REQ_VIEW_DIFF, flags);
3557 main_grep(struct view *view, struct line *line)
3559 struct commit *commit = line->data;
3560 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3561 char buf[DATE_COLS + 1];
3564 for (state = S_TITLE; state < S_END; state++) {
3568 case S_TITLE: text = commit->title; break;
3569 case S_AUTHOR: text = commit->author; break;
3571 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3580 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3588 main_select(struct view *view, struct line *line)
3590 struct commit *commit = line->data;
3592 string_copy_rev(view->ref, commit->id);
3593 string_copy_rev(ref_commit, view->ref);
3596 static struct view_ops main_ops = {
3608 * Unicode / UTF-8 handling
3610 * NOTE: Much of the following code for dealing with unicode is derived from
3611 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3612 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3615 /* I've (over)annotated a lot of code snippets because I am not entirely
3616 * confident that the approach taken by this small UTF-8 interface is correct.
3620 unicode_width(unsigned long c)
3623 (c <= 0x115f /* Hangul Jamo */
3626 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
3628 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
3629 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
3630 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
3631 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
3632 || (c >= 0xffe0 && c <= 0xffe6)
3633 || (c >= 0x20000 && c <= 0x2fffd)
3634 || (c >= 0x30000 && c <= 0x3fffd)))
3640 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3641 * Illegal bytes are set one. */
3642 static const unsigned char utf8_bytes[256] = {
3643 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,
3644 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,
3645 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,
3646 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,
3647 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,
3648 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,
3649 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,
3650 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,
3653 /* Decode UTF-8 multi-byte representation into a unicode character. */
3654 static inline unsigned long
3655 utf8_to_unicode(const char *string, size_t length)
3657 unsigned long unicode;
3661 unicode = string[0];
3664 unicode = (string[0] & 0x1f) << 6;
3665 unicode += (string[1] & 0x3f);
3668 unicode = (string[0] & 0x0f) << 12;
3669 unicode += ((string[1] & 0x3f) << 6);
3670 unicode += (string[2] & 0x3f);
3673 unicode = (string[0] & 0x0f) << 18;
3674 unicode += ((string[1] & 0x3f) << 12);
3675 unicode += ((string[2] & 0x3f) << 6);
3676 unicode += (string[3] & 0x3f);
3679 unicode = (string[0] & 0x0f) << 24;
3680 unicode += ((string[1] & 0x3f) << 18);
3681 unicode += ((string[2] & 0x3f) << 12);
3682 unicode += ((string[3] & 0x3f) << 6);
3683 unicode += (string[4] & 0x3f);
3686 unicode = (string[0] & 0x01) << 30;
3687 unicode += ((string[1] & 0x3f) << 24);
3688 unicode += ((string[2] & 0x3f) << 18);
3689 unicode += ((string[3] & 0x3f) << 12);
3690 unicode += ((string[4] & 0x3f) << 6);
3691 unicode += (string[5] & 0x3f);
3694 die("Invalid unicode length");
3697 /* Invalid characters could return the special 0xfffd value but NUL
3698 * should be just as good. */
3699 return unicode > 0xffff ? 0 : unicode;
3702 /* Calculates how much of string can be shown within the given maximum width
3703 * and sets trimmed parameter to non-zero value if all of string could not be
3706 * Additionally, adds to coloffset how many many columns to move to align with
3707 * the expected position. Takes into account how multi-byte and double-width
3708 * characters will effect the cursor position.
3710 * Returns the number of bytes to output from string to satisfy max_width. */
3712 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3714 const char *start = string;
3715 const char *end = strchr(string, '\0');
3721 while (string < end) {
3722 int c = *(unsigned char *) string;
3723 unsigned char bytes = utf8_bytes[c];
3725 unsigned long unicode;
3727 if (string + bytes > end)
3730 /* Change representation to figure out whether
3731 * it is a single- or double-width character. */
3733 unicode = utf8_to_unicode(string, bytes);
3734 /* FIXME: Graceful handling of invalid unicode character. */
3738 ucwidth = unicode_width(unicode);
3740 if (width > max_width) {
3745 /* The column offset collects the differences between the
3746 * number of bytes encoding a character and the number of
3747 * columns will be used for rendering said character.
3749 * So if some character A is encoded in 2 bytes, but will be
3750 * represented on the screen using only 1 byte this will and up
3751 * adding 1 to the multi-byte column offset.
3753 * Assumes that no double-width character can be encoding in
3754 * less than two bytes. */
3755 if (bytes > ucwidth)
3756 mbwidth += bytes - ucwidth;
3761 *coloffset += mbwidth;
3763 return string - start;
3771 /* Whether or not the curses interface has been initialized. */
3772 static bool cursed = FALSE;
3774 /* The status window is used for polling keystrokes. */
3775 static WINDOW *status_win;
3777 static bool status_empty = TRUE;
3779 /* Update status and title window. */
3781 report(const char *msg, ...)
3783 struct view *view = display[current_view];
3788 if (!status_empty || *msg) {
3791 va_start(args, msg);
3793 wmove(status_win, 0, 0);
3795 vwprintw(status_win, msg, args);
3796 status_empty = FALSE;
3798 status_empty = TRUE;
3800 wclrtoeol(status_win);
3801 wrefresh(status_win);
3806 update_view_title(view);
3807 update_display_cursor(view);
3810 /* Controls when nodelay should be in effect when polling user input. */
3812 set_nonblocking_input(bool loading)
3814 static unsigned int loading_views;
3816 if ((loading == FALSE && loading_views-- == 1) ||
3817 (loading == TRUE && loading_views++ == 0))
3818 nodelay(status_win, loading);
3826 /* Initialize the curses library */
3827 if (isatty(STDIN_FILENO)) {
3828 cursed = !!initscr();
3830 /* Leave stdin and stdout alone when acting as a pager. */
3831 FILE *io = fopen("/dev/tty", "r+");
3834 die("Failed to open /dev/tty");
3835 cursed = !!newterm(NULL, io, io);
3839 die("Failed to initialize curses");
3841 nonl(); /* Tell curses not to do NL->CR/NL on output */
3842 cbreak(); /* Take input chars one at a time, no wait for \n */
3843 noecho(); /* Don't echo input */
3844 leaveok(stdscr, TRUE);
3849 getmaxyx(stdscr, y, x);
3850 status_win = newwin(1, 0, y - 1, 0);
3852 die("Failed to create status window");
3854 /* Enable keyboard mapping */
3855 keypad(status_win, TRUE);
3856 wbkgdset(status_win, get_line_attr(LINE_STATUS));
3860 read_prompt(const char *prompt)
3862 enum { READING, STOP, CANCEL } status = READING;
3863 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3866 while (status == READING) {
3872 foreach_view (view, i)
3877 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3878 wclrtoeol(status_win);
3880 /* Refresh, accept single keystroke of input */
3881 key = wgetch(status_win);
3886 status = pos ? STOP : CANCEL;
3904 if (pos >= sizeof(buf)) {
3905 report("Input string too long");
3910 buf[pos++] = (char) key;
3914 /* Clear the status window */
3915 status_empty = FALSE;
3918 if (status == CANCEL)
3927 * Repository references
3930 static struct ref *refs;
3931 static size_t refs_size;
3933 /* Id <-> ref store */
3934 static struct ref ***id_refs;
3935 static size_t id_refs_size;
3937 static struct ref **
3940 struct ref ***tmp_id_refs;
3941 struct ref **ref_list = NULL;
3942 size_t ref_list_size = 0;
3945 for (i = 0; i < id_refs_size; i++)
3946 if (!strcmp(id, id_refs[i][0]->id))
3949 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3953 id_refs = tmp_id_refs;
3955 for (i = 0; i < refs_size; i++) {
3958 if (strcmp(id, refs[i].id))
3961 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3969 if (ref_list_size > 0)
3970 ref_list[ref_list_size - 1]->next = 1;
3971 ref_list[ref_list_size] = &refs[i];
3973 /* XXX: The properties of the commit chains ensures that we can
3974 * safely modify the shared ref. The repo references will
3975 * always be similar for the same id. */
3976 ref_list[ref_list_size]->next = 0;
3981 id_refs[id_refs_size++] = ref_list;
3987 read_ref(char *id, int idlen, char *name, int namelen)
3991 bool remote = FALSE;
3993 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3994 /* Commits referenced by tags has "^{}" appended. */
3995 if (name[namelen - 1] != '}')
3998 while (namelen > 0 && name[namelen] != '^')
4002 namelen -= STRING_SIZE("refs/tags/");
4003 name += STRING_SIZE("refs/tags/");
4005 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4007 namelen -= STRING_SIZE("refs/remotes/");
4008 name += STRING_SIZE("refs/remotes/");
4010 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4011 namelen -= STRING_SIZE("refs/heads/");
4012 name += STRING_SIZE("refs/heads/");
4014 } else if (!strcmp(name, "HEAD")) {
4018 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4022 ref = &refs[refs_size++];
4023 ref->name = malloc(namelen + 1);
4027 strncpy(ref->name, name, namelen);
4028 ref->name[namelen] = 0;
4030 ref->remote = remote;
4031 string_copy_rev(ref->id, id);
4039 const char *cmd_env = getenv("TIG_LS_REMOTE");
4040 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4042 return read_properties(popen(cmd, "r"), "\t", read_ref);
4046 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
4048 if (!strcmp(name, "i18n.commitencoding"))
4049 string_ncopy(opt_encoding, value, valuelen);
4055 load_repo_config(void)
4057 return read_properties(popen("git repo-config --list", "r"),
4058 "=", read_repo_config_option);
4062 read_repo_info(char *name, int namelen, char *value, int valuelen)
4065 string_ncopy(opt_cdup, name, namelen);
4070 load_repo_info(void)
4072 return read_properties(popen("git rev-parse --show-cdup", "r"),
4073 "=", read_repo_info);
4077 read_properties(FILE *pipe, const char *separators,
4078 int (*read_property)(char *, int, char *, int))
4080 char buffer[BUFSIZ];
4087 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4092 name = chomp_string(name);
4093 namelen = strcspn(name, separators);
4095 if (name[namelen]) {
4097 value = chomp_string(name + namelen + 1);
4098 valuelen = strlen(value);
4105 state = read_property(name, namelen, value, valuelen);
4108 if (state != ERR && ferror(pipe))
4121 static void __NORETURN
4124 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4130 static void __NORETURN
4131 die(const char *err, ...)
4137 va_start(args, err);
4138 fputs("tig: ", stderr);
4139 vfprintf(stderr, err, args);
4140 fputs("\n", stderr);
4147 main(int argc, char *argv[])
4150 enum request request;
4153 signal(SIGINT, quit);
4155 if (setlocale(LC_ALL, "")) {
4156 char *codeset = nl_langinfo(CODESET);
4158 string_ncopy(opt_codeset, codeset, strlen(codeset));
4161 if (load_options() == ERR)
4162 die("Failed to load user config.");
4164 /* Load the repo config file so options can be overwritten from
4165 * the command line. */
4166 if (load_repo_config() == ERR)
4167 die("Failed to load repo config.");
4169 if (load_repo_info() == ERR)
4170 die("Failed to load repo info.");
4172 if (!parse_options(argc, argv))
4175 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4176 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4177 if (opt_iconv == ICONV_NONE)
4178 die("Failed to initialize character set conversion");
4181 if (load_refs() == ERR)
4182 die("Failed to load refs.");
4184 /* Require a git repository unless when running in pager mode. */
4185 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
4186 die("Not a git repository");
4188 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4189 view->cmd_env = getenv(view->cmd_env);
4191 request = opt_request;
4195 while (view_driver(display[current_view], request)) {
4199 foreach_view (view, i)
4202 /* Refresh, accept single keystroke of input */
4203 key = wgetch(status_win);
4205 /* wgetch() with nodelay() enabled returns ERR when there's no
4212 request = get_keybinding(display[current_view]->keymap, key);
4214 /* Some low-level request handling. This keeps access to
4215 * status_win restricted. */
4219 char *cmd = read_prompt(":");
4221 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4222 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4223 opt_request = REQ_VIEW_DIFF;
4225 opt_request = REQ_VIEW_PAGER;
4234 case REQ_SEARCH_BACK:
4236 const char *prompt = request == REQ_SEARCH
4238 char *search = read_prompt(prompt);
4241 string_ncopy(opt_search, search, strlen(search));
4246 case REQ_SCREEN_RESIZE:
4250 getmaxyx(stdscr, height, width);
4252 /* Resize the status view and let the view driver take
4253 * care of resizing the displayed views. */
4254 wresize(status_win, 1, width);
4255 mvwin(status_win, height - 1, 0);
4256 wrefresh(status_win);